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