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