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