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