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