]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
wasted checked, configure will do them for us
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana 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 "propwin.h"
36 #include "stacking.h"
37 #include "openbox.h"
38 #include "group.h"
39 #include "config.h"
40 #include "menuframe.h"
41 #include "keyboard.h"
42 #include "mouse.h"
43 #include "render/render.h"
44
45 #ifdef HAVE_UNISTD_H
46 #  include <unistd.h>
47 #endif
48
49 #include <glib.h>
50 #include <X11/Xutil.h>
51
52 /*! The event mask to grab on client windows */
53 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
54                           ColormapChangeMask)
55
56 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
57                                 ButtonMotionMask)
58
59 typedef struct
60 {
61     ObClientCallback func;
62     gpointer data;
63 } ClientCallback;
64
65 GList            *client_list          = NULL;
66
67 static GSList *client_destroy_notifies = NULL;
68
69 static void client_get_all(ObClient *self, gboolean real);
70 static void client_get_startup_id(ObClient *self);
71 static void client_get_session_ids(ObClient *self);
72 static void client_get_area(ObClient *self);
73 static void client_get_desktop(ObClient *self);
74 static void client_get_state(ObClient *self);
75 static void client_get_shaped(ObClient *self);
76 static void client_get_mwm_hints(ObClient *self);
77 static void client_get_colormap(ObClient *self);
78 static void client_change_allowed_actions(ObClient *self);
79 static void client_change_state(ObClient *self);
80 static void client_change_wm_state(ObClient *self);
81 static void client_apply_startup_state(ObClient *self,
82                                        gint x, gint y, gint w, gint h);
83 static void client_restore_session_state(ObClient *self);
84 static gboolean client_restore_session_stacking(ObClient *self);
85 static ObAppSettings *client_get_settings_state(ObClient *self);
86 static void client_update_transient_tree(ObClient *self,
87                                          ObGroup *oldgroup, ObGroup *newgroup,
88                                          ObClient* oldparent,
89                                          ObClient *newparent);
90 static void client_present(ObClient *self, gboolean here, gboolean raise);
91 static GSList *client_search_all_top_parents_internal(ObClient *self,
92                                                       gboolean bylayer,
93                                                       ObStackingLayer layer);
94 static void client_call_notifies(ObClient *self, GSList *list);
95
96 void client_startup(gboolean reconfig)
97 {
98     if (reconfig) return;
99
100     client_set_list();
101 }
102
103 void client_shutdown(gboolean reconfig)
104 {
105     if (reconfig) return;
106 }
107
108 static void client_call_notifies(ObClient *self, GSList *list)
109 {
110     GSList *it;
111
112     for (it = list; it; it = g_slist_next(it)) {
113         ClientCallback *d = it->data;
114         d->func(self, d->data);
115     }
116 }
117
118 void client_add_destroy_notify(ObClientCallback func, gpointer data)
119 {
120     ClientCallback *d = g_new(ClientCallback, 1);
121     d->func = func;
122     d->data = data;
123     client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
124 }
125
126 void client_remove_destroy_notify(ObClientCallback func)
127 {
128     GSList *it;
129
130     for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
131         ClientCallback *d = it->data;
132         if (d->func == func) {
133             g_free(d);
134             client_destroy_notifies =
135                 g_slist_delete_link(client_destroy_notifies, it);
136             break;
137         }
138     }
139 }
140
141 void client_set_list()
142 {
143     Window *windows, *win_it;
144     GList *it;
145     guint size = g_list_length(client_list);
146
147     /* create an array of the window ids */
148     if (size > 0) {
149         windows = g_new(Window, size);
150         win_it = windows;
151         for (it = client_list; it; it = g_list_next(it), ++win_it)
152             *win_it = ((ObClient*)it->data)->window;
153     } else
154         windows = NULL;
155
156     PROP_SETA32(RootWindow(ob_display, ob_screen),
157                 net_client_list, window, (gulong*)windows, size);
158
159     if (windows)
160         g_free(windows);
161
162     stacking_set_list();
163 }
164
165 /*
166   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
167   {
168   GSList *it;
169
170   for (it = self->transients; it; it = g_slist_next(it)) {
171   if (!func(it->data, data)) return;
172   client_foreach_transient(it->data, func, data);
173   }
174   }
175
176   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
177   {
178   if (self->transient_for) {
179   if (self->transient_for != OB_TRAN_GROUP) {
180   if (!func(self->transient_for, data)) return;
181   client_foreach_ancestor(self->transient_for, func, data);
182   } else {
183   GSList *it;
184
185   for (it = self->group->members; it; it = g_slist_next(it))
186   if (it->data != self &&
187   !((ObClient*)it->data)->transient_for) {
188   if (!func(it->data, data)) return;
189   client_foreach_ancestor(it->data, func, data);
190   }
191   }
192   }
193   }
194 */
195
196 void client_manage_all()
197 {
198     guint i, j, nchild;
199     Window w, *children;
200     XWMHints *wmhints;
201     XWindowAttributes attrib;
202
203     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
204                &w, &w, &children, &nchild);
205
206     /* remove all icon windows from the list */
207     for (i = 0; i < nchild; i++) {
208         if (children[i] == None) continue;
209         wmhints = XGetWMHints(ob_display, children[i]);
210         if (wmhints) {
211             if ((wmhints->flags & IconWindowHint) &&
212                 (wmhints->icon_window != children[i]))
213                 for (j = 0; j < nchild; j++)
214                     if (children[j] == wmhints->icon_window) {
215                         children[j] = None;
216                         break;
217                     }
218             XFree(wmhints);
219         }
220     }
221
222     for (i = 0; i < nchild; ++i) {
223         if (children[i] == None)
224             continue;
225         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
226             if (attrib.override_redirect) continue;
227
228             if (attrib.map_state != IsUnmapped)
229                 client_manage(children[i]);
230         }
231     }
232     XFree(children);
233 }
234
235 void client_manage(Window window)
236 {
237     ObClient *self;
238     XEvent e;
239     XWindowAttributes attrib;
240     XSetWindowAttributes attrib_set;
241     XWMHints *wmhint;
242     gboolean activate = FALSE;
243     ObAppSettings *settings;
244     gint placex, placey, placew, placeh;
245     gboolean transient = FALSE;
246
247     grab_server(TRUE);
248
249     /* check if it has already been unmapped by the time we started
250        mapping. the grab does a sync so we don't have to here */
251     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
252         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
253     {
254         XPutBackEvent(ob_display, &e);
255
256         ob_debug("Trying to manage unmapped window. Aborting that.\n");
257         grab_server(FALSE);
258         return; /* don't manage it */
259     }
260
261     /* make sure it isn't an override-redirect window */
262     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
263         attrib.override_redirect)
264     {
265         grab_server(FALSE);
266         return; /* don't manage it */
267     }
268   
269     /* is the window a docking app */
270     if ((wmhint = XGetWMHints(ob_display, window))) {
271         if ((wmhint->flags & StateHint) &&
272             wmhint->initial_state == WithdrawnState)
273         {
274             dock_add(window, wmhint);
275             grab_server(FALSE);
276             XFree(wmhint);
277             return;
278         }
279         XFree(wmhint);
280     }
281
282     ob_debug("Managing window: 0x%lx\n", window);
283
284     /* choose the events we want to receive on the CLIENT window */
285     attrib_set.event_mask = CLIENT_EVENTMASK;
286     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
287     XChangeWindowAttributes(ob_display, window,
288                             CWEventMask|CWDontPropagate, &attrib_set);
289
290     /* create the ObClient struct, and populate it from the hints on the
291        window */
292     self = g_new0(ObClient, 1);
293     self->obwin.type = Window_Client;
294     self->window = window;
295
296     /* non-zero defaults */
297     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
298     self->gravity = NorthWestGravity;
299     self->desktop = screen_num_desktops; /* always an invalid value */
300     self->user_time = focus_client ? focus_client->user_time : CurrentTime;
301
302     /* get all the stuff off the window */
303     client_get_all(self, TRUE);
304
305     /* specify that if we exit, the window should not be destroyed and
306        should be reparented back to root automatically */
307     XChangeSaveSet(ob_display, window, SetModeInsert);
308
309     /* create the decoration frame for the client window */
310     self->frame = frame_new(self);
311
312     frame_grab_client(self->frame);
313
314     /* we've grabbed everything and set everything that we need to at mapping
315        time now */
316     grab_server(FALSE);
317
318     /* per-app settings override stuff from client_get_all, and return the
319        settings for other uses too. the returned settings is a shallow copy,
320        that needs to be freed with g_free(). */
321     settings = client_get_settings_state(self);
322     /* the session should get the last say thought */
323     client_restore_session_state(self);
324
325     /* now we have all of the window's information so we can set this up */
326     client_setup_decor_and_functions(self, FALSE);
327
328     {
329         Time t = sn_app_started(self->startup_id, self->class);
330         if (t) self->user_time = t;
331     }
332
333     /* do this after we have a frame.. it uses the frame to help determine the
334        WM_STATE to apply. */
335     client_change_state(self);
336
337     /* add ourselves to the focus order */
338     focus_order_add_new(self);
339
340     /* do this to add ourselves to the stacking list in a non-intrusive way */
341     client_calc_layer(self);
342
343     /* focus the new window? */
344     if (ob_state() != OB_STATE_STARTING &&
345         (!self->session || self->session->focused) &&
346         !self->iconic &&
347         /* this means focus=true for window is same as config_focus_new=true */
348         ((config_focus_new || (settings && settings->focus == 1)) ||
349          client_search_focus_tree_full(self)) &&
350         /* this checks for focus=false for the window */
351         (!settings || settings->focus != 0) &&
352         /* note the check against Type_Normal/Dialog, not client_normal(self),
353            which would also include other types. in this case we want more
354            strict rules for focus */
355         (self->type == OB_CLIENT_TYPE_NORMAL ||
356          self->type == OB_CLIENT_TYPE_DIALOG))
357     {
358         activate = TRUE;
359     }
360
361     /* remove the client's border */
362     XSetWindowBorderWidth(ob_display, self->window, 0);
363
364     /* adjust the frame to the client's size before showing or placing
365        the window */
366     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
367     frame_adjust_client_area(self->frame);
368
369     /* where the frame was placed is where the window was originally */
370     placex = self->area.x;
371     placey = self->area.y;
372     placew = self->area.width;
373     placeh = self->area.height;
374
375     /* figure out placement for the window if the window is new */
376     if (ob_state() == OB_STATE_RUNNING) {
377         ob_debug("Positioned: %s @ %d %d\n",
378                  (!self->positioned ? "no" :
379                   (self->positioned == PPosition ? "program specified" :
380                    (self->positioned == USPosition ? "user specified" :
381                     (self->positioned == (PPosition | USPosition) ?
382                      "program + user specified" :
383                      "BADNESS !?")))), placex, placey);
384
385         ob_debug("Sized: %s @ %d %d\n",
386                  (!self->sized ? "no" :
387                   (self->sized == PSize ? "program specified" :
388                    (self->sized == USSize ? "user specified" :
389                     (self->sized == (PSize | USSize) ?
390                      "program + user specified" :
391                      "BADNESS !?")))), placew, placeh);
392
393         transient = place_client(self, &placex, &placey, settings);
394
395         /* make sure the window is visible. */
396         client_find_onscreen(self, &placex, &placey, placew, placeh,
397                              /* non-normal clients has less rules, and
398                                 windows that are being restored from a
399                                 session do also. we can assume you want
400                                 it back where you saved it. Clients saying
401                                 they placed themselves are subjected to
402                                 harder rules, ones that are placed by
403                                 place.c or by the user are allowed partially
404                                 off-screen and on xinerama divides (ie,
405                                 it is up to the placement routines to avoid
406                                 the xinerama divides) */
407                              transient ||
408                              (!(self->positioned & USPosition) &&
409                               client_normal(self) &&
410                               !self->session));
411     }
412
413     /* if the window isn't user-sized, then make it fit inside
414        the visible screen area on its monitor. Use basically the same rules
415        for forcing the window on screen in the client_find_onscreen call.
416
417        do this after place_client, it chooses the monitor! */
418     if (transient ||
419         (!(self->sized & USSize) &&
420          client_normal(self) &&
421          !self->session))
422     {
423         /* make a copy to modify */
424         Rect a = *screen_area_monitor(self->desktop, client_monitor(self));
425
426         /* shrink by the frame's area */
427         a.width -= self->frame->size.left + self->frame->size.right;
428         a.height -= self->frame->size.top + self->frame->size.bottom;
429
430         /* fit the window inside the area */
431         if (placew > a.width || self->area.height > a.height) {
432             placew = MIN(self->area.width, a.width);
433             placeh = MIN(self->area.height, a.height);
434
435             ob_debug("setting window size to %dx%d\n",
436                      self->area.width, self->area.height);
437         }
438     }
439
440
441     ob_debug("placing window 0x%x at %d, %d with size %d x %d. "
442              "some restrictions may apply\n",
443              self->window, placex, placey, placew, placeh);
444     if (self->session)
445         ob_debug("  but session requested %d, %d  %d x %d instead, "
446                  "overriding\n",
447                  self->session->x, self->session->y,
448                  self->session->w, self->session->h);
449
450     /* do this after the window is placed, so the premax/prefullscreen numbers
451        won't be all wacko!!
452
453        this also places the window
454     */
455     client_apply_startup_state(self, placex, placey, placew, placeh);
456
457     if (activate) {
458         guint32 last_time = focus_client ?
459             focus_client->user_time : CurrentTime;
460
461         /* This is focus stealing prevention */
462         ob_debug_type(OB_DEBUG_FOCUS,
463                       "Want to focus new window 0x%x with time %u "
464                       "(last time %u)\n",
465                       self->window, self->user_time, last_time);
466
467         /* if it's on another desktop */
468         if (!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
469             && /* the timestamp is from before you changed desktops */
470             self->user_time && screen_desktop_user_time &&
471             !event_time_after(self->user_time, screen_desktop_user_time))
472         {
473             activate = FALSE;
474             ob_debug_type(OB_DEBUG_FOCUS,
475                           "Not focusing the window because its on another "
476                           "desktop\n");
477         }
478         /* If something is focused, and it's not our relative... */
479         else if (focus_client && client_search_focus_tree_full(self) == NULL)
480         {
481             /* If time stamp is old, don't steal focus */
482             if (self->user_time && last_time &&
483                 !event_time_after(self->user_time, last_time))
484             {
485                 activate = FALSE;
486                 ob_debug_type(OB_DEBUG_FOCUS,
487                               "Not focusing the window because the time is "
488                               "too old\n");
489             }
490             /* If its a transient (and parents aren't focused) and the time
491                is ambiguous (either the current focus target doesn't have
492                a timestamp, or they are the same (we probably inherited it
493                from them) */
494             else if (client_has_parent(self) &&
495                      (!last_time || self->user_time == last_time))
496             {
497                 activate = FALSE;
498                 ob_debug_type(OB_DEBUG_FOCUS,
499                               "Not focusing the window because it is a "
500                               "transient, and the time is very ambiguous\n");
501             }
502             /* Don't steal focus from globally active clients.
503                I stole this idea from KWin. It seems nice.
504              */
505             else if (!(focus_client->can_focus ||
506                        focus_client->focus_notify))
507             {
508                 activate = FALSE;
509                 ob_debug_type(OB_DEBUG_FOCUS,
510                               "Not focusing the window because a globally "
511                               "active client has focus\n");
512             }
513             /* Don't move focus if it's not going to go to this window
514                anyway */
515             else if (client_focus_target(self) != self) {
516                 activate = FALSE;
517                 ob_debug_type(OB_DEBUG_FOCUS,
518                               "Not focusing the window because another window "
519                               "would get the focus anyway\n");
520             }
521         }
522
523         if (!activate) {
524             ob_debug_type(OB_DEBUG_FOCUS,
525                           "Focus stealing prevention activated for %s with "
526                           "time %u (last time %u)\n",
527                           self->title, self->user_time, last_time);
528             /* if the client isn't focused, then hilite it so the user
529                knows it is there */
530             client_hilite(self, TRUE);
531         }
532     }
533     else {
534         /* This may look rather odd. Well it's because new windows are added
535            to the stacking order non-intrusively. If we're not going to focus
536            the new window or hilite it, then we raise it to the top. This will
537            take affect for things that don't get focused like splash screens.
538            Also if you don't have focus_new enabled, then it's going to get
539            raised to the top. Legacy begets legacy I guess?
540         */
541         if (!client_restore_session_stacking(self))
542             stacking_raise(CLIENT_AS_WINDOW(self));
543     }
544
545     mouse_grab_for_client(self, TRUE);
546
547     /* this has to happen before we try focus the window, but we want it to
548        happen after the client's stacking has been determined or it looks bad
549     */
550     client_show(self);
551
552     if (activate) {
553         gboolean stacked = client_restore_session_stacking(self);
554         client_present(self, FALSE, !stacked);
555     }
556
557     /* add to client list/map */
558     client_list = g_list_append(client_list, self);
559     g_hash_table_insert(window_map, &self->window, self);
560
561     /* this has to happen after we're in the client_list */
562     if (STRUT_EXISTS(self->strut))
563         screen_update_areas();
564
565     /* update the list hints */
566     client_set_list();
567
568     /* free the ObAppSettings shallow copy */
569     g_free(settings);
570
571     ob_debug("Managed window 0x%lx plate 0x%x (%s)\n",
572              window, self->frame->window, self->class);
573
574     return;
575 }
576
577
578 ObClient *client_fake_manage(Window window)
579 {
580     ObClient *self;
581     ObAppSettings *settings;
582
583     ob_debug("Pretend-managing window: %lx\n", window);
584
585     /* do this minimal stuff to figure out the client's decorations */
586
587     self = g_new0(ObClient, 1);
588     self->window = window;
589
590     client_get_all(self, FALSE);
591     /* per-app settings override stuff, and return the settings for other
592        uses too. this returns a shallow copy that needs to be freed */
593     settings = client_get_settings_state(self);
594
595     client_setup_decor_and_functions(self, FALSE);
596
597     /* create the decoration frame for the client window and adjust its size */
598     self->frame = frame_new(self);
599     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
600
601     ob_debug("gave extents left %d right %d top %d bottom %d\n",
602              self->frame->size.left, self->frame->size.right, 
603              self->frame->size.top, self->frame->size.bottom);
604
605     /* free the ObAppSettings shallow copy */
606     g_free(settings);
607
608     return self;
609 }
610
611 void client_unmanage_all()
612 {
613     while (client_list != NULL)
614         client_unmanage(client_list->data);
615 }
616
617 void client_unmanage(ObClient *self)
618 {
619     guint j;
620     GSList *it;
621
622     ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)\n",
623              self->window, self->frame->window,
624              self->class, self->title ? self->title : "");
625
626     g_assert(self != NULL);
627
628     /* we dont want events no more. do this before hiding the frame so we
629        don't generate more events */
630     XSelectInput(ob_display, self->window, NoEventMask);
631
632     frame_hide(self->frame);
633     /* flush to send the hide to the server quickly */
634     XFlush(ob_display);
635
636     /* ignore enter events from the unmap so it doesnt mess with the
637        focus */
638     event_ignore_all_queued_enters();
639
640     mouse_grab_for_client(self, FALSE);
641
642     /* remove the window from our save set */
643     XChangeSaveSet(ob_display, self->window, SetModeDelete);
644
645     /* kill the property windows */
646     propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
647
648     /* update the focus lists */
649     focus_order_remove(self);
650     if (client_focused(self)) {
651         /* don't leave an invalid focus_client */
652         focus_client = NULL;
653     }
654
655     client_list = g_list_remove(client_list, self);
656     stacking_remove(self);
657     g_hash_table_remove(window_map, &self->window);
658
659     /* once the client is out of the list, update the struts to remove its
660        influence */
661     if (STRUT_EXISTS(self->strut))
662         screen_update_areas();
663
664     client_call_notifies(self, client_destroy_notifies);
665
666     /* tell our parent(s) that we're gone */
667     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
668         for (it = self->group->members; it; it = g_slist_next(it))
669             if (it->data != self)
670                 ((ObClient*)it->data)->transients =
671                     g_slist_remove(((ObClient*)it->data)->transients,self);
672     } else if (self->transient_for) {        /* transient of window */
673         self->transient_for->transients =
674             g_slist_remove(self->transient_for->transients, self);
675     }
676
677     /* tell our transients that we're gone */
678     for (it = self->transients; it; it = g_slist_next(it)) {
679         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
680             ((ObClient*)it->data)->transient_for = NULL;
681             client_calc_layer(it->data);
682         }
683     }
684
685     /* remove from its group */
686     if (self->group) {
687         group_remove(self->group, self);
688         self->group = NULL;
689     }
690
691     /* restore the window's original geometry so it is not lost */
692     {
693         Rect a;
694
695         a = self->area;
696
697         if (self->fullscreen)
698             a = self->pre_fullscreen_area;
699         else if (self->max_horz || self->max_vert) {
700             if (self->max_horz) {
701                 a.x = self->pre_max_area.x;
702                 a.width = self->pre_max_area.width;
703             }
704             if (self->max_vert) {
705                 a.y = self->pre_max_area.y;
706                 a.height = self->pre_max_area.height;
707             }
708         }
709
710         self->fullscreen = self->max_horz = self->max_vert = FALSE;
711         /* let it be moved and resized no matter what */
712         self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE;
713         self->decorations = 0; /* unmanaged windows have no decor */
714
715         /* give the client its border back */
716         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
717
718         client_move_resize(self, a.x, a.y, a.width, a.height);
719     }
720
721     /* reparent the window out of the frame, and free the frame */
722     frame_release_client(self->frame);
723     frame_free(self->frame);
724     self->frame = NULL;
725
726     if (ob_state() != OB_STATE_EXITING) {
727         /* these values should not be persisted across a window
728            unmapping/mapping */
729         PROP_ERASE(self->window, net_wm_desktop);
730         PROP_ERASE(self->window, net_wm_state);
731         PROP_ERASE(self->window, wm_state);
732     } else {
733         /* if we're left in an unmapped state, the client wont be mapped.
734            this is bad, since we will no longer be managing the window on
735            restart */
736         XMapWindow(ob_display, self->window);
737     }
738
739     /* update the list hints */
740     client_set_list();
741
742     ob_debug("Unmanaged window 0x%lx\n", self->window);
743
744     /* free all data allocated in the client struct */
745     g_slist_free(self->transients);
746     for (j = 0; j < self->nicons; ++j)
747         g_free(self->icons[j].data);
748     if (self->nicons > 0)
749         g_free(self->icons);
750     g_free(self->wm_command);
751     g_free(self->title);
752     g_free(self->icon_title);
753     g_free(self->name);
754     g_free(self->class);
755     g_free(self->role);
756     g_free(self->client_machine);
757     g_free(self->sm_client_id);
758     g_free(self);
759 }
760
761 void client_fake_unmanage(ObClient *self)
762 {
763     /* this is all that got allocated to get the decorations */
764
765     frame_free(self->frame);
766     g_free(self);
767 }
768
769 /*! Returns a new structure containing the per-app settings for this client.
770   The returned structure needs to be freed with g_free. */
771 static ObAppSettings *client_get_settings_state(ObClient *self)
772 {
773     ObAppSettings *settings;
774     GSList *it;
775
776     settings = config_create_app_settings();
777
778     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
779         ObAppSettings *app = it->data;
780         gboolean match = TRUE;
781
782         g_assert(app->name != NULL || app->class != NULL);
783
784         /* we know that either name or class is not NULL so it will have to
785            match to use the rule */
786         if (app->name &&
787             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
788             match = FALSE;
789         else if (app->class &&
790                 !g_pattern_match(app->class,
791                                  strlen(self->class), self->class, NULL))
792             match = FALSE;
793         else if (app->role &&
794                  !g_pattern_match(app->role,
795                                   strlen(self->role), self->role, NULL))
796             match = FALSE;
797
798         if (match) {
799             ob_debug("Window matching: %s\n", app->name);
800
801             /* copy the settings to our struct, overriding the existing
802                settings if they are not defaults */
803             config_app_settings_copy_non_defaults(app, settings);
804         }
805     }
806
807     if (settings->shade != -1)
808         self->shaded = !!settings->shade;
809     if (settings->decor != -1)
810         self->undecorated = !settings->decor;
811     if (settings->iconic != -1)
812         self->iconic = !!settings->iconic;
813     if (settings->skip_pager != -1)
814         self->skip_pager = !!settings->skip_pager;
815     if (settings->skip_taskbar != -1)
816         self->skip_taskbar = !!settings->skip_taskbar;
817
818     if (settings->max_vert != -1)
819         self->max_vert = !!settings->max_vert;
820     if (settings->max_horz != -1)
821         self->max_horz = !!settings->max_horz;
822
823     if (settings->fullscreen != -1)
824         self->fullscreen = !!settings->fullscreen;
825
826     if (settings->desktop) {
827         if (settings->desktop == DESKTOP_ALL)
828             self->desktop = settings->desktop;
829         else if (settings->desktop > 0 &&
830                  settings->desktop <= screen_num_desktops)
831             self->desktop = settings->desktop - 1;
832     }
833
834     if (settings->layer == -1) {
835         self->below = TRUE;
836         self->above = FALSE;
837     }
838     else if (settings->layer == 0) {
839         self->below = FALSE;
840         self->above = FALSE;
841     }
842     else if (settings->layer == 1) {
843         self->below = FALSE;
844         self->above = TRUE;
845     }
846     return settings;
847 }
848
849 static void client_restore_session_state(ObClient *self)
850 {
851     GList *it;
852
853     ob_debug_type(OB_DEBUG_SM,
854                   "Restore session for client %s\n", self->title);
855
856     if (!(it = session_state_find(self))) {
857         ob_debug_type(OB_DEBUG_SM,
858                       "Session data not found for client %s\n", self->title);
859         return;
860     }
861
862     self->session = it->data;
863
864     ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s\n",
865                   self->title);
866
867     RECT_SET_POINT(self->area, self->session->x, self->session->y);
868     self->positioned = USPosition;
869     self->sized = USSize;
870     if (self->session->w > 0)
871         self->area.width = self->session->w;
872     if (self->session->h > 0)
873         self->area.height = self->session->h;
874     XResizeWindow(ob_display, self->window,
875                   self->area.width, self->area.height);
876
877     self->desktop = (self->session->desktop == DESKTOP_ALL ?
878                      self->session->desktop :
879                      MIN(screen_num_desktops - 1, self->session->desktop));
880     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
881
882     self->shaded = self->session->shaded;
883     self->iconic = self->session->iconic;
884     self->skip_pager = self->session->skip_pager;
885     self->skip_taskbar = self->session->skip_taskbar;
886     self->fullscreen = self->session->fullscreen;
887     self->above = self->session->above;
888     self->below = self->session->below;
889     self->max_horz = self->session->max_horz;
890     self->max_vert = self->session->max_vert;
891     self->undecorated = self->session->undecorated;
892 }
893
894 static gboolean client_restore_session_stacking(ObClient *self)
895 {
896     GList *it, *mypos;
897
898     if (!self->session) return FALSE;
899
900     mypos = g_list_find(session_saved_state, self->session);
901     if (!mypos) return FALSE;
902
903     /* start above me and look for the first client */
904     for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
905         GList *cit;
906
907         for (cit = client_list; cit; cit = g_list_next(cit)) {
908             ObClient *c = cit->data;
909             /* found a client that was in the session, so go below it */
910             if (c->session == it->data) {
911                 stacking_below(CLIENT_AS_WINDOW(self),
912                                CLIENT_AS_WINDOW(cit->data));
913                 return TRUE;
914             }
915         }
916     }
917     return FALSE;
918 }
919
920 void client_move_onscreen(ObClient *self, gboolean rude)
921 {
922     gint x = self->area.x;
923     gint y = self->area.y;
924     if (client_find_onscreen(self, &x, &y,
925                              self->area.width,
926                              self->area.height, rude)) {
927         client_move(self, x, y);
928     }
929 }
930
931 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
932                               gboolean rude)
933 {
934     Rect *mon_a, *all_a;
935     gint ox = *x, oy = *y;
936     gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
937     gint fw, fh;
938     Rect desired;
939
940     RECT_SET(desired, *x, *y, w, h);
941     all_a = screen_area(self->desktop);
942     mon_a = screen_area_monitor(self->desktop, screen_find_monitor(&desired));
943
944     /* get where the frame would be */
945     frame_client_gravity(self->frame, x, y, w, h);
946
947     /* get the requested size of the window with decorations */
948     fw = self->frame->size.left + w + self->frame->size.right;
949     fh = self->frame->size.top + h + self->frame->size.bottom;
950
951     /* This makes sure windows aren't entirely outside of the screen so you
952        can't see them at all.
953        It makes sure 10% of the window is on the screen at least. At don't let
954        it move itself off the top of the screen, which would hide the titlebar
955        on you. (The user can still do this if they want too, it's only limiting
956        the application.
957
958        XXX watch for xinerama dead areas...
959     */
960     if (client_normal(self)) {
961         if (!self->strut.right && *x + fw/10 >= all_a->x + all_a->width - 1)
962             *x = all_a->x + all_a->width - fw/10;
963         if (!self->strut.bottom && *y + fh/10 >= all_a->y + all_a->height - 1)
964             *y = all_a->y + all_a->height - fh/10;
965         if (!self->strut.left && *x + fw*9/10 - 1 < all_a->x)
966             *x = all_a->x - fw*9/10;
967         if (!self->strut.top && *y + fh*9/10 - 1 < all_a->y)
968             *y = all_a->y - fw*9/10;
969     }
970
971     /* If rudeness wasn't requested, then figure out of the client is currently
972        entirely on the screen. If it is, and the position isn't changing by
973        request, and it is enlarging, then be rude even though it wasn't
974        requested */
975     if (!rude) {
976         Point oldtl, oldtr, oldbl, oldbr;
977         Point newtl, newtr, newbl, newbr;
978         gboolean stationary_l, stationary_r, stationary_t, stationary_b;
979
980         POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
981         POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1,
982                   self->frame->area.y + self->frame->area.height - 1);
983         POINT_SET(oldtr, oldbr.x, oldtl.y);
984         POINT_SET(oldbl, oldtl.x, oldbr.y);
985
986         POINT_SET(newtl, *x, *y);
987         POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
988         POINT_SET(newtr, newbr.x, newtl.y);
989         POINT_SET(newbl, newtl.x, newbr.y);
990
991         /* is it moving or just resizing from some corner? */
992         stationary_l = oldtl.x == newtl.x;
993         stationary_r = oldtr.x == newtr.x;
994         stationary_t = oldtl.y == newtl.y;
995         stationary_b = oldbl.y == newbl.y;
996
997         /* if left edge is growing and didnt move right edge */
998         if (stationary_r && newtl.x < oldtl.x)
999             rudel = TRUE;
1000         /* if right edge is growing and didnt move left edge */
1001         if (stationary_l && newtr.x > oldtr.x)
1002             ruder = TRUE;
1003         /* if top edge is growing and didnt move bottom edge */
1004         if (stationary_b && newtl.y < oldtl.y)
1005             rudet = TRUE;
1006         /* if bottom edge is growing and didnt move top edge */
1007         if (stationary_t && newbl.y > oldbl.y)
1008             rudeb = TRUE;
1009     }
1010
1011     /* This here doesn't let windows even a pixel outside the struts/screen.
1012      * When called from client_manage, programs placing themselves are
1013      * forced completely onscreen, while things like
1014      * xterm -geometry resolution-width/2 will work fine. Trying to
1015      * place it completely offscreen will be handled in the above code.
1016      * Sorry for this confused comment, i am tired. */
1017     if (rudel && !self->strut.left && *x < mon_a->x) *x = mon_a->x;
1018     if (ruder && !self->strut.right && *x + fw > mon_a->x + mon_a->width)
1019         *x = mon_a->x + MAX(0, mon_a->width - fw);
1020
1021     if (rudet && !self->strut.top && *y < mon_a->y) *y = mon_a->y;
1022     if (rudeb && !self->strut.bottom && *y + fh > mon_a->y + mon_a->height)
1023         *y = mon_a->y + MAX(0, mon_a->height - fh);
1024
1025     /* get where the client should be */
1026     frame_frame_gravity(self->frame, x, y, w, h);
1027
1028     return ox != *x || oy != *y;
1029 }
1030
1031 static void client_get_all(ObClient *self, gboolean real)
1032 {
1033     /* this is needed for the frame to set itself up */
1034     client_get_area(self);
1035
1036     /* these things can change the decor and functions of the window */
1037
1038     client_get_mwm_hints(self);
1039     /* this can change the mwmhints for special cases */
1040     client_get_type_and_transientness(self);
1041     client_get_state(self);
1042     client_update_normal_hints(self);
1043
1044     /* get the session related properties, these can change decorations
1045        from per-app settings */
1046     client_get_session_ids(self);
1047
1048     /* now we got everything that can affect the decorations */
1049     if (!real)
1050         return;
1051
1052     /* get this early so we have it for debugging */
1053     client_update_title(self);
1054
1055     client_update_protocols(self);
1056
1057     client_update_wmhints(self);
1058     /* this may have already been called from client_update_wmhints */
1059     if (self->transient_for == NULL)
1060         client_update_transient_for(self);
1061
1062     client_get_startup_id(self);
1063     client_get_desktop(self);/* uses transient data/group/startup id if a
1064                                 desktop is not specified */
1065     client_get_shaped(self);
1066
1067     {
1068         /* a couple type-based defaults for new windows */
1069
1070         /* this makes sure that these windows appear on all desktops */
1071         if (self->type == OB_CLIENT_TYPE_DESKTOP)
1072             self->desktop = DESKTOP_ALL;
1073     }
1074   
1075 #ifdef SYNC
1076     client_update_sync_request_counter(self);
1077 #endif
1078
1079     client_get_colormap(self);
1080     client_update_strut(self);
1081     client_update_icons(self);
1082     client_update_user_time_window(self);
1083     if (!self->user_time_window) /* check if this would have been called */
1084         client_update_user_time(self);
1085     client_update_icon_geometry(self);
1086 }
1087
1088 static void client_get_startup_id(ObClient *self)
1089 {
1090     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
1091         if (self->group)
1092             PROP_GETS(self->group->leader,
1093                       net_startup_id, utf8, &self->startup_id);
1094 }
1095
1096 static void client_get_area(ObClient *self)
1097 {
1098     XWindowAttributes wattrib;
1099     Status ret;
1100   
1101     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1102     g_assert(ret != BadWindow);
1103
1104     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1105     POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1106     self->border_width = wattrib.border_width;
1107
1108     ob_debug("client area: %d %d  %d %d  bw %d\n", wattrib.x, wattrib.y,
1109              wattrib.width, wattrib.height, wattrib.border_width);
1110 }
1111
1112 static void client_get_desktop(ObClient *self)
1113 {
1114     guint32 d = screen_num_desktops; /* an always-invalid value */
1115
1116     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
1117         if (d >= screen_num_desktops && d != DESKTOP_ALL)
1118             self->desktop = screen_num_desktops - 1;
1119         else
1120             self->desktop = d;
1121         ob_debug("client requested desktop 0x%x\n", self->desktop); 
1122     } else {
1123         gboolean trdesk = FALSE;
1124
1125         if (self->transient_for) {
1126             if (self->transient_for != OB_TRAN_GROUP) {
1127                 if (self->transient_for->desktop != DESKTOP_ALL) {
1128                     self->desktop = self->transient_for->desktop;
1129                     trdesk = TRUE;
1130                 }
1131             } else {
1132                 /* if all the group is on one desktop, then open it on the
1133                    same desktop */
1134                 GSList *it;
1135                 gboolean first = TRUE;
1136                 guint all = screen_num_desktops; /* not a valid value */
1137
1138                 for (it = self->group->members; it; it = g_slist_next(it)) {
1139                     ObClient *c = it->data;
1140                     if (c != self) {
1141                         if (first) {
1142                             all = c->desktop;
1143                             first = FALSE;
1144                         }
1145                         else if (all != c->desktop)
1146                             all = screen_num_desktops; /* make it invalid */
1147                     }
1148                 }
1149                 if (all != screen_num_desktops) {
1150                     self->desktop = all;
1151                     trdesk = TRUE;
1152                 }
1153             }
1154         }
1155         if (!trdesk) {
1156             /* try get from the startup-notification protocol */
1157             if (sn_get_desktop(self->startup_id, &self->desktop)) {
1158                 if (self->desktop >= screen_num_desktops &&
1159                     self->desktop != DESKTOP_ALL)
1160                     self->desktop = screen_num_desktops - 1;
1161             } else
1162                 /* defaults to the current desktop */
1163                 self->desktop = screen_desktop;
1164         }
1165     }
1166 }
1167
1168 static void client_get_state(ObClient *self)
1169 {
1170     guint32 *state;
1171     guint num;
1172   
1173     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
1174         gulong i;
1175         for (i = 0; i < num; ++i) {
1176             if (state[i] == prop_atoms.net_wm_state_modal)
1177                 self->modal = TRUE;
1178             else if (state[i] == prop_atoms.net_wm_state_shaded)
1179                 self->shaded = TRUE;
1180             else if (state[i] == prop_atoms.net_wm_state_hidden)
1181                 self->iconic = TRUE;
1182             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
1183                 self->skip_taskbar = TRUE;
1184             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
1185                 self->skip_pager = TRUE;
1186             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
1187                 self->fullscreen = TRUE;
1188             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
1189                 self->max_vert = TRUE;
1190             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
1191                 self->max_horz = TRUE;
1192             else if (state[i] == prop_atoms.net_wm_state_above)
1193                 self->above = TRUE;
1194             else if (state[i] == prop_atoms.net_wm_state_below)
1195                 self->below = TRUE;
1196             else if (state[i] == prop_atoms.net_wm_state_demands_attention)
1197                 self->demands_attention = TRUE;
1198             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
1199                 self->undecorated = TRUE;
1200         }
1201
1202         g_free(state);
1203     }
1204 }
1205
1206 static void client_get_shaped(ObClient *self)
1207 {
1208     self->shaped = FALSE;
1209 #ifdef   SHAPE
1210     if (extensions_shape) {
1211         gint foo;
1212         guint ufoo;
1213         gint s;
1214
1215         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
1216
1217         XShapeQueryExtents(ob_display, self->window, &s, &foo,
1218                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1219                            &ufoo);
1220         self->shaped = (s != 0);
1221     }
1222 #endif
1223 }
1224
1225 void client_update_transient_for(ObClient *self)
1226 {
1227     Window t = None;
1228     ObClient *target = NULL;
1229
1230     if (XGetTransientForHint(ob_display, self->window, &t)) {
1231         if (t != self->window) { /* cant be transient to itself! */
1232             target = g_hash_table_lookup(window_map, &t);
1233             /* if this happens then we need to check for it*/
1234             g_assert(target != self);
1235             if (target && !WINDOW_IS_CLIENT(target)) {
1236                 /* this can happen when a dialog is a child of
1237                    a dockapp, for example */
1238                 target = NULL;
1239             }
1240
1241             /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat..
1242
1243                Setting the transient_for to Root is actually illegal, however
1244                applications from time have done this to specify transient for
1245                their group.
1246
1247                Now you can do that by being a TYPE_DIALOG and not setting
1248                the transient_for hint at all on your window. But people still
1249                use Root, and Kwin is very strange in this regard.
1250
1251                KWin 3.0 will not consider windows with transient_for set to
1252                Root as transient for their group *UNLESS* they are also modal.
1253                In that case, it will make them transient for the group. This
1254                leads to all sorts of weird behavior from KDE apps which are
1255                only tested in KWin. I'd like to follow their behavior just to
1256                make this work right with KDE stuff, but that seems wrong.
1257             */
1258             if (!target && self->group) {
1259                 /* not transient to a client, see if it is transient for a
1260                    group */
1261                 if (t == RootWindow(ob_display, ob_screen)) {
1262                     /* window is a transient for its group! */
1263                     target = OB_TRAN_GROUP;
1264                 }
1265             }
1266         }
1267     } else if (self->transient && self->group)
1268         target = OB_TRAN_GROUP;
1269
1270     client_update_transient_tree(self, self->group, self->group,
1271                                  self->transient_for, target);
1272     self->transient_for = target;
1273                           
1274 }
1275
1276 static void client_update_transient_tree(ObClient *self,
1277                                          ObGroup *oldgroup, ObGroup *newgroup,
1278                                          ObClient* oldparent,
1279                                          ObClient *newparent)
1280 {
1281     GSList *it, *next;
1282     ObClient *c;
1283
1284     /* * *
1285       Group transient windows are not allowed to have other group
1286       transient windows as their children.
1287       * * */
1288
1289
1290     /* No change has occured */
1291     if (oldgroup == newgroup && oldparent == newparent) return;
1292
1293     /** Remove the client from the transient tree wherever it has changed **/
1294
1295     /* If the window is becoming a direct transient for a window in its group
1296        then any group transients which were our children and are now becoming
1297        our parents need to stop being our children.
1298
1299        Group transients can't be children of group transients already, but
1300        we could have any number of direct parents above up, any of which could
1301        be transient for the group, and we need to remove it from our children.
1302     */
1303     if (oldparent != newparent &&
1304         newparent != NULL && newparent != OB_TRAN_GROUP &&
1305         newgroup != NULL && newgroup == oldgroup)
1306     {
1307         ObClient *look = newparent;
1308         do {
1309             self->transients = g_slist_remove(self->transients, look);
1310             look = look->transient_for;
1311         } while (look != NULL && look != OB_TRAN_GROUP);
1312     }
1313             
1314
1315     /* If the group changed, or if we are just becoming transient for the
1316        group, then we need to remove any old group transient windows
1317        from our children. But if we were already transient for the group, then
1318        other group transients are not our children. */
1319     if ((oldgroup != newgroup ||
1320          (newparent == OB_TRAN_GROUP && oldparent != newparent)) &&
1321         oldgroup != NULL && oldparent != OB_TRAN_GROUP)
1322     {
1323         for (it = self->transients; it; it = next) {
1324             next = g_slist_next(it);
1325             c = it->data;
1326             if (c->group == oldgroup)
1327                 self->transients = g_slist_delete_link(self->transients, it);
1328         }
1329     }
1330
1331     /* If we used to be transient for a group and now we are not, or we're
1332        transient for a new group, then we need to remove ourselves from all
1333        our ex-parents */
1334     if (oldparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1335                                        oldparent != newparent))
1336     {
1337         for (it = oldgroup->members; it; it = g_slist_next(it)) {
1338             c = it->data;
1339             if (c != self && (!c->transient_for ||
1340                               c->transient_for != OB_TRAN_GROUP))
1341                 c->transients = g_slist_remove(c->transients, self);
1342         }
1343     }
1344     /* If we used to be transient for a single window and we are no longer
1345        transient for it, then we need to remove ourself from its children */
1346     else if (oldparent != NULL && oldparent != OB_TRAN_GROUP &&
1347              oldparent != newparent)
1348         oldparent->transients = g_slist_remove(oldparent->transients, self);
1349
1350
1351     /** Re-add the client to the transient tree wherever it has changed **/
1352
1353     /* If we're now transient for a group and we weren't transient for it
1354        before then we need to add ourselves to all our new parents */
1355     if (newparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1356                                        oldparent != newparent))
1357     {
1358         for (it = oldgroup->members; it; it = g_slist_next(it)) {
1359             c = it->data;
1360             if (c != self && (!c->transient_for ||
1361                               c->transient_for != OB_TRAN_GROUP))
1362                 c->transients = g_slist_prepend(c->transients, self);
1363         }
1364     }
1365     /* If we are now transient for a single window which we weren't before,
1366        we need to add ourselves to its children
1367
1368        WARNING: Cyclical transient ness is possible if two windows are
1369        transient for eachother.
1370     */
1371     else if (newparent != NULL && newparent != OB_TRAN_GROUP &&
1372              newparent != oldparent &&
1373              /* don't make ourself its child if it is already our child */
1374              !client_is_direct_child(self, newparent))
1375         newparent->transients = g_slist_prepend(newparent->transients, self);
1376
1377     /* If the group changed then we need to add any new group transient
1378        windows to our children. But if we're transient for the group, then
1379        other group transients are not our children.
1380
1381        WARNING: Cyclical transient-ness is possible. For e.g. if:
1382        A is transient for the group
1383        B is transient for A
1384        C is transient for B
1385        A can't be transient for C or we have a cycle
1386     */
1387     if (oldgroup != newgroup && newgroup != NULL &&
1388         newparent != OB_TRAN_GROUP)
1389     {
1390         for (it = newgroup->members; it; it = g_slist_next(it)) {
1391             c = it->data;
1392             if (c != self && c->transient_for == OB_TRAN_GROUP &&
1393                 /* Don't make it our child if it is already our parent */
1394                 !client_is_direct_child(c, self))
1395             {
1396                 self->transients = g_slist_prepend(self->transients, c);
1397             }
1398         }
1399     }
1400 }
1401
1402 static void client_get_mwm_hints(ObClient *self)
1403 {
1404     guint num;
1405     guint32 *hints;
1406
1407     self->mwmhints.flags = 0; /* default to none */
1408
1409     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1410                     &hints, &num)) {
1411         if (num >= OB_MWM_ELEMENTS) {
1412             self->mwmhints.flags = hints[0];
1413             self->mwmhints.functions = hints[1];
1414             self->mwmhints.decorations = hints[2];
1415         }
1416         g_free(hints);
1417     }
1418 }
1419
1420 void client_get_type_and_transientness(ObClient *self)
1421 {
1422     guint num, i;
1423     guint32 *val;
1424     Window t;
1425
1426     self->type = -1;
1427     self->transient = FALSE;
1428   
1429     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1430         /* use the first value that we know about in the array */
1431         for (i = 0; i < num; ++i) {
1432             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1433                 self->type = OB_CLIENT_TYPE_DESKTOP;
1434             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1435                 self->type = OB_CLIENT_TYPE_DOCK;
1436             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1437                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1438             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1439                 self->type = OB_CLIENT_TYPE_MENU;
1440             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1441                 self->type = OB_CLIENT_TYPE_UTILITY;
1442             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1443                 self->type = OB_CLIENT_TYPE_SPLASH;
1444             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1445                 self->type = OB_CLIENT_TYPE_DIALOG;
1446             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1447                 self->type = OB_CLIENT_TYPE_NORMAL;
1448             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1449                 /* prevent this window from getting any decor or
1450                    functionality */
1451                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1452                                          OB_MWM_FLAG_DECORATIONS);
1453                 self->mwmhints.decorations = 0;
1454                 self->mwmhints.functions = 0;
1455             }
1456             if (self->type != (ObClientType) -1)
1457                 break; /* grab the first legit type */
1458         }
1459         g_free(val);
1460     }
1461
1462     if (XGetTransientForHint(ob_display, self->window, &t))
1463         self->transient = TRUE;
1464             
1465     if (self->type == (ObClientType) -1) {
1466         /*the window type hint was not set, which means we either classify
1467           ourself as a normal window or a dialog, depending on if we are a
1468           transient. */
1469         if (self->transient)
1470             self->type = OB_CLIENT_TYPE_DIALOG;
1471         else
1472             self->type = OB_CLIENT_TYPE_NORMAL;
1473     }
1474
1475     /* then, based on our type, we can update our transientness.. */
1476     if (self->type == OB_CLIENT_TYPE_DIALOG ||
1477         self->type == OB_CLIENT_TYPE_TOOLBAR ||
1478         self->type == OB_CLIENT_TYPE_MENU ||
1479         self->type == OB_CLIENT_TYPE_UTILITY)
1480     {
1481         self->transient = TRUE;
1482     }
1483 }
1484
1485 void client_update_protocols(ObClient *self)
1486 {
1487     guint32 *proto;
1488     guint num_return, i;
1489
1490     self->focus_notify = FALSE;
1491     self->delete_window = FALSE;
1492
1493     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1494         for (i = 0; i < num_return; ++i) {
1495             if (proto[i] == prop_atoms.wm_delete_window)
1496                 /* this means we can request the window to close */
1497                 self->delete_window = TRUE;
1498             else if (proto[i] == prop_atoms.wm_take_focus)
1499                 /* if this protocol is requested, then the window will be
1500                    notified whenever we want it to receive focus */
1501                 self->focus_notify = TRUE;
1502 #ifdef SYNC
1503             else if (proto[i] == prop_atoms.net_wm_sync_request) 
1504                 /* if this protocol is requested, then resizing the
1505                    window will be synchronized between the frame and the
1506                    client */
1507                 self->sync_request = TRUE;
1508 #endif
1509         }
1510         g_free(proto);
1511     }
1512 }
1513
1514 #ifdef SYNC
1515 void client_update_sync_request_counter(ObClient *self)
1516 {
1517     guint32 i;
1518
1519     if (PROP_GET32(self->window, net_wm_sync_request_counter, cardinal, &i)) {
1520         self->sync_counter = i;
1521     } else
1522         self->sync_counter = None;
1523 }
1524 #endif
1525
1526 void client_get_colormap(ObClient *self)
1527 {
1528     XWindowAttributes wa;
1529
1530     if (XGetWindowAttributes(ob_display, self->window, &wa))
1531         client_update_colormap(self, wa.colormap);
1532 }
1533
1534 void client_update_colormap(ObClient *self, Colormap colormap)
1535 {
1536     if (colormap == self->colormap) return;
1537
1538     ob_debug("Setting client %s colormap: 0x%x\n", self->title, colormap);
1539
1540     if (client_focused(self)) {
1541         screen_install_colormap(self, FALSE); /* uninstall old one */
1542         self->colormap = colormap;
1543         screen_install_colormap(self, FALSE); /* install new one */
1544     } else
1545         self->colormap = colormap;
1546 }
1547
1548 void client_update_normal_hints(ObClient *self)
1549 {
1550     XSizeHints size;
1551     glong ret;
1552
1553     /* defaults */
1554     self->min_ratio = 0.0f;
1555     self->max_ratio = 0.0f;
1556     SIZE_SET(self->size_inc, 1, 1);
1557     SIZE_SET(self->base_size, 0, 0);
1558     SIZE_SET(self->min_size, 0, 0);
1559     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1560
1561     /* get the hints from the window */
1562     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1563         /* normal windows can't request placement! har har
1564         if (!client_normal(self))
1565         */
1566         self->positioned = (size.flags & (PPosition|USPosition));
1567         self->sized = (size.flags & (PSize|USSize));
1568
1569         if (size.flags & PWinGravity)
1570             self->gravity = size.win_gravity;
1571
1572         if (size.flags & PAspect) {
1573             if (size.min_aspect.y)
1574                 self->min_ratio =
1575                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1576             if (size.max_aspect.y)
1577                 self->max_ratio =
1578                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1579         }
1580
1581         if (size.flags & PMinSize)
1582             SIZE_SET(self->min_size, size.min_width, size.min_height);
1583     
1584         if (size.flags & PMaxSize)
1585             SIZE_SET(self->max_size, size.max_width, size.max_height);
1586     
1587         if (size.flags & PBaseSize)
1588             SIZE_SET(self->base_size, size.base_width, size.base_height);
1589     
1590         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1591             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1592     }
1593 }
1594
1595 void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
1596 {
1597     /* start with everything (cept fullscreen) */
1598     self->decorations =
1599         (OB_FRAME_DECOR_TITLEBAR |
1600          OB_FRAME_DECOR_HANDLE |
1601          OB_FRAME_DECOR_GRIPS |
1602          OB_FRAME_DECOR_BORDER |
1603          OB_FRAME_DECOR_ICON |
1604          OB_FRAME_DECOR_ALLDESKTOPS |
1605          OB_FRAME_DECOR_ICONIFY |
1606          OB_FRAME_DECOR_MAXIMIZE |
1607          OB_FRAME_DECOR_SHADE |
1608          OB_FRAME_DECOR_CLOSE);
1609     self->functions =
1610         (OB_CLIENT_FUNC_RESIZE |
1611          OB_CLIENT_FUNC_MOVE |
1612          OB_CLIENT_FUNC_ICONIFY |
1613          OB_CLIENT_FUNC_MAXIMIZE |
1614          OB_CLIENT_FUNC_SHADE |
1615          OB_CLIENT_FUNC_CLOSE |
1616          OB_CLIENT_FUNC_BELOW |
1617          OB_CLIENT_FUNC_ABOVE |
1618          OB_CLIENT_FUNC_UNDECORATE);
1619
1620     if (!(self->min_size.width < self->max_size.width ||
1621           self->min_size.height < self->max_size.height))
1622         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1623
1624     switch (self->type) {
1625     case OB_CLIENT_TYPE_NORMAL:
1626         /* normal windows retain all of the possible decorations and
1627            functionality, and are the only windows that you can fullscreen */
1628         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1629         break;
1630
1631     case OB_CLIENT_TYPE_DIALOG:
1632     case OB_CLIENT_TYPE_UTILITY:
1633         /* these windows don't have anything added or removed by default */
1634         break;
1635
1636     case OB_CLIENT_TYPE_MENU:
1637     case OB_CLIENT_TYPE_TOOLBAR:
1638         /* these windows can't iconify or maximize */
1639         self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1640                                OB_FRAME_DECOR_MAXIMIZE);
1641         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1642                              OB_CLIENT_FUNC_MAXIMIZE);
1643         break;
1644
1645     case OB_CLIENT_TYPE_SPLASH:
1646         /* these don't get get any decorations, and the only thing you can
1647            do with them is move them */
1648         self->decorations = 0;
1649         self->functions = OB_CLIENT_FUNC_MOVE;
1650         break;
1651
1652     case OB_CLIENT_TYPE_DESKTOP:
1653         /* these windows are not manipulated by the window manager */
1654         self->decorations = 0;
1655         self->functions = 0;
1656         break;
1657
1658     case OB_CLIENT_TYPE_DOCK:
1659         /* these windows are not manipulated by the window manager, but they
1660            can set below layer which has a special meaning */
1661         self->decorations = 0;
1662         self->functions = OB_CLIENT_FUNC_BELOW;
1663         break;
1664     }
1665
1666     /* Mwm Hints are applied subtractively to what has already been chosen for
1667        decor and functionality */
1668     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1669         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1670             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1671                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1672             {
1673                 /* if the mwm hints request no handle or title, then all
1674                    decorations are disabled, but keep the border if that's
1675                    specified */
1676                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1677                     self->decorations = OB_FRAME_DECOR_BORDER;
1678                 else
1679                     self->decorations = 0;
1680             }
1681         }
1682     }
1683
1684     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1685         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1686             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1687                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1688             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1689                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1690             /* dont let mwm hints kill any buttons
1691                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1692                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1693                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1694                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1695             */
1696             /* dont let mwm hints kill the close button
1697                if (! (self->mwmhints.functions & MwmFunc_Close))
1698                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1699         }
1700     }
1701
1702     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1703         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1704     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1705         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1706     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1707         self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1708
1709     /* can't maximize without moving/resizing */
1710     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1711           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1712           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1713         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1714         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1715     }
1716
1717     if (self->max_horz && self->max_vert) {
1718         /* you can't resize fully maximized windows */
1719         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1720         /* kill the handle on fully maxed windows */
1721         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1722     }
1723
1724     /* If there are no decorations to remove, don't allow the user to try
1725        toggle the state */
1726     if (self->decorations == 0)
1727         self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1728
1729     /* finally, the user can have requested no decorations, which overrides
1730        everything (but doesnt give it a border if it doesnt have one) */
1731     if (self->undecorated) {
1732         if (config_theme_keepborder)
1733             self->decorations &= OB_FRAME_DECOR_BORDER;
1734         else
1735             self->decorations = 0;
1736     }
1737
1738     /* if we don't have a titlebar, then we cannot shade! */
1739     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1740         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1741
1742     /* now we need to check against rules for the client's current state */
1743     if (self->fullscreen) {
1744         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1745                             OB_CLIENT_FUNC_FULLSCREEN |
1746                             OB_CLIENT_FUNC_ICONIFY);
1747         self->decorations = 0;
1748     }
1749
1750     client_change_allowed_actions(self);
1751
1752     if (reconfig)
1753         client_reconfigure(self);
1754 }
1755
1756 static void client_change_allowed_actions(ObClient *self)
1757 {
1758     gulong actions[12];
1759     gint num = 0;
1760
1761     /* desktop windows are kept on all desktops */
1762     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1763         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1764
1765     if (self->functions & OB_CLIENT_FUNC_SHADE)
1766         actions[num++] = prop_atoms.net_wm_action_shade;
1767     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1768         actions[num++] = prop_atoms.net_wm_action_close;
1769     if (self->functions & OB_CLIENT_FUNC_MOVE)
1770         actions[num++] = prop_atoms.net_wm_action_move;
1771     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1772         actions[num++] = prop_atoms.net_wm_action_minimize;
1773     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1774         actions[num++] = prop_atoms.net_wm_action_resize;
1775     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1776         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1777     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1778         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1779         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1780     }
1781     if (self->functions & OB_CLIENT_FUNC_ABOVE)
1782         actions[num++] = prop_atoms.net_wm_action_above;
1783     if (self->functions & OB_CLIENT_FUNC_BELOW)
1784         actions[num++] = prop_atoms.net_wm_action_below;
1785     if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1786         actions[num++] = prop_atoms.ob_wm_action_undecorate;
1787
1788     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1789
1790     /* make sure the window isn't breaking any rules now */
1791
1792     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1793         if (self->frame) client_shade(self, FALSE);
1794         else self->shaded = FALSE;
1795     }
1796     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1797         if (self->frame) client_iconify(self, FALSE, TRUE, FALSE);
1798         else self->iconic = FALSE;
1799     }
1800     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1801         if (self->frame) client_fullscreen(self, FALSE);
1802         else self->fullscreen = FALSE;
1803     }
1804     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1805                                                          self->max_vert)) {
1806         if (self->frame) client_maximize(self, FALSE, 0);
1807         else self->max_vert = self->max_horz = FALSE;
1808     }
1809 }
1810
1811 void client_reconfigure(ObClient *self)
1812 {
1813     client_configure(self, self->area.x, self->area.y,
1814                      self->area.width, self->area.height,
1815                      FALSE, TRUE);
1816 }
1817
1818 void client_update_wmhints(ObClient *self)
1819 {
1820     XWMHints *hints;
1821
1822     /* assume a window takes input if it doesnt specify */
1823     self->can_focus = TRUE;
1824   
1825     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1826         gboolean ur;
1827
1828         if (hints->flags & InputHint)
1829             self->can_focus = hints->input;
1830
1831         /* only do this when first managing the window *AND* when we aren't
1832            starting up! */
1833         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1834             if (hints->flags & StateHint)
1835                 self->iconic = hints->initial_state == IconicState;
1836
1837         ur = self->urgent;
1838         self->urgent = (hints->flags & XUrgencyHint);
1839         if (self->urgent && !ur)
1840             client_hilite(self, TRUE);
1841         else if (!self->urgent && ur && self->demands_attention)
1842             client_hilite(self, FALSE);
1843
1844         if (!(hints->flags & WindowGroupHint))
1845             hints->window_group = None;
1846
1847         /* did the group state change? */
1848         if (hints->window_group !=
1849             (self->group ? self->group->leader : None))
1850         {
1851             ObGroup *oldgroup = self->group;
1852
1853             /* remove from the old group if there was one */
1854             if (self->group != NULL) {
1855                 group_remove(self->group, self);
1856                 self->group = NULL;
1857             }
1858
1859             /* add ourself to the group if we have one */
1860             if (hints->window_group != None) {
1861                 self->group = group_add(hints->window_group, self);
1862             }
1863
1864             /* Put ourselves into the new group's transient tree, and remove
1865                ourselves from the old group's */
1866             client_update_transient_tree(self, oldgroup, self->group,
1867                                          self->transient_for,
1868                                          self->transient_for);
1869
1870             /* Lastly, being in a group, or not, can change if the window is
1871                transient for anything.
1872
1873                The logic for this is:
1874                self->transient = TRUE always if the window wants to be
1875                transient for something, even if transient_for was NULL because
1876                it wasn't in a group before.
1877
1878                If transient_for was NULL and oldgroup was NULL we can assume
1879                that when we add the new group, it will become transient for
1880                something.
1881
1882                If transient_for was OB_TRAN_GROUP, then it must have already
1883                had a group. If it is getting a new group, the above call to
1884                client_update_transient_tree has already taken care of
1885                everything ! If it is losing all group status then it will
1886                no longer be transient for anything and that needs to be
1887                updated.
1888             */
1889             if (self->transient &&
1890                 ((self->transient_for == NULL && oldgroup == NULL) ||
1891                  (self->transient_for == OB_TRAN_GROUP && !self->group)))
1892                 client_update_transient_for(self);
1893         }
1894
1895         /* the WM_HINTS can contain an icon */
1896         if (hints->flags & IconPixmapHint)
1897             client_update_icons(self);
1898
1899         XFree(hints);
1900     }
1901 }
1902
1903 void client_update_title(ObClient *self)
1904 {
1905     gchar *data = NULL;
1906     gchar *visible = NULL;
1907
1908     g_free(self->title);
1909      
1910     /* try netwm */
1911     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1912         /* try old x stuff */
1913         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1914               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1915             if (self->transient) {
1916                 /*
1917                   GNOME alert windows are not given titles:
1918                   http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1919                 */
1920                 data = g_strdup("");
1921             } else
1922                 data = g_strdup("Unnamed Window");
1923         }
1924     }
1925
1926     if (self->client_machine) {
1927         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1928         g_free(data);
1929     } else
1930         visible = data;
1931
1932     PROP_SETS(self->window, net_wm_visible_name, visible);
1933     self->title = visible;
1934
1935     if (self->frame)
1936         frame_adjust_title(self->frame);
1937
1938     /* update the icon title */
1939     data = NULL;
1940     g_free(self->icon_title);
1941
1942     /* try netwm */
1943     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1944         /* try old x stuff */
1945         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1946               PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1947             data = g_strdup(self->title);
1948
1949     if (self->client_machine) {
1950         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1951         g_free(data);
1952     } else
1953         visible = data;
1954
1955     PROP_SETS(self->window, net_wm_visible_icon_name, visible);
1956     self->icon_title = visible;
1957 }
1958
1959 void client_update_strut(ObClient *self)
1960 {
1961     guint num;
1962     guint32 *data;
1963     gboolean got = FALSE;
1964     StrutPartial strut;
1965
1966     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1967                     &data, &num)) {
1968         if (num == 12) {
1969             got = TRUE;
1970             STRUT_PARTIAL_SET(strut,
1971                               data[0], data[2], data[1], data[3],
1972                               data[4], data[5], data[8], data[9],
1973                               data[6], data[7], data[10], data[11]);
1974         }
1975         g_free(data);
1976     }
1977
1978     if (!got &&
1979         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1980         if (num == 4) {
1981             const Rect *a;
1982
1983             got = TRUE;
1984
1985             /* use the screen's width/height */
1986             a = screen_physical_area();
1987
1988             STRUT_PARTIAL_SET(strut,
1989                               data[0], data[2], data[1], data[3],
1990                               a->y, a->y + a->height - 1,
1991                               a->x, a->x + a->width - 1,
1992                               a->y, a->y + a->height - 1,
1993                               a->x, a->x + a->width - 1);
1994         }
1995         g_free(data);
1996     }
1997
1998     if (!got)
1999         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2000                           0, 0, 0, 0, 0, 0, 0, 0);
2001
2002     if (!STRUT_EQUAL(strut, self->strut)) {
2003         self->strut = strut;
2004
2005         /* updating here is pointless while we're being mapped cuz we're not in
2006            the client list yet */
2007         if (self->frame)
2008             screen_update_areas();
2009     }
2010 }
2011
2012 void client_update_icons(ObClient *self)
2013 {
2014     guint num;
2015     guint32 *data;
2016     guint w, h, i, j;
2017
2018     for (i = 0; i < self->nicons; ++i)
2019         g_free(self->icons[i].data);
2020     if (self->nicons > 0)
2021         g_free(self->icons);
2022     self->nicons = 0;
2023
2024     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
2025         /* figure out how many valid icons are in here */
2026         i = 0;
2027         while (num - i > 2) {
2028             w = data[i++];
2029             h = data[i++];
2030             i += w * h;
2031             if (i > num || w*h == 0) break;
2032             ++self->nicons;
2033         }
2034
2035         self->icons = g_new(ObClientIcon, self->nicons);
2036     
2037         /* store the icons */
2038         i = 0;
2039         for (j = 0; j < self->nicons; ++j) {
2040             guint x, y, t;
2041
2042             w = self->icons[j].width = data[i++];
2043             h = self->icons[j].height = data[i++];
2044
2045             if (w*h == 0) continue;
2046
2047             self->icons[j].data = g_new(RrPixel32, w * h);
2048             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
2049                 if (x >= w) {
2050                     x = 0;
2051                     ++y;
2052                 }
2053                 self->icons[j].data[t] =
2054                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2055                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
2056                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
2057                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
2058             }
2059             g_assert(i <= num);
2060         }
2061
2062         g_free(data);
2063     } else {
2064         XWMHints *hints;
2065
2066         if ((hints = XGetWMHints(ob_display, self->window))) {
2067             if (hints->flags & IconPixmapHint) {
2068                 self->nicons++;
2069                 self->icons = g_new(ObClientIcon, self->nicons);
2070                 xerror_set_ignore(TRUE);
2071                 if (!RrPixmapToRGBA(ob_rr_inst,
2072                                     hints->icon_pixmap,
2073                                     (hints->flags & IconMaskHint ?
2074                                      hints->icon_mask : None),
2075                                     &self->icons[self->nicons-1].width,
2076                                     &self->icons[self->nicons-1].height,
2077                                     &self->icons[self->nicons-1].data)){
2078                     g_free(&self->icons[self->nicons-1]);
2079                     self->nicons--;
2080                 }
2081                 xerror_set_ignore(FALSE);
2082             }
2083             XFree(hints);
2084         }
2085     }
2086
2087     /* set the default icon onto the window
2088        in theory, this could be a race, but if a window doesn't set an icon
2089        or removes it entirely, it's not very likely it is going to set one
2090        right away afterwards */
2091     if (self->nicons == 0) {
2092         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2093         gulong *data;
2094
2095         data = g_new(gulong, 48*48+2);
2096         data[0] = data[1] =  48;
2097         for (i = 0; i < 48*48; ++i)
2098             data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2099                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2100                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2101                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2102         PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
2103         g_free(data);
2104     } else if (self->frame)
2105         /* don't draw the icon empty if we're just setting one now anyways,
2106            we'll get the property change any second */
2107         frame_adjust_icon(self->frame);
2108 }
2109
2110 void client_update_user_time(ObClient *self)
2111 {
2112     guint32 time;
2113     gboolean got = FALSE;
2114
2115     if (self->user_time_window)
2116         got = PROP_GET32(self->user_time_window,
2117                          net_wm_user_time, cardinal, &time);
2118     if (!got)
2119         got = PROP_GET32(self->window, net_wm_user_time, cardinal, &time);
2120
2121     if (got) {
2122         /* we set this every time, not just when it grows, because in practice
2123            sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
2124            backward we don't want all windows to stop focusing. we'll just
2125            assume noone is setting times older than the last one, cuz that
2126            would be pretty stupid anyways
2127         */
2128         self->user_time = time;
2129
2130         /*ob_debug("window %s user time %u\n", self->title, time);*/
2131     }
2132 }
2133
2134 void client_update_user_time_window(ObClient *self)
2135 {
2136     guint32 w;
2137
2138     if (!PROP_GET32(self->window, net_wm_user_time_window, window, &w))
2139         w = None;
2140
2141     if (w != self->user_time_window) {
2142         /* remove the old window */
2143         propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
2144         self->user_time_window = None;
2145
2146         if (self->group && self->group->leader == w) {
2147             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2148                           "_NET_WM_USER_TYPE_WINDOW to its group leader\n");
2149             /* do it anyways..? */
2150         }
2151         else if (w == self->window) {
2152             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2153                           "_NET_WM_USER_TIME_WINDOW to itself\n");
2154             w = None; /* don't do it */
2155         }
2156
2157         /* add the new window */
2158         propwin_add(w, OB_PROPWIN_USER_TIME, self);
2159         self->user_time_window = w;
2160
2161         /* and update from it */
2162         client_update_user_time(self);
2163     }
2164 }
2165
2166 void client_update_icon_geometry(ObClient *self)
2167 {
2168     guint num;
2169     guint32 *data;
2170
2171     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2172
2173     if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
2174         && num == 4)
2175     {
2176         /* don't let them set it with an area < 0 */
2177         RECT_SET(self->icon_geometry, data[0], data[1],
2178                  MAX(data[2],0), MAX(data[3],0));
2179     }
2180 }
2181
2182 static void client_get_session_ids(ObClient *self)
2183 {
2184     guint32 leader;
2185     gboolean got;
2186     gchar *s;
2187     gchar **ss;
2188
2189     if (!PROP_GET32(self->window, wm_client_leader, window, &leader))
2190         leader = None;
2191
2192     /* get the SM_CLIENT_ID */
2193     got = FALSE;
2194     if (leader)
2195         got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id);
2196     if (!got)
2197         PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id);
2198
2199     /* get the WM_CLASS (name and class). make them "" if they are not
2200        provided */
2201     got = FALSE;
2202     if (leader)
2203         got = PROP_GETSS(leader, wm_class, locale, &ss);
2204     if (!got)
2205         got = PROP_GETSS(self->window, wm_class, locale, &ss);
2206
2207     if (got) {
2208         if (ss[0]) {
2209             self->name = g_strdup(ss[0]);
2210             if (ss[1])
2211                 self->class = g_strdup(ss[1]);
2212         }
2213         g_strfreev(ss);
2214     }
2215
2216     if (self->name == NULL) self->name = g_strdup("");
2217     if (self->class == NULL) self->class = g_strdup("");
2218
2219     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2220     got = FALSE;
2221     if (leader)
2222         got = PROP_GETS(leader, wm_window_role, locale, &s);
2223     if (!got)
2224         got = PROP_GETS(self->window, wm_window_role, locale, &s);
2225
2226     if (got)
2227         self->role = s;
2228     else
2229         self->role = g_strdup("");
2230
2231     /* get the WM_COMMAND */
2232     got = FALSE;
2233
2234     if (leader)
2235         got = PROP_GETSS(leader, wm_command, locale, &ss);
2236     if (!got)
2237         got = PROP_GETSS(self->window, wm_command, locale, &ss);
2238
2239     if (got) {
2240         /* merge/mash them all together */
2241         gchar *merge = NULL;
2242         gint i;
2243
2244         for (i = 0; ss[i]; ++i) {
2245             gchar *tmp = merge;
2246             if (merge)
2247                 merge = g_strconcat(merge, ss[i], NULL);
2248             else
2249                 merge = g_strconcat(ss[i], NULL);
2250             g_free(tmp);
2251         }
2252         g_strfreev(ss);
2253
2254         self->wm_command = merge;
2255     }
2256
2257     /* get the WM_CLIENT_MACHINE */
2258     got = FALSE;
2259     if (leader)
2260         got = PROP_GETS(leader, wm_client_machine, locale, &s);
2261     if (!got)
2262         got = PROP_GETS(self->window, wm_client_machine, locale, &s);
2263
2264     if (got) {
2265         gchar localhost[128];
2266
2267         gethostname(localhost, 127);
2268         localhost[127] = '\0';
2269         if (strcmp(localhost, s) != 0)
2270             self->client_machine = s;
2271         else
2272             g_free(s);
2273     }
2274 }
2275
2276 static void client_change_wm_state(ObClient *self)
2277 {
2278     gulong state[2];
2279     glong old;
2280
2281     old = self->wmstate;
2282
2283     if (self->shaded || self->iconic ||
2284         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2285     {
2286         self->wmstate = IconicState;
2287     } else
2288         self->wmstate = NormalState;
2289
2290     if (old != self->wmstate) {
2291         PROP_MSG(self->window, kde_wm_change_state,
2292                  self->wmstate, 1, 0, 0);
2293
2294         state[0] = self->wmstate;
2295         state[1] = None;
2296         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2297     }
2298 }
2299
2300 static void client_change_state(ObClient *self)
2301 {
2302     gulong netstate[11];
2303     guint num;
2304
2305     num = 0;
2306     if (self->modal)
2307         netstate[num++] = prop_atoms.net_wm_state_modal;
2308     if (self->shaded)
2309         netstate[num++] = prop_atoms.net_wm_state_shaded;
2310     if (self->iconic)
2311         netstate[num++] = prop_atoms.net_wm_state_hidden;
2312     if (self->skip_taskbar)
2313         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2314     if (self->skip_pager)
2315         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2316     if (self->fullscreen)
2317         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2318     if (self->max_vert)
2319         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2320     if (self->max_horz)
2321         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2322     if (self->above)
2323         netstate[num++] = prop_atoms.net_wm_state_above;
2324     if (self->below)
2325         netstate[num++] = prop_atoms.net_wm_state_below;
2326     if (self->demands_attention)
2327         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2328     if (self->undecorated)
2329         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
2330     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2331
2332     if (self->frame)
2333         frame_adjust_state(self->frame);
2334 }
2335
2336 ObClient *client_search_focus_tree(ObClient *self)
2337 {
2338     GSList *it;
2339     ObClient *ret;
2340
2341     for (it = self->transients; it; it = g_slist_next(it)) {
2342         if (client_focused(it->data)) return it->data;
2343         if ((ret = client_search_focus_tree(it->data))) return ret;
2344     }
2345     return NULL;
2346 }
2347
2348 ObClient *client_search_focus_tree_full(ObClient *self)
2349 {
2350     if (self->transient_for) {
2351         if (self->transient_for != OB_TRAN_GROUP) {
2352             return client_search_focus_tree_full(self->transient_for);
2353         } else {
2354             GSList *it;
2355         
2356             for (it = self->group->members; it; it = g_slist_next(it)) {
2357                 if (it->data != self) {
2358                     ObClient *c = it->data;
2359
2360                     if (client_focused(c)) return c;
2361                     if ((c = client_search_focus_tree(it->data))) return c;
2362                 }
2363             }
2364         }
2365     }
2366
2367     /* this function checks the whole tree, the client_search_focus_tree
2368        does not, so we need to check this window */
2369     if (client_focused(self))
2370         return self;
2371     return client_search_focus_tree(self);
2372 }
2373
2374 gboolean client_has_parent(ObClient *self)
2375 {
2376     if (self->transient_for) {
2377         if (self->transient_for != OB_TRAN_GROUP) {
2378             if (client_normal(self->transient_for))
2379                 return TRUE;
2380         }
2381         else if (self->group) {
2382             GSList *it;
2383
2384             for (it = self->group->members; it; it = g_slist_next(it)) {
2385                 if (it->data != self && client_normal(it->data))
2386                     return TRUE;
2387             }
2388         }
2389     }
2390     return FALSE;
2391 }
2392
2393 static ObStackingLayer calc_layer(ObClient *self)
2394 {
2395     ObStackingLayer l;
2396
2397     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2398         l = OB_STACKING_LAYER_DESKTOP;
2399     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2400         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2401         else l = OB_STACKING_LAYER_ABOVE;
2402     }
2403     else if ((self->fullscreen ||
2404               /* No decorations and fills the monitor = oldskool fullscreen.
2405                  But not for maximized windows.
2406               */
2407               (self->decorations == 0 &&
2408                !(self->max_horz && self->max_vert) &&
2409                RECT_EQUAL(self->area,
2410                           *screen_physical_area_monitor
2411                           (client_monitor(self))))) &&
2412              (client_focused(self) || client_search_focus_tree(self)))
2413         l = OB_STACKING_LAYER_FULLSCREEN;
2414     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2415     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2416     else l = OB_STACKING_LAYER_NORMAL;
2417
2418     return l;
2419 }
2420
2421 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2422                                         ObStackingLayer min)
2423 {
2424     ObStackingLayer old, own;
2425     GSList *it;
2426
2427     old = self->layer;
2428     own = calc_layer(self);
2429     self->layer = MAX(own, min);
2430
2431     if (self->layer != old) {
2432         stacking_remove(CLIENT_AS_WINDOW(self));
2433         stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2434     }
2435
2436     for (it = self->transients; it; it = g_slist_next(it))
2437         client_calc_layer_recursive(it->data, orig,
2438                                     self->layer);
2439 }
2440
2441 void client_calc_layer(ObClient *self)
2442 {
2443     ObClient *orig;
2444     GSList *it;
2445
2446     orig = self;
2447
2448     /* transients take on the layer of their parents */
2449     it = client_search_all_top_parents(self);
2450
2451     for (; it; it = g_slist_next(it))
2452         client_calc_layer_recursive(it->data, orig, 0);
2453 }
2454
2455 gboolean client_should_show(ObClient *self)
2456 {
2457     if (self->iconic)
2458         return FALSE;
2459     if (client_normal(self) && screen_showing_desktop)
2460         return FALSE;
2461     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2462         return TRUE;
2463     
2464     return FALSE;
2465 }
2466
2467 gboolean client_show(ObClient *self)
2468 {
2469     gboolean show = FALSE;
2470
2471     if (client_should_show(self)) {
2472         frame_show(self->frame);
2473         show = TRUE;
2474
2475         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2476            it needs to be in IconicState. This includes when it is on another
2477            desktop!
2478         */
2479         client_change_wm_state(self);
2480     }
2481     return show;
2482 }
2483
2484 gboolean client_hide(ObClient *self)
2485 {
2486     gboolean hide = FALSE;
2487
2488     if (!client_should_show(self)) {
2489         if (self == focus_client) {
2490             /* if there is a grab going on, then we need to cancel it. if we
2491                move focus during the grab, applications will get
2492                NotifyWhileGrabbed events and ignore them !
2493
2494                actions should not rely on being able to move focus during an
2495                interactive grab.
2496             */
2497             event_cancel_all_key_grabs();
2498         }
2499
2500         frame_hide(self->frame);
2501         hide = TRUE;
2502
2503         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2504            it needs to be in IconicState. This includes when it is on another
2505            desktop!
2506         */
2507         client_change_wm_state(self);
2508     }
2509     return hide;
2510 }
2511
2512 void client_showhide(ObClient *self)
2513 {
2514     if (!client_show(self))
2515         client_hide(self);
2516 }
2517
2518 gboolean client_normal(ObClient *self) {
2519     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2520               self->type == OB_CLIENT_TYPE_DOCK ||
2521               self->type == OB_CLIENT_TYPE_SPLASH);
2522 }
2523
2524 gboolean client_helper(ObClient *self)
2525 {
2526     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2527             self->type == OB_CLIENT_TYPE_MENU ||
2528             self->type == OB_CLIENT_TYPE_TOOLBAR);
2529 }
2530
2531 gboolean client_mouse_focusable(ObClient *self)
2532 {
2533     return !(self->type == OB_CLIENT_TYPE_MENU ||
2534              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2535              self->type == OB_CLIENT_TYPE_SPLASH ||
2536              self->type == OB_CLIENT_TYPE_DOCK);
2537 }
2538
2539 gboolean client_enter_focusable(ObClient *self)
2540 {
2541     /* you can focus desktops but it shouldn't on enter */
2542     return (client_mouse_focusable(self) &&
2543             self->type != OB_CLIENT_TYPE_DESKTOP);
2544 }
2545
2546
2547 static void client_apply_startup_state(ObClient *self,
2548                                        gint x, gint y, gint w, gint h)
2549 {
2550     /* save the states that we are going to apply */
2551     gboolean iconic = self->iconic;
2552     gboolean fullscreen = self->fullscreen;
2553     gboolean undecorated = self->undecorated;
2554     gboolean shaded = self->shaded;
2555     gboolean demands_attention = self->demands_attention;
2556     gboolean max_horz = self->max_horz;
2557     gboolean max_vert = self->max_vert;
2558
2559     /* turn them all off in the client, so they won't affect the window
2560        being placed */
2561     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2562         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2563
2564     /* move the client to its placed position, or it it's already there,
2565        generate a ConfigureNotify telling the client where it is.
2566
2567        do this after adjusting the frame. otherwise it gets all weird and
2568        clients don't work right
2569
2570        do this before applying the states so they have the correct
2571        pre-max/pre-fullscreen values
2572     */
2573     client_configure(self, x, y, w, h, FALSE, TRUE);
2574     ob_debug("placed window 0x%x at %d, %d with size %d x %d\n",
2575              self->window, self->area.x, self->area.y,
2576              self->area.width, self->area.height);
2577
2578     /* apply the states. these are in a carefully crafted order.. */
2579
2580     if (iconic)
2581         client_iconify(self, TRUE, FALSE, TRUE);
2582     if (fullscreen)
2583         client_fullscreen(self, TRUE);
2584     if (undecorated)
2585         client_set_undecorated(self, TRUE);
2586     if (shaded)
2587         client_shade(self, TRUE);
2588     if (demands_attention)
2589         client_hilite(self, TRUE);
2590   
2591     if (max_vert && max_horz)
2592         client_maximize(self, TRUE, 0);
2593     else if (max_vert)
2594         client_maximize(self, TRUE, 2);
2595     else if (max_horz)
2596         client_maximize(self, TRUE, 1);
2597
2598     /* set the desktop hint, to make sure that it always exists */
2599     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2600
2601     /* nothing to do for the other states:
2602        skip_taskbar
2603        skip_pager
2604        modal
2605        above
2606        below
2607     */
2608 }
2609
2610 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2611 {
2612     /* these should be the current values. this is for when you're not moving,
2613        just resizing */
2614     g_assert(*x == self->area.x);
2615     g_assert(oldw == self->area.width);
2616
2617     /* horizontal */
2618     switch (self->gravity) {
2619     default:
2620     case NorthWestGravity:
2621     case WestGravity:
2622     case SouthWestGravity:
2623     case StaticGravity:
2624     case ForgetGravity:
2625         break;
2626     case NorthGravity:
2627     case CenterGravity:
2628     case SouthGravity:
2629         *x -= (neww - oldw) / 2;
2630         break;
2631     case NorthEastGravity:
2632     case EastGravity:
2633     case SouthEastGravity:
2634         *x -= neww - oldw;
2635         break;
2636     }
2637 }
2638
2639 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2640 {
2641     /* these should be the current values. this is for when you're not moving,
2642        just resizing */
2643     g_assert(*y == self->area.y);
2644     g_assert(oldh == self->area.height);
2645
2646     /* vertical */
2647     switch (self->gravity) {
2648     default:
2649     case NorthWestGravity:
2650     case NorthGravity:
2651     case NorthEastGravity:
2652     case StaticGravity:
2653     case ForgetGravity:
2654         break;
2655     case WestGravity:
2656     case CenterGravity:
2657     case EastGravity:
2658         *y -= (newh - oldh) / 2;
2659         break;
2660     case SouthWestGravity:
2661     case SouthGravity:
2662     case SouthEastGravity:
2663         *y -= newh - oldh;
2664         break;
2665     }
2666 }
2667
2668 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2669                           gint *logicalw, gint *logicalh,
2670                           gboolean user)
2671 {
2672     Rect desired_area = {*x, *y, *w, *h};
2673
2674     /* make the frame recalculate its dimentions n shit without changing
2675        anything visible for real, this way the constraints below can work with
2676        the updated frame dimensions. */
2677     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2678
2679     /* work within the prefered sizes given by the window */
2680     if (!(*w == self->area.width && *h == self->area.height)) {
2681         gint basew, baseh, minw, minh;
2682
2683         /* base size is substituted with min size if not specified */
2684         if (self->base_size.width || self->base_size.height) {
2685             basew = self->base_size.width;
2686             baseh = self->base_size.height;
2687         } else {
2688             basew = self->min_size.width;
2689             baseh = self->min_size.height;
2690         }
2691         /* min size is substituted with base size if not specified */
2692         if (self->min_size.width || self->min_size.height) {
2693             minw = self->min_size.width;
2694             minh = self->min_size.height;
2695         } else {
2696             minw = self->base_size.width;
2697             minh = self->base_size.height;
2698         }
2699
2700         /* if this is a user-requested resize, then check against min/max
2701            sizes */
2702
2703         /* smaller than min size or bigger than max size? */
2704         if (*w > self->max_size.width) *w = self->max_size.width;
2705         if (*w < minw) *w = minw;
2706         if (*h > self->max_size.height) *h = self->max_size.height;
2707         if (*h < minh) *h = minh;
2708
2709         *w -= basew;
2710         *h -= baseh;
2711
2712         /* keep to the increments */
2713         *w /= self->size_inc.width;
2714         *h /= self->size_inc.height;
2715
2716         /* you cannot resize to nothing */
2717         if (basew + *w < 1) *w = 1 - basew;
2718         if (baseh + *h < 1) *h = 1 - baseh;
2719   
2720         /* save the logical size */
2721         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2722         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2723
2724         *w *= self->size_inc.width;
2725         *h *= self->size_inc.height;
2726
2727         *w += basew;
2728         *h += baseh;
2729
2730         /* adjust the height to match the width for the aspect ratios.
2731            for this, min size is not substituted for base size ever. */
2732         *w -= self->base_size.width;
2733         *h -= self->base_size.height;
2734
2735         if (!self->fullscreen) {
2736             if (self->min_ratio)
2737                 if (*h * self->min_ratio > *w) {
2738                     *h = (gint)(*w / self->min_ratio);
2739
2740                     /* you cannot resize to nothing */
2741                     if (*h < 1) {
2742                         *h = 1;
2743                         *w = (gint)(*h * self->min_ratio);
2744                     }
2745                 }
2746             if (self->max_ratio)
2747                 if (*h * self->max_ratio < *w) {
2748                     *h = (gint)(*w / self->max_ratio);
2749
2750                     /* you cannot resize to nothing */
2751                     if (*h < 1) {
2752                         *h = 1;
2753                         *w = (gint)(*h * self->min_ratio);
2754                     }
2755                 }
2756         }
2757
2758         *w += self->base_size.width;
2759         *h += self->base_size.height;
2760     }
2761
2762     /* gets the frame's position */
2763     frame_client_gravity(self->frame, x, y, *w, *h);
2764
2765     /* these positions are frame positions, not client positions */
2766
2767     /* set the size and position if fullscreen */
2768     if (self->fullscreen) {
2769         Rect *a;
2770         guint i;
2771
2772         i = screen_find_monitor(&desired_area);
2773         a = screen_physical_area_monitor(i);
2774
2775         *x = a->x;
2776         *y = a->y;
2777         *w = a->width;
2778         *h = a->height;
2779
2780         user = FALSE; /* ignore if the client can't be moved/resized when it
2781                          is fullscreening */
2782     } else if (self->max_horz || self->max_vert) {
2783         Rect *a;
2784         guint i;
2785
2786         i = screen_find_monitor(&desired_area);
2787         a = screen_area_monitor(self->desktop, i);
2788
2789         /* set the size and position if maximized */
2790         if (self->max_horz) {
2791             *x = a->x;
2792             *w = a->width - self->frame->size.left - self->frame->size.right;
2793         }
2794         if (self->max_vert) {
2795             *y = a->y;
2796             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2797         }
2798
2799         user = FALSE; /* ignore if the client can't be moved/resized when it
2800                          is maximizing */
2801     }
2802
2803     /* gets the client's position */
2804     frame_frame_gravity(self->frame, x, y, *w, *h);
2805
2806     /* these override the above states! if you cant move you can't move! */
2807     if (user) {
2808         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2809             *x = self->area.x;
2810             *y = self->area.y;
2811         }
2812         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2813             *w = self->area.width;
2814             *h = self->area.height;
2815         }
2816     }
2817
2818     g_assert(*w > 0);
2819     g_assert(*h > 0);
2820 }
2821
2822
2823 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2824                       gboolean user, gboolean final)
2825 {
2826     gint oldw, oldh;
2827     gboolean send_resize_client;
2828     gboolean moved = FALSE, resized = FALSE;
2829     gboolean fmoved, fresized;
2830     guint fdecor = self->frame->decorations;
2831     gboolean fhorz = self->frame->max_horz;
2832     gboolean fvert = self->frame->max_vert;
2833     gint logicalw, logicalh;
2834
2835     /* find the new x, y, width, and height (and logical size) */
2836     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2837
2838     /* set the logical size if things changed */
2839     if (!(w == self->area.width && h == self->area.height))
2840         SIZE_SET(self->logical_size, logicalw, logicalh);
2841
2842     /* figure out if we moved or resized or what */
2843     moved = x != self->area.x || y != self->area.y;
2844     resized = w != self->area.width || h != self->area.height;
2845
2846     oldw = self->area.width;
2847     oldh = self->area.height;
2848     RECT_SET(self->area, x, y, w, h);
2849
2850     /* for app-requested resizes, always resize if 'resized' is true.
2851        for user-requested ones, only resize if final is true, or when
2852        resizing in redraw mode */
2853     send_resize_client = ((!user && resized) ||
2854                           (user && (final ||
2855                                     (resized && config_resize_redraw))));
2856
2857     /* if the client is enlarging, then resize the client before the frame */
2858     if (send_resize_client && (w > oldw || h > oldh)) {
2859         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2860         frame_adjust_client_area(self->frame);
2861     }
2862
2863     /* find the frame's dimensions and move/resize it */
2864     fmoved = moved;
2865     fresized = resized;
2866
2867     /* if decorations changed, then readjust everything for the frame */
2868     if (self->decorations != fdecor ||
2869         self->max_horz != fhorz || self->max_vert != fvert)
2870     {
2871         fmoved = fresized = TRUE;
2872     }
2873
2874     /* adjust the frame */
2875     if (fmoved || fresized)
2876         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
2877
2878     if ((!user || (user && final)) && !resized)
2879     {
2880         XEvent event;
2881
2882         /* we have reset the client to 0 border width, so don't include
2883            it in these coords */
2884         POINT_SET(self->root_pos,
2885                   self->frame->area.x + self->frame->size.left -
2886                   self->border_width,
2887                   self->frame->area.y + self->frame->size.top -
2888                   self->border_width);
2889
2890         event.type = ConfigureNotify;
2891         event.xconfigure.display = ob_display;
2892         event.xconfigure.event = self->window;
2893         event.xconfigure.window = self->window;
2894
2895         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d\n",
2896                  self->title, self->root_pos.x, self->root_pos.y, w, h);
2897
2898         /* root window real coords */
2899         event.xconfigure.x = self->root_pos.x;
2900         event.xconfigure.y = self->root_pos.y;
2901         event.xconfigure.width = w;
2902         event.xconfigure.height = h;
2903         event.xconfigure.border_width = self->border_width;
2904         event.xconfigure.above = None;
2905         event.xconfigure.override_redirect = FALSE;
2906         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2907                    FALSE, StructureNotifyMask, &event);
2908     }
2909
2910     /* if the client is shrinking, then resize the frame before the client */
2911     if (send_resize_client && (w <= oldw && h <= oldh)) {
2912         frame_adjust_client_area(self->frame);
2913         XResizeWindow(ob_display, self->window, w, h);
2914     }
2915
2916     XFlush(ob_display);
2917 }
2918
2919 void client_fullscreen(ObClient *self, gboolean fs)
2920 {
2921     gint x, y, w, h;
2922
2923     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2924         self->fullscreen == fs) return;                   /* already done */
2925
2926     self->fullscreen = fs;
2927     client_change_state(self); /* change the state hints on the client */
2928
2929     if (fs) {
2930         self->pre_fullscreen_area = self->area;
2931         /* if the window is maximized, its area isn't all that meaningful.
2932            save it's premax area instead. */
2933         if (self->max_horz) {
2934             self->pre_fullscreen_area.x = self->pre_max_area.x;
2935             self->pre_fullscreen_area.width = self->pre_max_area.width;
2936         }
2937         if (self->max_vert) {
2938             self->pre_fullscreen_area.y = self->pre_max_area.y;
2939             self->pre_fullscreen_area.height = self->pre_max_area.height;
2940         }
2941
2942         /* these will help configure_full figure out where to fullscreen
2943            the window */
2944         x = self->area.x;
2945         y = self->area.y;
2946         w = self->area.width;
2947         h = self->area.height;
2948     } else {
2949         g_assert(self->pre_fullscreen_area.width > 0 &&
2950                  self->pre_fullscreen_area.height > 0);
2951
2952         x = self->pre_fullscreen_area.x;
2953         y = self->pre_fullscreen_area.y;
2954         w = self->pre_fullscreen_area.width;
2955         h = self->pre_fullscreen_area.height;
2956         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2957     }
2958
2959     client_setup_decor_and_functions(self, FALSE);
2960     client_move_resize(self, x, y, w, h);
2961
2962     /* and adjust our layer/stacking. do this after resizing the window,
2963        and applying decorations, because windows which fill the screen are
2964        considered "fullscreen" and it affects their layer */
2965     client_calc_layer(self);
2966
2967     if (fs) {
2968         /* try focus us when we go into fullscreen mode */
2969         client_focus(self);
2970     }
2971 }
2972
2973 static void client_iconify_recursive(ObClient *self,
2974                                      gboolean iconic, gboolean curdesk,
2975                                      gboolean hide_animation)
2976 {
2977     GSList *it;
2978     gboolean changed = FALSE;
2979
2980
2981     if (self->iconic != iconic) {
2982         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2983                  self->window);
2984
2985         if (iconic) {
2986             /* don't let non-normal windows iconify along with their parents
2987                or whatever */
2988             if (client_normal(self)) {
2989                 self->iconic = iconic;
2990
2991                 /* update the focus lists.. iconic windows go to the bottom of
2992                    the list */
2993                 focus_order_to_bottom(self);
2994
2995                 changed = TRUE;
2996             }
2997         } else {
2998             self->iconic = iconic;
2999
3000             if (curdesk && self->desktop != screen_desktop &&
3001                 self->desktop != DESKTOP_ALL)
3002                 client_set_desktop(self, screen_desktop, FALSE);
3003
3004             /* this puts it after the current focused window */
3005             focus_order_remove(self);
3006             focus_order_add_new(self);
3007
3008             changed = TRUE;
3009         }
3010     }
3011
3012     if (changed) {
3013         client_change_state(self);
3014         if (config_animate_iconify && !hide_animation)
3015             frame_begin_iconify_animation(self->frame, iconic);
3016         /* do this after starting the animation so it doesn't flash */
3017         client_showhide(self);
3018     }
3019
3020     /* iconify all direct transients, and deiconify all transients
3021        (non-direct too) */
3022     for (it = self->transients; it; it = g_slist_next(it))
3023         if (it->data != self)
3024             if (client_is_direct_child(self, it->data) || !iconic)
3025                 client_iconify_recursive(it->data, iconic, curdesk,
3026                                          hide_animation);
3027 }
3028
3029 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3030                     gboolean hide_animation)
3031 {
3032     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3033         /* move up the transient chain as far as possible first */
3034         self = client_search_top_normal_parent(self);
3035         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3036     }
3037 }
3038
3039 void client_maximize(ObClient *self, gboolean max, gint dir)
3040 {
3041     gint x, y, w, h;
3042      
3043     g_assert(dir == 0 || dir == 1 || dir == 2);
3044     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3045
3046     /* check if already done */
3047     if (max) {
3048         if (dir == 0 && self->max_horz && self->max_vert) return;
3049         if (dir == 1 && self->max_horz) return;
3050         if (dir == 2 && self->max_vert) return;
3051     } else {
3052         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3053         if (dir == 1 && !self->max_horz) return;
3054         if (dir == 2 && !self->max_vert) return;
3055     }
3056
3057     /* these will help configure_full figure out which screen to fill with
3058        the window */
3059     x = self->area.x;
3060     y = self->area.y;
3061     w = self->area.width;
3062     h = self->area.height;
3063
3064     if (max) {
3065         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3066             RECT_SET(self->pre_max_area,
3067                      self->area.x, self->pre_max_area.y,
3068                      self->area.width, self->pre_max_area.height);
3069         }
3070         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3071             RECT_SET(self->pre_max_area,
3072                      self->pre_max_area.x, self->area.y,
3073                      self->pre_max_area.width, self->area.height);
3074         }
3075     } else {
3076         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3077             g_assert(self->pre_max_area.width > 0);
3078
3079             x = self->pre_max_area.x;
3080             w = self->pre_max_area.width;
3081
3082             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3083                      0, self->pre_max_area.height);
3084         }
3085         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3086             g_assert(self->pre_max_area.height > 0);
3087
3088             y = self->pre_max_area.y;
3089             h = self->pre_max_area.height;
3090
3091             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3092                      self->pre_max_area.width, 0);
3093         }
3094     }
3095
3096     if (dir == 0 || dir == 1) /* horz */
3097         self->max_horz = max;
3098     if (dir == 0 || dir == 2) /* vert */
3099         self->max_vert = max;
3100
3101     client_change_state(self); /* change the state hints on the client */
3102
3103     client_setup_decor_and_functions(self, FALSE);
3104     client_move_resize(self, x, y, w, h);
3105 }
3106
3107 void client_shade(ObClient *self, gboolean shade)
3108 {
3109     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3110          shade) ||                         /* can't shade */
3111         self->shaded == shade) return;     /* already done */
3112
3113     self->shaded = shade;
3114     client_change_state(self);
3115     client_change_wm_state(self); /* the window is being hidden/shown */
3116     /* resize the frame to just the titlebar */
3117     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3118 }
3119
3120 void client_close(ObClient *self)
3121 {
3122     XEvent ce;
3123
3124     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3125
3126     /* in the case that the client provides no means to requesting that it
3127        close, we just kill it */
3128     if (!self->delete_window)
3129         client_kill(self);
3130     
3131     /*
3132       XXX: itd be cool to do timeouts and shit here for killing the client's
3133       process off
3134       like... if the window is around after 5 seconds, then the close button
3135       turns a nice red, and if this function is called again, the client is
3136       explicitly killed.
3137     */
3138
3139     ce.xclient.type = ClientMessage;
3140     ce.xclient.message_type =  prop_atoms.wm_protocols;
3141     ce.xclient.display = ob_display;
3142     ce.xclient.window = self->window;
3143     ce.xclient.format = 32;
3144     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3145     ce.xclient.data.l[1] = event_curtime;
3146     ce.xclient.data.l[2] = 0l;
3147     ce.xclient.data.l[3] = 0l;
3148     ce.xclient.data.l[4] = 0l;
3149     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3150 }
3151
3152 void client_kill(ObClient *self)
3153 {
3154     XKillClient(ob_display, self->window);
3155 }
3156
3157 void client_hilite(ObClient *self, gboolean hilite)
3158 {
3159     if (self->demands_attention == hilite)
3160         return; /* no change */
3161
3162     /* don't allow focused windows to hilite */
3163     self->demands_attention = hilite && !client_focused(self);
3164     if (self->frame != NULL) { /* if we're mapping, just set the state */
3165         if (self->demands_attention)
3166             frame_flash_start(self->frame);
3167         else
3168             frame_flash_stop(self->frame);
3169         client_change_state(self);
3170     }
3171 }
3172
3173 void client_set_desktop_recursive(ObClient *self,
3174                                   guint target,
3175                                   gboolean donthide)
3176 {
3177     guint old;
3178     GSList *it;
3179
3180     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3181
3182         ob_debug("Setting desktop %u\n", target+1);
3183
3184         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3185
3186         old = self->desktop;
3187         self->desktop = target;
3188         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3189         /* the frame can display the current desktop state */
3190         frame_adjust_state(self->frame);
3191         /* 'move' the window to the new desktop */
3192         if (!donthide)
3193             client_showhide(self);
3194         /* raise if it was not already on the desktop */
3195         if (old != DESKTOP_ALL)
3196             stacking_raise(CLIENT_AS_WINDOW(self));
3197         if (STRUT_EXISTS(self->strut))
3198             screen_update_areas();
3199     }
3200
3201     /* move all transients */
3202     for (it = self->transients; it; it = g_slist_next(it))
3203         if (it->data != self)
3204             if (client_is_direct_child(self, it->data))
3205                 client_set_desktop_recursive(it->data, target, donthide);
3206 }
3207
3208 void client_set_desktop(ObClient *self, guint target,
3209                         gboolean donthide)
3210 {
3211     self = client_search_top_normal_parent(self);
3212     client_set_desktop_recursive(self, target, donthide);
3213 }
3214
3215 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3216 {
3217     while (child != parent &&
3218            child->transient_for && child->transient_for != OB_TRAN_GROUP)
3219         child = child->transient_for;
3220     return child == parent;
3221 }
3222
3223 ObClient *client_search_modal_child(ObClient *self)
3224 {
3225     GSList *it;
3226     ObClient *ret;
3227   
3228     for (it = self->transients; it; it = g_slist_next(it)) {
3229         ObClient *c = it->data;
3230         if ((ret = client_search_modal_child(c))) return ret;
3231         if (c->modal) return c;
3232     }
3233     return NULL;
3234 }
3235
3236 gboolean client_validate(ObClient *self)
3237 {
3238     XEvent e; 
3239
3240     XSync(ob_display, FALSE); /* get all events on the server */
3241
3242     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3243         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3244         XPutBackEvent(ob_display, &e);
3245         return FALSE;
3246     }
3247
3248     return TRUE;
3249 }
3250
3251 void client_set_wm_state(ObClient *self, glong state)
3252 {
3253     if (state == self->wmstate) return; /* no change */
3254   
3255     switch (state) {
3256     case IconicState:
3257         client_iconify(self, TRUE, TRUE, FALSE);
3258         break;
3259     case NormalState:
3260         client_iconify(self, FALSE, TRUE, FALSE);
3261         break;
3262     }
3263 }
3264
3265 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3266 {
3267     gboolean shaded = self->shaded;
3268     gboolean fullscreen = self->fullscreen;
3269     gboolean undecorated = self->undecorated;
3270     gboolean max_horz = self->max_horz;
3271     gboolean max_vert = self->max_vert;
3272     gboolean modal = self->modal;
3273     gboolean iconic = self->iconic;
3274     gboolean demands_attention = self->demands_attention;
3275     gboolean above = self->above;
3276     gboolean below = self->below;
3277     gint i;
3278
3279     if (!(action == prop_atoms.net_wm_state_add ||
3280           action == prop_atoms.net_wm_state_remove ||
3281           action == prop_atoms.net_wm_state_toggle))
3282         /* an invalid action was passed to the client message, ignore it */
3283         return; 
3284
3285     for (i = 0; i < 2; ++i) {
3286         Atom state = i == 0 ? data1 : data2;
3287     
3288         if (!state) continue;
3289
3290         /* if toggling, then pick whether we're adding or removing */
3291         if (action == prop_atoms.net_wm_state_toggle) {
3292             if (state == prop_atoms.net_wm_state_modal)
3293                 action = modal ? prop_atoms.net_wm_state_remove :
3294                     prop_atoms.net_wm_state_add;
3295             else if (state == prop_atoms.net_wm_state_maximized_vert)
3296                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3297                     prop_atoms.net_wm_state_add;
3298             else if (state == prop_atoms.net_wm_state_maximized_horz)
3299                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3300                     prop_atoms.net_wm_state_add;
3301             else if (state == prop_atoms.net_wm_state_shaded)
3302                 action = shaded ? prop_atoms.net_wm_state_remove :
3303                     prop_atoms.net_wm_state_add;
3304             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3305                 action = self->skip_taskbar ?
3306                     prop_atoms.net_wm_state_remove :
3307                     prop_atoms.net_wm_state_add;
3308             else if (state == prop_atoms.net_wm_state_skip_pager)
3309                 action = self->skip_pager ?
3310                     prop_atoms.net_wm_state_remove :
3311                     prop_atoms.net_wm_state_add;
3312             else if (state == prop_atoms.net_wm_state_hidden)
3313                 action = self->iconic ?
3314                     prop_atoms.net_wm_state_remove :
3315                     prop_atoms.net_wm_state_add;
3316             else if (state == prop_atoms.net_wm_state_fullscreen)
3317                 action = fullscreen ?
3318                     prop_atoms.net_wm_state_remove :
3319                     prop_atoms.net_wm_state_add;
3320             else if (state == prop_atoms.net_wm_state_above)
3321                 action = self->above ? prop_atoms.net_wm_state_remove :
3322                     prop_atoms.net_wm_state_add;
3323             else if (state == prop_atoms.net_wm_state_below)
3324                 action = self->below ? prop_atoms.net_wm_state_remove :
3325                     prop_atoms.net_wm_state_add;
3326             else if (state == prop_atoms.net_wm_state_demands_attention)
3327                 action = self->demands_attention ?
3328                     prop_atoms.net_wm_state_remove :
3329                     prop_atoms.net_wm_state_add;
3330             else if (state == prop_atoms.ob_wm_state_undecorated)
3331                 action = undecorated ? prop_atoms.net_wm_state_remove :
3332                     prop_atoms.net_wm_state_add;
3333         }
3334     
3335         if (action == prop_atoms.net_wm_state_add) {
3336             if (state == prop_atoms.net_wm_state_modal) {
3337                 modal = TRUE;
3338             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3339                 max_vert = TRUE;
3340             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3341                 max_horz = TRUE;
3342             } else if (state == prop_atoms.net_wm_state_shaded) {
3343                 shaded = TRUE;
3344             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3345                 self->skip_taskbar = TRUE;
3346             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3347                 self->skip_pager = TRUE;
3348             } else if (state == prop_atoms.net_wm_state_hidden) {
3349                 iconic = TRUE;
3350             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3351                 fullscreen = TRUE;
3352             } else if (state == prop_atoms.net_wm_state_above) {
3353                 above = TRUE;
3354                 below = FALSE;
3355             } else if (state == prop_atoms.net_wm_state_below) {
3356                 above = FALSE;
3357                 below = TRUE;
3358             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3359                 demands_attention = TRUE;
3360             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3361                 undecorated = TRUE;
3362             }
3363
3364         } else { /* action == prop_atoms.net_wm_state_remove */
3365             if (state == prop_atoms.net_wm_state_modal) {
3366                 modal = FALSE;
3367             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3368                 max_vert = FALSE;
3369             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3370                 max_horz = FALSE;
3371             } else if (state == prop_atoms.net_wm_state_shaded) {
3372                 shaded = FALSE;
3373             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3374                 self->skip_taskbar = FALSE;
3375             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3376                 self->skip_pager = FALSE;
3377             } else if (state == prop_atoms.net_wm_state_hidden) {
3378                 iconic = FALSE;
3379             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3380                 fullscreen = FALSE;
3381             } else if (state == prop_atoms.net_wm_state_above) {
3382                 above = FALSE;
3383             } else if (state == prop_atoms.net_wm_state_below) {
3384                 below = FALSE;
3385             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3386                 demands_attention = FALSE;
3387             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3388                 undecorated = FALSE;
3389             }
3390         }
3391     }
3392
3393     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3394         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3395             /* toggling both */
3396             if (max_horz == max_vert) { /* both going the same way */
3397                 client_maximize(self, max_horz, 0);
3398             } else {
3399                 client_maximize(self, max_horz, 1);
3400                 client_maximize(self, max_vert, 2);
3401             }
3402         } else {
3403             /* toggling one */
3404             if (max_horz != self->max_horz)
3405                 client_maximize(self, max_horz, 1);
3406             else
3407                 client_maximize(self, max_vert, 2);
3408         }
3409     }
3410     /* change fullscreen state before shading, as it will affect if the window
3411        can shade or not */
3412     if (fullscreen != self->fullscreen)
3413         client_fullscreen(self, fullscreen);
3414     if (shaded != self->shaded)
3415         client_shade(self, shaded);
3416     if (undecorated != self->undecorated)
3417         client_set_undecorated(self, undecorated);
3418     if (above != self->above || below != self->below) {
3419         self->above = above;
3420         self->below = below;
3421         client_calc_layer(self);
3422     }
3423
3424     if (modal != self->modal) {
3425         self->modal = modal;
3426         /* when a window changes modality, then its stacking order with its
3427            transients needs to change */
3428         stacking_raise(CLIENT_AS_WINDOW(self));
3429
3430         /* it also may get focused. if something is focused that shouldn't
3431            be focused anymore, then move the focus */
3432         if (focus_client && client_focus_target(focus_client) != focus_client)
3433             client_focus(focus_client);
3434     }
3435
3436     if (iconic != self->iconic)
3437         client_iconify(self, iconic, FALSE, FALSE);
3438
3439     if (demands_attention != self->demands_attention)
3440         client_hilite(self, demands_attention);
3441
3442     client_change_state(self); /* change the hint to reflect these changes */
3443 }
3444
3445 ObClient *client_focus_target(ObClient *self)
3446 {
3447     ObClient *child = NULL;
3448
3449     child = client_search_modal_child(self);
3450     if (child) return child;
3451     return self;
3452 }
3453
3454 gboolean client_can_focus(ObClient *self)
3455 {
3456     /* choose the correct target */
3457     self = client_focus_target(self);
3458
3459     if (!self->frame->visible)
3460         return FALSE;
3461
3462     if (!(self->can_focus || self->focus_notify))
3463         return FALSE;
3464
3465     return TRUE;
3466 }
3467
3468 gboolean client_focus(ObClient *self)
3469 {
3470     /* we might not focus this window, so if we have modal children which would
3471        be focused instead, bring them to this desktop */
3472     client_bring_modal_windows(self);
3473
3474     /* choose the correct target */
3475     self = client_focus_target(self);
3476
3477     if (!client_can_focus(self)) {
3478         if (!self->frame->visible) {
3479             /* update the focus lists */
3480             focus_order_to_top(self);
3481         }
3482         ob_debug_type(OB_DEBUG_FOCUS,
3483                       "Client %s can't be focused\n", self->title);
3484         return FALSE;
3485     }
3486
3487     ob_debug_type(OB_DEBUG_FOCUS,
3488                   "Focusing client \"%s\" (0x%x) at time %u\n",
3489                   self->title, self->window, event_curtime);
3490
3491     /* if there is a grab going on, then we need to cancel it. if we move
3492        focus during the grab, applications will get NotifyWhileGrabbed events
3493        and ignore them !
3494
3495        actions should not rely on being able to move focus during an
3496        interactive grab.
3497     */
3498     event_cancel_all_key_grabs();
3499
3500     xerror_set_ignore(TRUE);
3501     xerror_occured = FALSE;
3502
3503     if (self->can_focus) {
3504         /* This can cause a BadMatch error with CurrentTime, or if an app
3505            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3506         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3507                        event_curtime);
3508     }
3509
3510     if (self->focus_notify) {
3511         XEvent ce;
3512         ce.xclient.type = ClientMessage;
3513         ce.xclient.message_type = prop_atoms.wm_protocols;
3514         ce.xclient.display = ob_display;
3515         ce.xclient.window = self->window;
3516         ce.xclient.format = 32;
3517         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3518         ce.xclient.data.l[1] = event_curtime;
3519         ce.xclient.data.l[2] = 0l;
3520         ce.xclient.data.l[3] = 0l;
3521         ce.xclient.data.l[4] = 0l;
3522         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3523     }
3524
3525     xerror_set_ignore(FALSE);
3526
3527     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d\n", xerror_occured);
3528     return !xerror_occured;
3529 }
3530
3531 /*! Present the client to the user.
3532   @param raise If the client should be raised or not. You should only set
3533                raise to false if you don't care if the window is completely
3534                hidden.
3535 */
3536 static void client_present(ObClient *self, gboolean here, gboolean raise)
3537 {
3538     /* if using focus_delay, stop the timer now so that focus doesn't
3539        go moving on us */
3540     event_halt_focus_delay();
3541
3542     if (client_normal(self) && screen_showing_desktop)
3543         screen_show_desktop(FALSE, self);
3544     if (self->iconic)
3545         client_iconify(self, FALSE, here, FALSE);
3546     if (self->desktop != DESKTOP_ALL &&
3547         self->desktop != screen_desktop)
3548     {
3549         if (here)
3550             client_set_desktop(self, screen_desktop, FALSE);
3551         else
3552             screen_set_desktop(self->desktop, FALSE);
3553     } else if (!self->frame->visible)
3554         /* if its not visible for other reasons, then don't mess
3555            with it */
3556         return;
3557     if (self->shaded)
3558         client_shade(self, FALSE);
3559     if (raise)
3560         stacking_raise(CLIENT_AS_WINDOW(self));
3561
3562     client_focus(self);
3563 }
3564
3565 void client_activate(ObClient *self, gboolean here, gboolean user)
3566 {
3567     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3568     gboolean allow = FALSE;
3569
3570     /* if the request came from the user, or if nothing is focused, then grant
3571        the request.
3572        if the currently focused app doesn't set a user_time, then it can't
3573        benefit from any focus stealing prevention.
3574     */
3575     if (user || !focus_client || !last_time)
3576         allow = TRUE;
3577     /* otherwise, if they didn't give a time stamp or if it is too old, they
3578        don't get focus */
3579     else
3580         allow = event_curtime && event_time_after(event_curtime, last_time);
3581
3582     ob_debug_type(OB_DEBUG_FOCUS,
3583                   "Want to activate window 0x%x with time %u (last time %u), "
3584                   "source=%s allowing? %d\n",
3585                   self->window, event_curtime, last_time,
3586                   (user ? "user" : "application"), allow);
3587
3588     if (allow)
3589         client_present(self, here, TRUE);
3590     else
3591         /* don't focus it but tell the user it wants attention */
3592         client_hilite(self, TRUE);
3593 }
3594
3595 static void client_bring_windows_recursive(ObClient *self,
3596                                            guint desktop,
3597                                            gboolean helpers,
3598                                            gboolean modals)
3599 {
3600     GSList *it;
3601
3602     for (it = self->transients; it; it = g_slist_next(it))
3603         client_bring_windows_recursive(it->data, desktop, helpers, modals);
3604
3605     if (((helpers && client_helper(self)) ||
3606          (modals && self->modal))&&
3607         self->desktop != desktop && self->desktop != DESKTOP_ALL)
3608     {
3609         client_set_desktop(self, desktop, FALSE);
3610     }
3611 }
3612
3613 void client_bring_helper_windows(ObClient *self)
3614 {
3615     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE);
3616 }
3617
3618 void client_bring_modal_windows(ObClient *self)
3619 {
3620     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE);
3621 }
3622
3623 gboolean client_focused(ObClient *self)
3624 {
3625     return self == focus_client;
3626 }
3627
3628 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3629 {
3630     guint i;
3631     gulong min_diff, min_i;
3632
3633     if (!self->nicons) {
3634         ObClientIcon *parent = NULL;
3635
3636         if (self->transient_for) {
3637             if (self->transient_for != OB_TRAN_GROUP)
3638                 parent = client_icon_recursive(self->transient_for, w, h);
3639             else {
3640                 GSList *it;
3641                 for (it = self->group->members; it; it = g_slist_next(it)) {
3642                     ObClient *c = it->data;
3643                     if (c != self && !c->transient_for) {
3644                         if ((parent = client_icon_recursive(c, w, h)))
3645                             break;
3646                     }
3647                 }
3648             }
3649         }
3650         
3651         return parent;
3652     }
3653
3654     /* some kind of crappy approximation to find the icon closest in size to
3655        what we requested, but icons are generally all the same ratio as
3656        eachother so it's good enough. */
3657
3658     min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3659     min_i = 0;
3660
3661     for (i = 1; i < self->nicons; ++i) {
3662         gulong diff;
3663
3664         diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3665         if (diff < min_diff) {
3666             min_diff = diff;
3667             min_i = i;
3668         }
3669     }
3670     return &self->icons[min_i];
3671 }
3672
3673 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3674 {
3675     ObClientIcon *ret;
3676     static ObClientIcon deficon;
3677
3678     if (!(ret = client_icon_recursive(self, w, h))) {
3679         deficon.width = deficon.height = 48;
3680         deficon.data = ob_rr_theme->def_win_icon;
3681         ret = &deficon;
3682     }
3683     return ret;
3684 }
3685
3686 void client_set_layer(ObClient *self, gint layer)
3687 {
3688     if (layer < 0) {
3689         self->below = TRUE;
3690         self->above = FALSE;
3691     } else if (layer == 0) {
3692         self->below = self->above = FALSE;
3693     } else {
3694         self->below = FALSE;
3695         self->above = TRUE;
3696     }
3697     client_calc_layer(self);
3698     client_change_state(self); /* reflect this in the state hints */
3699 }
3700
3701 void client_set_undecorated(ObClient *self, gboolean undecorated)
3702 {
3703     if (self->undecorated != undecorated &&
3704         /* don't let it undecorate if the function is missing, but let 
3705            it redecorate */
3706         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3707     {
3708         self->undecorated = undecorated;
3709         client_setup_decor_and_functions(self, TRUE);
3710         client_change_state(self); /* reflect this in the state hints */
3711     }
3712 }
3713
3714 guint client_monitor(ObClient *self)
3715 {
3716     return screen_find_monitor(&self->frame->area);
3717 }
3718
3719 ObClient *client_search_top_normal_parent(ObClient *self)
3720 {
3721     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3722            client_normal(self->transient_for))
3723         self = self->transient_for;
3724     return self;
3725 }
3726
3727 static GSList *client_search_all_top_parents_internal(ObClient *self,
3728                                                       gboolean bylayer,
3729                                                       ObStackingLayer layer)
3730 {
3731     GSList *ret = NULL;
3732     
3733     /* move up the direct transient chain as far as possible */
3734     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3735            (!bylayer || self->transient_for->layer == layer) &&
3736            client_normal(self->transient_for))
3737         self = self->transient_for;
3738
3739     if (!self->transient_for)
3740         ret = g_slist_prepend(ret, self);
3741     else {
3742             GSList *it;
3743
3744             g_assert(self->group);
3745
3746             for (it = self->group->members; it; it = g_slist_next(it)) {
3747                 ObClient *c = it->data;
3748
3749                 if (!c->transient_for && client_normal(c) &&
3750                     (!bylayer || c->layer == layer))
3751                 {
3752                     ret = g_slist_prepend(ret, c);
3753                 }
3754             }
3755
3756             if (ret == NULL) /* no group parents */
3757                 ret = g_slist_prepend(ret, self);
3758     }
3759
3760     return ret;
3761 }
3762
3763 GSList *client_search_all_top_parents(ObClient *self)
3764 {
3765     return client_search_all_top_parents_internal(self, FALSE, 0);
3766 }
3767
3768 GSList *client_search_all_top_parents_layer(ObClient *self)
3769 {
3770     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3771 }
3772
3773 ObClient *client_search_focus_parent(ObClient *self)
3774 {
3775     if (self->transient_for) {
3776         if (self->transient_for != OB_TRAN_GROUP) {
3777             if (client_focused(self->transient_for))
3778                 return self->transient_for;
3779         } else {
3780             GSList *it;
3781
3782             for (it = self->group->members; it; it = g_slist_next(it)) {
3783                 ObClient *c = it->data;
3784
3785                 /* checking transient_for prevents infinate loops! */
3786                 if (c != self && !c->transient_for)
3787                     if (client_focused(c))
3788                         return c;
3789             }
3790         }
3791     }
3792
3793     return NULL;
3794 }
3795
3796 ObClient *client_search_parent(ObClient *self, ObClient *search)
3797 {
3798     if (self->transient_for) {
3799         if (self->transient_for != OB_TRAN_GROUP) {
3800             if (self->transient_for == search)
3801                 return search;
3802         } else {
3803             GSList *it;
3804
3805             for (it = self->group->members; it; it = g_slist_next(it)) {
3806                 ObClient *c = it->data;
3807
3808                 /* checking transient_for prevents infinate loops! */
3809                 if (c != self && !c->transient_for)
3810                     if (c == search)
3811                         return search;
3812             }
3813         }
3814     }
3815
3816     return NULL;
3817 }
3818
3819 ObClient *client_search_transient(ObClient *self, ObClient *search)
3820 {
3821     GSList *sit;
3822
3823     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3824         if (sit->data == search)
3825             return search;
3826         if (client_search_transient(sit->data, search))
3827             return search;
3828     }
3829     return NULL;
3830 }
3831
3832 #define WANT_EDGE(cur, c) \
3833             if(cur == c)                                                      \
3834                 continue;                                                     \
3835             if(!client_normal(cur))                                           \
3836                 continue;                                                     \
3837             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3838                 continue;                                                     \
3839             if(cur->iconic)                                                   \
3840                 continue;
3841
3842 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3843             if ((his_edge_start >= my_edge_start && \
3844                  his_edge_start <= my_edge_end) ||  \
3845                 (my_edge_start >= his_edge_start && \
3846                  my_edge_start <= his_edge_end))    \
3847                 dest = his_offset;
3848
3849 /* finds the nearest edge in the given direction from the current client
3850  * note to self: the edge is the -frame- edge (the actual one), not the
3851  * client edge.
3852  */
3853 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3854 {
3855     gint dest, monitor_dest;
3856     gint my_edge_start, my_edge_end, my_offset;
3857     GList *it;
3858     Rect *a, *monitor;
3859     
3860     if(!client_list)
3861         return -1;
3862
3863     a = screen_area(c->desktop);
3864     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3865
3866     switch(dir) {
3867     case OB_DIRECTION_NORTH:
3868         my_edge_start = c->frame->area.x;
3869         my_edge_end = c->frame->area.x + c->frame->area.width;
3870         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3871         
3872         /* default: top of screen */
3873         dest = a->y + (hang ? c->frame->area.height : 0);
3874         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3875         /* if the monitor edge comes before the screen edge, */
3876         /* use that as the destination instead. (For xinerama) */
3877         if (monitor_dest != dest && my_offset > monitor_dest)
3878             dest = monitor_dest; 
3879
3880         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3881             gint his_edge_start, his_edge_end, his_offset;
3882             ObClient *cur = it->data;
3883
3884             WANT_EDGE(cur, c)
3885
3886             his_edge_start = cur->frame->area.x;
3887             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3888             his_offset = cur->frame->area.y + 
3889                          (hang ? 0 : cur->frame->area.height);
3890
3891             if(his_offset + 1 > my_offset)
3892                 continue;
3893
3894             if(his_offset < dest)
3895                 continue;
3896
3897             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3898         }
3899         break;
3900     case OB_DIRECTION_SOUTH:
3901         my_edge_start = c->frame->area.x;
3902         my_edge_end = c->frame->area.x + c->frame->area.width;
3903         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3904
3905         /* default: bottom of screen */
3906         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3907         monitor_dest = monitor->y + monitor->height -
3908                        (hang ? c->frame->area.height : 0);
3909         /* if the monitor edge comes before the screen edge, */
3910         /* use that as the destination instead. (For xinerama) */
3911         if (monitor_dest != dest && my_offset < monitor_dest)
3912             dest = monitor_dest; 
3913
3914         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3915             gint his_edge_start, his_edge_end, his_offset;
3916             ObClient *cur = it->data;
3917
3918             WANT_EDGE(cur, c)
3919
3920             his_edge_start = cur->frame->area.x;
3921             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3922             his_offset = cur->frame->area.y +
3923                          (hang ? cur->frame->area.height : 0);
3924
3925
3926             if(his_offset - 1 < my_offset)
3927                 continue;
3928             
3929             if(his_offset > dest)
3930                 continue;
3931
3932             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3933         }
3934         break;
3935     case OB_DIRECTION_WEST:
3936         my_edge_start = c->frame->area.y;
3937         my_edge_end = c->frame->area.y + c->frame->area.height;
3938         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3939
3940         /* default: leftmost egde of screen */
3941         dest = a->x + (hang ? c->frame->area.width : 0);
3942         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3943         /* if the monitor edge comes before the screen edge, */
3944         /* use that as the destination instead. (For xinerama) */
3945         if (monitor_dest != dest && my_offset > monitor_dest)
3946             dest = monitor_dest;            
3947
3948         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3949             gint his_edge_start, his_edge_end, his_offset;
3950             ObClient *cur = it->data;
3951
3952             WANT_EDGE(cur, c)
3953
3954             his_edge_start = cur->frame->area.y;
3955             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3956             his_offset = cur->frame->area.x +
3957                          (hang ? 0 : cur->frame->area.width);
3958
3959             if(his_offset + 1 > my_offset)
3960                 continue;
3961
3962             if(his_offset < dest)
3963                 continue;
3964
3965             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3966         }
3967        break;
3968     case OB_DIRECTION_EAST:
3969         my_edge_start = c->frame->area.y;
3970         my_edge_end = c->frame->area.y + c->frame->area.height;
3971         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3972         
3973         /* default: rightmost edge of screen */
3974         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3975         monitor_dest = monitor->x + monitor->width -
3976                        (hang ? c->frame->area.width : 0);
3977         /* if the monitor edge comes before the screen edge, */
3978         /* use that as the destination instead. (For xinerama) */
3979         if (monitor_dest != dest && my_offset < monitor_dest)
3980             dest = monitor_dest;            
3981
3982         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3983             gint his_edge_start, his_edge_end, his_offset;
3984             ObClient *cur = it->data;
3985
3986             WANT_EDGE(cur, c)
3987
3988             his_edge_start = cur->frame->area.y;
3989             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3990             his_offset = cur->frame->area.x +
3991                          (hang ? cur->frame->area.width : 0);
3992
3993             if(his_offset - 1 < my_offset)
3994                 continue;
3995             
3996             if(his_offset > dest)
3997                 continue;
3998
3999             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4000         }
4001         break;
4002     case OB_DIRECTION_NORTHEAST:
4003     case OB_DIRECTION_SOUTHEAST:
4004     case OB_DIRECTION_NORTHWEST:
4005     case OB_DIRECTION_SOUTHWEST:
4006         /* not implemented */
4007     default:
4008         g_assert_not_reached();
4009         dest = 0; /* suppress warning */
4010     }
4011     return dest;
4012 }
4013
4014 ObClient* client_under_pointer()
4015 {
4016     gint x, y;
4017     GList *it;
4018     ObClient *ret = NULL;
4019
4020     if (screen_pointer_pos(&x, &y)) {
4021         for (it = stacking_list; it; it = g_list_next(it)) {
4022             if (WINDOW_IS_CLIENT(it->data)) {
4023                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4024                 if (c->frame->visible &&
4025                     /* ignore all animating windows */
4026                     !frame_iconify_animating(c->frame) &&
4027                     RECT_CONTAINS(c->frame->area, x, y))
4028                 {
4029                     ret = c;
4030                     break;
4031                 }
4032             }
4033         }
4034     }
4035     return ret;
4036 }
4037
4038 gboolean client_has_group_siblings(ObClient *self)
4039 {
4040     return self->group && self->group->members->next;
4041 }