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