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