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