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