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