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