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