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