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