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