]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
oops, added borders to windows when keepborder was on and they didnt have one to...
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003        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, TRUE);
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         gboolean transient;
419
420         transient = place_client(self, &x, &y, settings);
421
422         /* make sure the window is visible. */
423         client_find_onscreen(self, &x, &y,
424                              self->frame->area.width,
425                              self->frame->area.height,
426                              /* non-normal clients has less rules, and
427                                 windows that are being restored from a
428                                 session do also. we can assume you want
429                                 it back where you saved it. Clients saying
430                                 they placed themselves are subjected to
431                                 harder rules, ones that are placed by
432                                 place.c or by the user are allowed partially
433                                 off-screen and on xinerama divides (ie,
434                                 it is up to the placement routines to avoid
435                                 the xinerama divides) */
436                              transient ||
437                              (((self->positioned & PPosition) &&
438                                !(self->positioned & USPosition)) &&
439                               client_normal(self) &&
440                               !self->session));
441         if (x != ox || y != oy)          
442             client_move(self, x, y);
443     }
444
445     keyboard_grab_for_client(self, TRUE);
446     mouse_grab_for_client(self, TRUE);
447
448     client_showhide(self);
449
450     /* use client_focus instead of client_activate cuz client_activate does
451        stuff like switch desktops etc and I'm not interested in all that when
452        a window maps since its not based on an action from the user like
453        clicking a window to activate is. so keep the new window out of the way
454        but do focus it. */
455     if (activate) {
456         /* if using focus_delay, stop the timer now so that focus doesn't go
457            moving on us */
458         event_halt_focus_delay();
459
460         client_focus(self);
461         /* since focus can change the stacking orders, if we focus the window
462            then the standard raise it gets is not enough, we need to queue one
463            for after the focus change takes place */
464         client_raise(self);
465     }
466
467     /* client_activate does this but we aret using it so we have to do it
468        here as well */
469     if (screen_showing_desktop)
470         screen_show_desktop(FALSE);
471
472     /* add to client list/map */
473     client_list = g_list_append(client_list, self);
474     g_hash_table_insert(window_map, &self->window, self);
475
476     /* this has to happen after we're in the client_list */
477     screen_update_areas();
478
479     /* update the list hints */
480     client_set_list();
481
482     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
483 }
484
485 void client_unmanage_all()
486 {
487     while (client_list != NULL)
488         client_unmanage(client_list->data);
489 }
490
491 void client_unmanage(ObClient *self)
492 {
493     guint j;
494     GSList *it;
495
496     ob_debug("Unmanaging window: %lx (%s)\n", self->window, self->class);
497
498     g_assert(self != NULL);
499
500     keyboard_grab_for_client(self, FALSE);
501     mouse_grab_for_client(self, FALSE);
502
503     /* potentially fix focusLast */
504     if (config_focus_last)
505         grab_pointer(TRUE, OB_CURSOR_NONE);
506
507     /* remove the window from our save set */
508     XChangeSaveSet(ob_display, self->window, SetModeDelete);
509
510     /* we dont want events no more */
511     XSelectInput(ob_display, self->window, NoEventMask);
512
513     frame_hide(self->frame);
514
515     client_list = g_list_remove(client_list, self);
516     stacking_remove(self);
517     g_hash_table_remove(window_map, &self->window);
518
519     /* update the focus lists */
520     focus_order_remove(self);
521
522     /* once the client is out of the list, update the struts to remove it's
523        influence */
524     screen_update_areas();
525
526     for (it = client_destructors; it; it = g_slist_next(it)) {
527         Destructor *d = it->data;
528         d->func(self, d->data);
529     }
530         
531     if (focus_client == self) {
532         XEvent e;
533
534         /* focus the last focused window on the desktop, and ignore enter
535            events from the unmap so it doesnt mess with the focus */
536         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
537         /* remove these flags so we don't end up getting focused in the
538            fallback! */
539         self->can_focus = FALSE;
540         self->focus_notify = FALSE;
541         self->modal = FALSE;
542         client_unfocus(self);
543     }
544
545     /* tell our parent(s) that we're gone */
546     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
547         for (it = self->group->members; it; it = g_slist_next(it))
548             if (it->data != self)
549                 ((ObClient*)it->data)->transients =
550                     g_slist_remove(((ObClient*)it->data)->transients, self);
551     } else if (self->transient_for) {        /* transient of window */
552         self->transient_for->transients =
553             g_slist_remove(self->transient_for->transients, self);
554     }
555
556     /* tell our transients that we're gone */
557     for (it = self->transients; it; it = g_slist_next(it)) {
558         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
559             ((ObClient*)it->data)->transient_for = NULL;
560             client_calc_layer(it->data);
561         }
562     }
563
564     /* remove from its group */
565     if (self->group) {
566         group_remove(self->group, self);
567         self->group = NULL;
568     }
569
570     /* give the client its border back */
571     client_toggle_border(self, TRUE);
572
573     /* reparent the window out of the frame, and free the frame */
574     frame_release_client(self->frame, self);
575     self->frame = NULL;
576      
577     if (ob_state() != OB_STATE_EXITING) {
578         /* these values should not be persisted across a window
579            unmapping/mapping */
580         PROP_ERASE(self->window, net_wm_desktop);
581         PROP_ERASE(self->window, net_wm_state);
582         PROP_ERASE(self->window, wm_state);
583     } else {
584         /* if we're left in an unmapped state, the client wont be mapped. this
585            is bad, since we will no longer be managing the window on restart */
586         XMapWindow(ob_display, self->window);
587     }
588
589
590     ob_debug("Unmanaged window 0x%lx\n", self->window);
591
592     /* free all data allocated in the client struct */
593     g_slist_free(self->transients);
594     for (j = 0; j < self->nicons; ++j)
595         g_free(self->icons[j].data);
596     if (self->nicons > 0)
597         g_free(self->icons);
598     g_free(self->title);
599     g_free(self->icon_title);
600     g_free(self->name);
601     g_free(self->class);
602     g_free(self->role);
603     g_free(self->sm_client_id);
604     g_free(self);
605      
606     /* update the list hints */
607     client_set_list();
608
609     if (config_focus_last)
610         grab_pointer(FALSE, OB_CURSOR_NONE);
611 }
612
613 static void client_urgent_notify(ObClient *self)
614 {
615     if (self->urgent)
616         frame_flash_start(self->frame);
617     else
618         frame_flash_stop(self->frame);
619 }
620
621 static void client_restore_session_state(ObClient *self)
622 {
623     GList *it;
624
625     if (!(it = session_state_find(self)))
626         return;
627
628     self->session = it->data;
629
630     RECT_SET_POINT(self->area, self->session->x, self->session->y);
631     self->positioned = PPosition;
632     if (self->session->w > 0)
633         self->area.width = self->session->w;
634     if (self->session->h > 0)
635         self->area.height = self->session->h;
636     XResizeWindow(ob_display, self->window,
637                   self->area.width, self->area.height);
638
639     self->desktop = (self->session->desktop == DESKTOP_ALL ?
640                      self->session->desktop :
641                      MIN(screen_num_desktops - 1, self->session->desktop));
642     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
643
644     self->shaded = self->session->shaded;
645     self->iconic = self->session->iconic;
646     self->skip_pager = self->session->skip_pager;
647     self->skip_taskbar = self->session->skip_taskbar;
648     self->fullscreen = self->session->fullscreen;
649     self->above = self->session->above;
650     self->below = self->session->below;
651     self->max_horz = self->session->max_horz;
652     self->max_vert = self->session->max_vert;
653 }
654
655 static void client_restore_session_stacking(ObClient *self)
656 {
657     GList *it;
658
659     if (!self->session) return;
660
661     it = g_list_find(session_saved_state, self->session);
662     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
663         GList *cit;
664
665         for (cit = client_list; cit; cit = g_list_next(cit))
666             if (session_state_cmp(it->data, cit->data))
667                 break;
668         if (cit) {
669             client_calc_layer(self);
670             stacking_below(CLIENT_AS_WINDOW(self),
671                            CLIENT_AS_WINDOW(cit->data));
672             break;
673         }
674     }
675 }
676
677 void client_move_onscreen(ObClient *self, gboolean rude)
678 {
679     gint x = self->area.x;
680     gint y = self->area.y;
681     if (client_find_onscreen(self, &x, &y,
682                              self->frame->area.width,
683                              self->frame->area.height, rude)) {
684         client_move(self, x, y);
685     }
686 }
687
688 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
689                               gboolean rude)
690 {
691     Rect *a;
692     gint ox = *x, oy = *y;
693
694     frame_client_gravity(self->frame, x, y); /* get where the frame
695                                                 would be */
696
697     /* XXX watch for xinerama dead areas */
698     /* This makes sure windows aren't entirely outside of the screen so you
699      * can't see them at all */
700     if (client_normal(self)) {
701         a = screen_area(self->desktop);
702         if (!self->strut.right && *x >= a->x + a->width - 1)
703             *x = a->x + a->width - self->frame->area.width;
704         if (!self->strut.bottom && *y >= a->y + a->height - 1)
705             *y = a->y + a->height - self->frame->area.height;
706         if (!self->strut.left && *x + self->frame->area.width - 1 < a->x)
707             *x = a->x;
708         if (!self->strut.top && *y + self->frame->area.height - 1 < a->y)
709             *y = a->y;
710     }
711
712     /* This here doesn't let windows even a pixel outside the screen,
713      * when called from client_manage, programs placing themselves are
714      * forced completely onscreen, while things like
715      * xterm -geometry resolution-width/2 will work fine. Trying to
716      * place it completely offscreen will be handled in the above code.
717      * Sorry for this confused comment, i am tired. */
718     if (rude) {
719         /* avoid the xinerama monitor divide while we're at it,
720          * remember to fix the placement stuff to avoid it also and
721          * then remove this XXX */
722         a = screen_physical_area_monitor(client_monitor(self));
723         /* dont let windows map/move into the strut unless they
724            are bigger than the available area */
725         if (w <= a->width) {
726             if (!self->strut.left && *x < a->x) *x = a->x;
727             if (!self->strut.right && *x + w > a->x + a->width)
728                 *x = a->x + a->width - w;
729         }
730         if (h <= a->height) {
731             if (!self->strut.top && *y < a->y) *y = a->y;
732             if (!self->strut.bottom && *y + h > a->y + a->height)
733                 *y = a->y + a->height - h;
734         }
735     }
736
737     frame_frame_gravity(self->frame, x, y); /* get where the client
738                                                should be */
739
740     return ox != *x || oy != *y;
741 }
742
743 static void client_toggle_border(ObClient *self, gboolean show)
744 {
745     /* adjust our idea of where the client is, based on its border. When the
746        border is removed, the client should now be considered to be in a
747        different position.
748        when re-adding the border to the client, the same operation needs to be
749        reversed. */
750     gint oldx = self->area.x, oldy = self->area.y;
751     gint x = oldx, y = oldy;
752     switch(self->gravity) {
753     default:
754     case NorthWestGravity:
755     case WestGravity:
756     case SouthWestGravity:
757         break;
758     case NorthEastGravity:
759     case EastGravity:
760     case SouthEastGravity:
761         if (show) x -= self->border_width * 2;
762         else      x += self->border_width * 2;
763         break;
764     case NorthGravity:
765     case SouthGravity:
766     case CenterGravity:
767     case ForgetGravity:
768     case StaticGravity:
769         if (show) x -= self->border_width;
770         else      x += self->border_width;
771         break;
772     }
773     switch(self->gravity) {
774     default:
775     case NorthWestGravity:
776     case NorthGravity:
777     case NorthEastGravity:
778         break;
779     case SouthWestGravity:
780     case SouthGravity:
781     case SouthEastGravity:
782         if (show) y -= self->border_width * 2;
783         else      y += self->border_width * 2;
784         break;
785     case WestGravity:
786     case EastGravity:
787     case CenterGravity:
788     case ForgetGravity:
789     case StaticGravity:
790         if (show) y -= self->border_width;
791         else      y += self->border_width;
792         break;
793     }
794     self->area.x = x;
795     self->area.y = y;
796
797     if (show) {
798         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
799
800         /* move the client so it is back it the right spot _with_ its
801            border! */
802         if (x != oldx || y != oldy)
803             XMoveWindow(ob_display, self->window, x, y);
804     } else
805         XSetWindowBorderWidth(ob_display, self->window, 0);
806 }
807
808
809 static void client_get_all(ObClient *self)
810 {
811     client_get_area(self);
812     client_update_transient_for(self);
813     client_update_wmhints(self);
814     client_get_startup_id(self);
815     client_get_desktop(self);
816     client_get_shaped(self);
817
818     client_get_mwm_hints(self);
819     client_get_type(self);/* this can change the mwmhints for special cases */
820
821     /* The transient hint is used to pick a type, but the type can also affect
822        transiency (dialogs are always made transients). This is Havoc's idea,
823        but it is needed to make some apps work right (eg tsclient). */
824     client_update_transient_for(self);
825
826     client_get_state(self);
827
828     {
829         /* a couple type-based defaults for new windows */
830
831         /* this makes sure that these windows appear on all desktops */
832         if (self->type == OB_CLIENT_TYPE_DESKTOP)
833             self->desktop = DESKTOP_ALL;
834     }
835
836     client_update_protocols(self);
837
838     client_get_gravity(self); /* get the attribute gravity */
839     client_update_normal_hints(self); /* this may override the attribute
840                                          gravity */
841
842     /* got the type, the mwmhints, the protocols, and the normal hints
843        (min/max sizes), so we're ready to set up the decorations/functions */
844     client_setup_decor_and_functions(self);
845   
846     client_update_title(self);
847     client_update_class(self);
848     client_update_sm_client_id(self);
849     client_update_strut(self);
850     client_update_icons(self);
851 }
852
853 static void client_get_startup_id(ObClient *self)
854 {
855     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
856         if (self->group)
857             PROP_GETS(self->group->leader,
858                       net_startup_id, utf8, &self->startup_id);
859 }
860
861 static void client_get_area(ObClient *self)
862 {
863     XWindowAttributes wattrib;
864     Status ret;
865   
866     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
867     g_assert(ret != BadWindow);
868
869     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
870     self->border_width = wattrib.border_width;
871 }
872
873 static void client_get_desktop(ObClient *self)
874 {
875     guint32 d = screen_num_desktops; /* an always-invalid value */
876
877     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
878         if (d >= screen_num_desktops && d != DESKTOP_ALL)
879             self->desktop = screen_num_desktops - 1;
880         else
881             self->desktop = d;
882     } else {
883         gboolean trdesk = FALSE;
884
885         if (self->transient_for) {
886             if (self->transient_for != OB_TRAN_GROUP) {
887                 self->desktop = self->transient_for->desktop;
888                 trdesk = TRUE;
889             } else {
890                 GSList *it;
891
892                 for (it = self->group->members; it; it = g_slist_next(it))
893                     if (it->data != self &&
894                         !((ObClient*)it->data)->transient_for) {
895                         self->desktop = ((ObClient*)it->data)->desktop;
896                         trdesk = TRUE;
897                         break;
898                     }
899             }
900         }
901         if (!trdesk) {
902             /* try get from the startup-notification protocol */
903             if (sn_get_desktop(self->startup_id, &self->desktop)) {
904                 if (self->desktop >= screen_num_desktops &&
905                     self->desktop != DESKTOP_ALL)
906                     self->desktop = screen_num_desktops - 1;
907             } else
908                 /* defaults to the current desktop */
909                 self->desktop = screen_desktop;
910         }
911     }
912     if (self->desktop != d) {
913         /* set the desktop hint, to make sure that it always exists */
914         PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
915     }
916 }
917
918 static void client_get_state(ObClient *self)
919 {
920     guint32 *state;
921     guint num;
922   
923     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
924         gulong i;
925         for (i = 0; i < num; ++i) {
926             if (state[i] == prop_atoms.net_wm_state_modal)
927                 self->modal = TRUE;
928             else if (state[i] == prop_atoms.net_wm_state_shaded)
929                 self->shaded = TRUE;
930             else if (state[i] == prop_atoms.net_wm_state_hidden)
931                 self->iconic = TRUE;
932             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
933                 self->skip_taskbar = TRUE;
934             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
935                 self->skip_pager = TRUE;
936             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
937                 self->fullscreen = TRUE;
938             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
939                 self->max_vert = TRUE;
940             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
941                 self->max_horz = TRUE;
942             else if (state[i] == prop_atoms.net_wm_state_above)
943                 self->above = TRUE;
944             else if (state[i] == prop_atoms.net_wm_state_below)
945                 self->below = TRUE;
946             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
947                 self->undecorated = TRUE;
948         }
949
950         g_free(state);
951     }
952
953     if (!(self->above || self->below)) {
954         if (self->group) {
955             /* apply stuff from the group */
956             GSList *it;
957             gint layer = -2;
958
959             for (it = self->group->members; it; it = g_slist_next(it)) {
960                 ObClient *c = it->data;
961                 if (c != self && !client_search_transient(self, c) &&
962                     client_normal(self) && client_normal(c))
963                 {
964                     layer = MAX(layer,
965                                 (c->above ? 1 : (c->below ? -1 : 0)));
966                 }
967             }
968             switch (layer) {
969             case -1:
970                 self->below = TRUE;
971                 break;
972             case -2:
973             case 0:
974                 break;
975             case 1:
976                 self->above = TRUE;
977                 break;
978             default:
979                 g_assert_not_reached();
980                 break;
981             }
982         }
983     }
984 }
985
986 static void client_get_shaped(ObClient *self)
987 {
988     self->shaped = FALSE;
989 #ifdef   SHAPE
990     if (extensions_shape) {
991         gint foo;
992         guint ufoo;
993         gint s;
994
995         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
996
997         XShapeQueryExtents(ob_display, self->window, &s, &foo,
998                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
999                            &ufoo);
1000         self->shaped = (s != 0);
1001     }
1002 #endif
1003 }
1004
1005 void client_update_transient_for(ObClient *self)
1006 {
1007     Window t = None;
1008     ObClient *target = NULL;
1009
1010     if (XGetTransientForHint(ob_display, self->window, &t)) {
1011         self->transient = TRUE;
1012         if (t != self->window) { /* cant be transient to itself! */
1013             target = g_hash_table_lookup(window_map, &t);
1014             /* if this happens then we need to check for it*/
1015             g_assert(target != self);
1016             if (target && !WINDOW_IS_CLIENT(target)) {
1017                 /* this can happen when a dialog is a child of
1018                    a dockapp, for example */
1019                 target = NULL;
1020             }
1021             
1022             if (!target && self->group) {
1023                 /* not transient to a client, see if it is transient for a
1024                    group */
1025                 if (t == self->group->leader ||
1026                     t == None ||
1027                     t == RootWindow(ob_display, ob_screen))
1028                 {
1029                     /* window is a transient for its group! */
1030                     target = OB_TRAN_GROUP;
1031                 }
1032             }
1033         }
1034     } else if (self->type == OB_CLIENT_TYPE_DIALOG && self->group) {
1035         self->transient = TRUE;
1036         target = OB_TRAN_GROUP;
1037     } else
1038         self->transient = FALSE;
1039
1040     /* if anything has changed... */
1041     if (target != self->transient_for) {
1042         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1043             GSList *it;
1044
1045             /* remove from old parents */
1046             for (it = self->group->members; it; it = g_slist_next(it)) {
1047                 ObClient *c = it->data;
1048                 if (c != self && !c->transient_for)
1049                     c->transients = g_slist_remove(c->transients, self);
1050             }
1051         } else if (self->transient_for != NULL) { /* transient of window */
1052             /* remove from old parent */
1053             self->transient_for->transients =
1054                 g_slist_remove(self->transient_for->transients, self);
1055         }
1056         self->transient_for = target;
1057         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1058             GSList *it;
1059
1060             /* add to new parents */
1061             for (it = self->group->members; it; it = g_slist_next(it)) {
1062                 ObClient *c = it->data;
1063                 if (c != self && !c->transient_for)
1064                     c->transients = g_slist_append(c->transients, self);
1065             }
1066
1067             /* remove all transients which are in the group, that causes
1068                circlular pointer hell of doom */
1069             for (it = self->group->members; it; it = g_slist_next(it)) {
1070                 GSList *sit, *next;
1071                 for (sit = self->transients; sit; sit = next) {
1072                     next = g_slist_next(sit);
1073                     if (sit->data == it->data)
1074                         self->transients =
1075                             g_slist_delete_link(self->transients, sit);
1076                 }
1077             }
1078         } else if (self->transient_for != NULL) { /* transient of window */
1079             /* add to new parent */
1080             self->transient_for->transients =
1081                 g_slist_append(self->transient_for->transients, self);
1082         }
1083     }
1084 }
1085
1086 static void client_get_mwm_hints(ObClient *self)
1087 {
1088     guint num;
1089     guint32 *hints;
1090
1091     self->mwmhints.flags = 0; /* default to none */
1092
1093     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1094                     &hints, &num)) {
1095         if (num >= OB_MWM_ELEMENTS) {
1096             self->mwmhints.flags = hints[0];
1097             self->mwmhints.functions = hints[1];
1098             self->mwmhints.decorations = hints[2];
1099         }
1100         g_free(hints);
1101     }
1102 }
1103
1104 void client_get_type(ObClient *self)
1105 {
1106     guint num, i;
1107     guint32 *val;
1108
1109     self->type = -1;
1110   
1111     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1112         /* use the first value that we know about in the array */
1113         for (i = 0; i < num; ++i) {
1114             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1115                 self->type = OB_CLIENT_TYPE_DESKTOP;
1116             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1117                 self->type = OB_CLIENT_TYPE_DOCK;
1118             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1119                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1120             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1121                 self->type = OB_CLIENT_TYPE_MENU;
1122             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1123                 self->type = OB_CLIENT_TYPE_UTILITY;
1124             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1125                 self->type = OB_CLIENT_TYPE_SPLASH;
1126             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1127                 self->type = OB_CLIENT_TYPE_DIALOG;
1128             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1129                 self->type = OB_CLIENT_TYPE_NORMAL;
1130             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1131                 /* prevent this window from getting any decor or
1132                    functionality */
1133                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1134                                          OB_MWM_FLAG_DECORATIONS);
1135                 self->mwmhints.decorations = 0;
1136                 self->mwmhints.functions = 0;
1137             }
1138             if (self->type != (ObClientType) -1)
1139                 break; /* grab the first legit type */
1140         }
1141         g_free(val);
1142     }
1143     
1144     if (self->type == (ObClientType) -1) {
1145         /*the window type hint was not set, which means we either classify
1146           ourself as a normal window or a dialog, depending on if we are a
1147           transient. */
1148         if (self->transient)
1149             self->type = OB_CLIENT_TYPE_DIALOG;
1150         else
1151             self->type = OB_CLIENT_TYPE_NORMAL;
1152     }
1153 }
1154
1155 void client_update_protocols(ObClient *self)
1156 {
1157     guint32 *proto;
1158     guint num_return, i;
1159
1160     self->focus_notify = FALSE;
1161     self->delete_window = FALSE;
1162
1163     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1164         for (i = 0; i < num_return; ++i) {
1165             if (proto[i] == prop_atoms.wm_delete_window) {
1166                 /* this means we can request the window to close */
1167                 self->delete_window = TRUE;
1168             } else if (proto[i] == prop_atoms.wm_take_focus)
1169                 /* if this protocol is requested, then the window will be
1170                    notified whenever we want it to receive focus */
1171                 self->focus_notify = TRUE;
1172         }
1173         g_free(proto);
1174     }
1175 }
1176
1177 static void client_get_gravity(ObClient *self)
1178 {
1179     XWindowAttributes wattrib;
1180     Status ret;
1181
1182     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1183     g_assert(ret != BadWindow);
1184     self->gravity = wattrib.win_gravity;
1185 }
1186
1187 void client_update_normal_hints(ObClient *self)
1188 {
1189     XSizeHints size;
1190     glong ret;
1191     gint oldgravity = self->gravity;
1192
1193     /* defaults */
1194     self->min_ratio = 0.0f;
1195     self->max_ratio = 0.0f;
1196     SIZE_SET(self->size_inc, 1, 1);
1197     SIZE_SET(self->base_size, 0, 0);
1198     SIZE_SET(self->min_size, 0, 0);
1199     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1200
1201     /* get the hints from the window */
1202     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1203         /* normal windows can't request placement! har har
1204         if (!client_normal(self))
1205         */
1206         self->positioned = (size.flags & (PPosition|USPosition));
1207
1208         if (size.flags & PWinGravity) {
1209             self->gravity = size.win_gravity;
1210       
1211             /* if the client has a frame, i.e. has already been mapped and
1212                is changing its gravity */
1213             if (self->frame && self->gravity != oldgravity) {
1214                 /* move our idea of the client's position based on its new
1215                    gravity */
1216                 self->area.x = self->frame->area.x;
1217                 self->area.y = self->frame->area.y;
1218                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1219             }
1220         }
1221
1222         if (size.flags & PAspect) {
1223             if (size.min_aspect.y)
1224                 self->min_ratio =
1225                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1226             if (size.max_aspect.y)
1227                 self->max_ratio =
1228                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1229         }
1230
1231         if (size.flags & PMinSize)
1232             SIZE_SET(self->min_size, size.min_width, size.min_height);
1233     
1234         if (size.flags & PMaxSize)
1235             SIZE_SET(self->max_size, size.max_width, size.max_height);
1236     
1237         if (size.flags & PBaseSize)
1238             SIZE_SET(self->base_size, size.base_width, size.base_height);
1239     
1240         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1241             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1242     }
1243 }
1244
1245 void client_setup_decor_and_functions(ObClient *self)
1246 {
1247     /* start with everything (cept fullscreen) */
1248     self->decorations =
1249         (OB_FRAME_DECOR_TITLEBAR |
1250          OB_FRAME_DECOR_HANDLE |
1251          OB_FRAME_DECOR_GRIPS |
1252          OB_FRAME_DECOR_BORDER |
1253          OB_FRAME_DECOR_ICON |
1254          OB_FRAME_DECOR_ALLDESKTOPS |
1255          OB_FRAME_DECOR_ICONIFY |
1256          OB_FRAME_DECOR_MAXIMIZE |
1257          OB_FRAME_DECOR_SHADE |
1258          OB_FRAME_DECOR_CLOSE);
1259     self->functions =
1260         (OB_CLIENT_FUNC_RESIZE |
1261          OB_CLIENT_FUNC_MOVE |
1262          OB_CLIENT_FUNC_ICONIFY |
1263          OB_CLIENT_FUNC_MAXIMIZE |
1264          OB_CLIENT_FUNC_SHADE |
1265          OB_CLIENT_FUNC_CLOSE);
1266
1267     if (!(self->min_size.width < self->max_size.width ||
1268           self->min_size.height < self->max_size.height))
1269         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1270
1271     switch (self->type) {
1272     case OB_CLIENT_TYPE_NORMAL:
1273         /* normal windows retain all of the possible decorations and
1274            functionality, and are the only windows that you can fullscreen */
1275         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1276         break;
1277
1278     case OB_CLIENT_TYPE_DIALOG:
1279     case OB_CLIENT_TYPE_UTILITY:
1280         /* these windows cannot be maximized */
1281         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1282         break;
1283
1284     case OB_CLIENT_TYPE_MENU:
1285     case OB_CLIENT_TYPE_TOOLBAR:
1286         /* these windows get less functionality */
1287         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1288         break;
1289
1290     case OB_CLIENT_TYPE_DESKTOP:
1291     case OB_CLIENT_TYPE_DOCK:
1292     case OB_CLIENT_TYPE_SPLASH:
1293         /* none of these windows are manipulated by the window manager */
1294         self->decorations = 0;
1295         self->functions = 0;
1296         break;
1297     }
1298
1299     /* Mwm Hints are applied subtractively to what has already been chosen for
1300        decor and functionality */
1301     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1302         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1303             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1304                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1305                 /* if the mwm hints request no handle or title, then all
1306                    decorations are disabled */
1307                 self->decorations = config_theme_keepborder ?
1308                     self->decorations & OB_FRAME_DECOR_BORDER : 0;
1309         }
1310     }
1311
1312     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1313         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1314             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1315                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1316             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1317                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1318             /* dont let mwm hints kill any buttons
1319                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1320                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1321                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1322                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1323             */
1324             /* dont let mwm hints kill the close button
1325                if (! (self->mwmhints.functions & MwmFunc_Close))
1326                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1327         }
1328     }
1329
1330     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1331         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1332     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1333         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1334     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1335         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1336
1337     /* can't maximize without moving/resizing */
1338     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1339           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1340           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1341         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1342         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1343     }
1344
1345     /* kill the handle on fully maxed windows */
1346     if (self->max_vert && self->max_horz)
1347         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1348
1349     /* finally, the user can have requested no decorations, which overrides
1350        everything (but doesnt give it a border if it doesnt have one) */
1351     if (self->undecorated) {
1352         if (config_theme_keepborder)
1353             self->decorations &= OB_FRAME_DECOR_BORDER;
1354         else
1355             self->decorations = 0;
1356     }
1357
1358     /* if we don't have a titlebar, then we cannot shade! */
1359     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1360         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1361
1362     /* now we need to check against rules for the client's current state */
1363     if (self->fullscreen) {
1364         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1365                             OB_CLIENT_FUNC_FULLSCREEN |
1366                             OB_CLIENT_FUNC_ICONIFY);
1367         self->decorations = 0;
1368     }
1369
1370     client_change_allowed_actions(self);
1371
1372     if (self->frame) {
1373         /* adjust the client's decorations, etc. */
1374         client_reconfigure(self);
1375     }
1376 }
1377
1378 static void client_change_allowed_actions(ObClient *self)
1379 {
1380     gulong actions[9];
1381     gint num = 0;
1382
1383     /* desktop windows are kept on all desktops */
1384     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1385         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1386
1387     if (self->functions & OB_CLIENT_FUNC_SHADE)
1388         actions[num++] = prop_atoms.net_wm_action_shade;
1389     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1390         actions[num++] = prop_atoms.net_wm_action_close;
1391     if (self->functions & OB_CLIENT_FUNC_MOVE)
1392         actions[num++] = prop_atoms.net_wm_action_move;
1393     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1394         actions[num++] = prop_atoms.net_wm_action_minimize;
1395     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1396         actions[num++] = prop_atoms.net_wm_action_resize;
1397     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1398         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1399     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1400         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1401         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1402     }
1403
1404     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1405
1406     /* make sure the window isn't breaking any rules now */
1407
1408     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1409         if (self->frame) client_shade(self, FALSE);
1410         else self->shaded = FALSE;
1411     }
1412     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1413         if (self->frame) client_iconify(self, FALSE, TRUE);
1414         else self->iconic = FALSE;
1415     }
1416     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1417         if (self->frame) client_fullscreen(self, FALSE, TRUE);
1418         else self->fullscreen = FALSE;
1419     }
1420     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1421                                                          self->max_vert)) {
1422         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1423         else self->max_vert = self->max_horz = FALSE;
1424     }
1425 }
1426
1427 void client_reconfigure(ObClient *self)
1428 {
1429     /* by making this pass FALSE for user, we avoid the emacs event storm where
1430        every configurenotify causes an update in its normal hints, i think this
1431        is generally what we want anyways... */
1432     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1433                      self->area.width, self->area.height, FALSE, TRUE);
1434 }
1435
1436 void client_update_wmhints(ObClient *self)
1437 {
1438     XWMHints *hints;
1439     gboolean ur = FALSE;
1440     GSList *it;
1441
1442     /* assume a window takes input if it doesnt specify */
1443     self->can_focus = TRUE;
1444   
1445     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1446         if (hints->flags & InputHint)
1447             self->can_focus = hints->input;
1448
1449         /* only do this when first managing the window *AND* when we aren't
1450            starting up! */
1451         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1452             if (hints->flags & StateHint)
1453                 self->iconic = hints->initial_state == IconicState;
1454
1455         if (hints->flags & XUrgencyHint)
1456             ur = TRUE;
1457
1458         if (!(hints->flags & WindowGroupHint))
1459             hints->window_group = None;
1460
1461         /* did the group state change? */
1462         if (hints->window_group !=
1463             (self->group ? self->group->leader : None)) {
1464             /* remove from the old group if there was one */
1465             if (self->group != NULL) {
1466                 /* remove transients of the group */
1467                 for (it = self->group->members; it; it = g_slist_next(it))
1468                     self->transients = g_slist_remove(self->transients,
1469                                                       it->data);
1470
1471                 /* remove myself from parents in the group */
1472                 if (self->transient_for == OB_TRAN_GROUP) {
1473                     for (it = self->group->members; it;
1474                          it = g_slist_next(it))
1475                     {
1476                         ObClient *c = it->data;
1477
1478                         if (c != self && !c->transient_for)
1479                             c->transients = g_slist_remove(c->transients,
1480                                                            self);
1481                     }
1482                 }
1483
1484                 group_remove(self->group, self);
1485                 self->group = NULL;
1486             }
1487             if (hints->window_group != None) {
1488                 self->group = group_add(hints->window_group, self);
1489
1490                 /* i can only have transients from the group if i am not
1491                    transient myself */
1492                 if (!self->transient_for) {
1493                     /* add other transients of the group that are already
1494                        set up */
1495                     for (it = self->group->members; it;
1496                          it = g_slist_next(it))
1497                     {
1498                         ObClient *c = it->data;
1499                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1500                             self->transients =
1501                                 g_slist_append(self->transients, c);
1502                     }
1503                 }
1504             }
1505
1506             /* because the self->transient flag wont change from this call,
1507                we don't need to update the window's type and such, only its
1508                transient_for, and the transients lists of other windows in
1509                the group may be affected */
1510             client_update_transient_for(self);
1511         }
1512
1513         /* the WM_HINTS can contain an icon */
1514         client_update_icons(self);
1515
1516         XFree(hints);
1517     }
1518
1519     if (ur != self->urgent) {
1520         self->urgent = ur;
1521         /* fire the urgent callback if we're mapped, otherwise, wait until
1522            after we're mapped */
1523         if (self->frame)
1524             client_urgent_notify(self);
1525     }
1526 }
1527
1528 void client_update_title(ObClient *self)
1529 {
1530     GList *it;
1531     guint32 nums;
1532     guint i;
1533     gchar *data = NULL;
1534     gboolean read_title;
1535     gchar *old_title;
1536
1537     old_title = self->title;
1538      
1539     /* try netwm */
1540     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1541         /* try old x stuff */
1542         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1543               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1544             // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1545             if (self->transient) {
1546                 data = g_strdup("");
1547                 goto no_number;
1548             } else
1549                 data = g_strdup("Unnamed Window");
1550         }
1551     }
1552
1553     if (config_title_number) {
1554
1555         /* did the title change? then reset the title_count */
1556         if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1557             self->title_count = 1;
1558
1559         /* look for duplicates and append a number */
1560         nums = 0;
1561         for (it = client_list; it; it = g_list_next(it))
1562             if (it->data != self) {
1563                 ObClient *c = it->data;
1564                 if (0 == strncmp(c->title, data, strlen(data)))
1565                     nums |= 1 << c->title_count;
1566             }
1567         /* find first free number */
1568         for (i = 1; i <= 32; ++i)
1569             if (!(nums & (1 << i))) {
1570                 if (self->title_count == 1 || i == 1)
1571                     self->title_count = i;
1572                 break;
1573             }
1574         /* dont display the number for the first window */
1575         if (self->title_count > 1) {
1576             gchar *ndata;
1577             ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1578             g_free(data);
1579             data = ndata;
1580         }
1581     } else
1582         self->title_count = 1;
1583
1584 no_number:
1585     PROP_SETS(self->window, net_wm_visible_name, data);
1586     self->title = data;
1587
1588     if (self->frame)
1589         frame_adjust_title(self->frame);
1590
1591     g_free(old_title);
1592
1593     /* update the icon title */
1594     data = NULL;
1595     g_free(self->icon_title);
1596
1597     read_title = TRUE;
1598     /* try netwm */
1599     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1600         /* try old x stuff */
1601         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data)
1602               || PROP_GETS(self->window, wm_icon_name, utf8, &data))) {
1603             data = g_strdup(self->title);
1604             read_title = FALSE;
1605         }
1606
1607     /* append the title count, dont display the number for the first window.
1608      * We don't need to check for config_title_number here since title_count
1609      * is not set above 1 then. */
1610     if (read_title && self->title_count > 1) {
1611         gchar *vdata, *ndata;
1612         ndata = g_strdup_printf(" - [%u]", self->title_count);
1613         vdata = g_strconcat(data, ndata, NULL);
1614         g_free(ndata);
1615         g_free(data);
1616         data = vdata;
1617     }
1618
1619     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1620
1621     self->icon_title = data;
1622 }
1623
1624 void client_update_class(ObClient *self)
1625 {
1626     gchar **data;
1627     gchar *s;
1628
1629     if (self->name) g_free(self->name);
1630     if (self->class) g_free(self->class);
1631     if (self->role) g_free(self->role);
1632
1633     self->name = self->class = self->role = NULL;
1634
1635     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1636         if (data[0]) {
1637             self->name = g_strdup(data[0]);
1638             if (data[1])
1639                 self->class = g_strdup(data[1]);
1640         }
1641         g_strfreev(data);     
1642     }
1643
1644     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1645         self->role = s;
1646
1647     if (self->name == NULL) self->name = g_strdup("");
1648     if (self->class == NULL) self->class = g_strdup("");
1649     if (self->role == NULL) self->role = g_strdup("");
1650 }
1651
1652 void client_update_strut(ObClient *self)
1653 {
1654     guint num;
1655     guint32 *data;
1656     gboolean got = FALSE;
1657     StrutPartial strut;
1658
1659     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1660                     &data, &num)) {
1661         if (num == 12) {
1662             got = TRUE;
1663             STRUT_PARTIAL_SET(strut,
1664                               data[0], data[2], data[1], data[3],
1665                               data[4], data[5], data[8], data[9],
1666                               data[6], data[7], data[10], data[11]);
1667         }
1668         g_free(data);
1669     }
1670
1671     if (!got &&
1672         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1673         if (num == 4) {
1674             const Rect *a;
1675
1676             got = TRUE;
1677
1678             /* use the screen's width/height */
1679             a = screen_physical_area();
1680
1681             STRUT_PARTIAL_SET(strut,
1682                               data[0], data[2], data[1], data[3],
1683                               a->y, a->y + a->height - 1,
1684                               a->x, a->x + a->width - 1,
1685                               a->y, a->y + a->height - 1,
1686                               a->x, a->x + a->width - 1);
1687         }
1688         g_free(data);
1689     }
1690
1691     if (!got)
1692         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1693                           0, 0, 0, 0, 0, 0, 0, 0);
1694
1695     if (!STRUT_EQUAL(strut, self->strut)) {
1696         self->strut = strut;
1697
1698         /* updating here is pointless while we're being mapped cuz we're not in
1699            the client list yet */
1700         if (self->frame)
1701             screen_update_areas();
1702     }
1703 }
1704
1705 void client_update_icons(ObClient *self)
1706 {
1707     guint num;
1708     guint32 *data;
1709     guint w, h, i, j;
1710
1711     for (i = 0; i < self->nicons; ++i)
1712         g_free(self->icons[i].data);
1713     if (self->nicons > 0)
1714         g_free(self->icons);
1715     self->nicons = 0;
1716
1717     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1718         /* figure out how many valid icons are in here */
1719         i = 0;
1720         while (num - i > 2) {
1721             w = data[i++];
1722             h = data[i++];
1723             i += w * h;
1724             if (i > num || w*h == 0) break;
1725             ++self->nicons;
1726         }
1727
1728         self->icons = g_new(ObClientIcon, self->nicons);
1729     
1730         /* store the icons */
1731         i = 0;
1732         for (j = 0; j < self->nicons; ++j) {
1733             guint x, y, t;
1734
1735             w = self->icons[j].width = data[i++];
1736             h = self->icons[j].height = data[i++];
1737
1738             if (w*h == 0) continue;
1739
1740             self->icons[j].data = g_new(RrPixel32, w * h);
1741             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1742                 if (x >= w) {
1743                     x = 0;
1744                     ++y;
1745                 }
1746                 self->icons[j].data[t] =
1747                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1748                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1749                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1750                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1751             }
1752             g_assert(i <= num);
1753         }
1754
1755         g_free(data);
1756     } else if (PROP_GETA32(self->window, kwm_win_icon,
1757                            kwm_win_icon, &data, &num)) {
1758         if (num == 2) {
1759             self->nicons++;
1760             self->icons = g_new(ObClientIcon, self->nicons);
1761             xerror_set_ignore(TRUE);
1762             if (!RrPixmapToRGBA(ob_rr_inst,
1763                                 data[0], data[1],
1764                                 &self->icons[self->nicons-1].width,
1765                                 &self->icons[self->nicons-1].height,
1766                                 &self->icons[self->nicons-1].data)) {
1767                 g_free(&self->icons[self->nicons-1]);
1768                 self->nicons--;
1769             }
1770             xerror_set_ignore(FALSE);
1771         }
1772         g_free(data);
1773     } else {
1774         XWMHints *hints;
1775
1776         if ((hints = XGetWMHints(ob_display, self->window))) {
1777             if (hints->flags & IconPixmapHint) {
1778                 self->nicons++;
1779                 self->icons = g_new(ObClientIcon, self->nicons);
1780                 xerror_set_ignore(TRUE);
1781                 if (!RrPixmapToRGBA(ob_rr_inst,
1782                                     hints->icon_pixmap,
1783                                     (hints->flags & IconMaskHint ?
1784                                      hints->icon_mask : None),
1785                                     &self->icons[self->nicons-1].width,
1786                                     &self->icons[self->nicons-1].height,
1787                                     &self->icons[self->nicons-1].data)){
1788                     g_free(&self->icons[self->nicons-1]);
1789                     self->nicons--;
1790                 }
1791                 xerror_set_ignore(FALSE);
1792             }
1793             XFree(hints);
1794         }
1795     }
1796
1797     if (self->frame)
1798         frame_adjust_icon(self->frame);
1799 }
1800
1801 static void client_change_state(ObClient *self)
1802 {
1803     gulong state[2];
1804     gulong netstate[11];
1805     guint num;
1806
1807     state[0] = self->wmstate;
1808     state[1] = None;
1809     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1810
1811     num = 0;
1812     if (self->modal)
1813         netstate[num++] = prop_atoms.net_wm_state_modal;
1814     if (self->shaded)
1815         netstate[num++] = prop_atoms.net_wm_state_shaded;
1816     if (self->iconic)
1817         netstate[num++] = prop_atoms.net_wm_state_hidden;
1818     if (self->skip_taskbar)
1819         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1820     if (self->skip_pager)
1821         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1822     if (self->fullscreen)
1823         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1824     if (self->max_vert)
1825         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1826     if (self->max_horz)
1827         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1828     if (self->above)
1829         netstate[num++] = prop_atoms.net_wm_state_above;
1830     if (self->below)
1831         netstate[num++] = prop_atoms.net_wm_state_below;
1832     if (self->undecorated)
1833         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1834     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1835
1836     client_calc_layer(self);
1837
1838     if (self->frame)
1839         frame_adjust_state(self->frame);
1840 }
1841
1842 ObClient *client_search_focus_tree(ObClient *self)
1843 {
1844     GSList *it;
1845     ObClient *ret;
1846
1847     for (it = self->transients; it; it = g_slist_next(it)) {
1848         if (client_focused(it->data)) return it->data;
1849         if ((ret = client_search_focus_tree(it->data))) return ret;
1850     }
1851     return NULL;
1852 }
1853
1854 ObClient *client_search_focus_tree_full(ObClient *self)
1855 {
1856     if (self->transient_for) {
1857         if (self->transient_for != OB_TRAN_GROUP) {
1858             return client_search_focus_tree_full(self->transient_for);
1859         } else {
1860             GSList *it;
1861             gboolean recursed = FALSE;
1862         
1863             for (it = self->group->members; it; it = g_slist_next(it))
1864                 if (!((ObClient*)it->data)->transient_for) {
1865                     ObClient *c;
1866                     if ((c = client_search_focus_tree_full(it->data)))
1867                         return c;
1868                     recursed = TRUE;
1869                 }
1870             if (recursed)
1871                 return NULL;
1872         }
1873     }
1874
1875     /* this function checks the whole tree, the client_search_focus_tree~
1876        does not, so we need to check this window */
1877     if (client_focused(self))
1878         return self;
1879     return client_search_focus_tree(self);
1880 }
1881
1882 static ObStackingLayer calc_layer(ObClient *self)
1883 {
1884     ObStackingLayer l;
1885
1886     if (self->fullscreen &&
1887         (client_focused(self) || client_search_focus_tree(self)))
1888         l = OB_STACKING_LAYER_FULLSCREEN;
1889     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1890         l = OB_STACKING_LAYER_DESKTOP;
1891     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1892         if (self->below) l = OB_STACKING_LAYER_NORMAL;
1893         else l = OB_STACKING_LAYER_ABOVE;
1894     }
1895     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1896     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1897     else l = OB_STACKING_LAYER_NORMAL;
1898
1899     return l;
1900 }
1901
1902 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1903                                         ObStackingLayer l, gboolean raised)
1904 {
1905     ObStackingLayer old, own;
1906     GSList *it;
1907
1908     old = self->layer;
1909     own = calc_layer(self);
1910     self->layer = l > own ? l : own;
1911
1912     for (it = self->transients; it; it = g_slist_next(it))
1913         client_calc_layer_recursive(it->data, orig,
1914                                     l, raised ? raised : l != old);
1915
1916     if (!raised && l != old)
1917         if (orig->frame) { /* only restack if the original window is managed */
1918             stacking_remove(CLIENT_AS_WINDOW(self));
1919             stacking_add(CLIENT_AS_WINDOW(self));
1920         }
1921 }
1922
1923 void client_calc_layer(ObClient *self)
1924 {
1925     ObStackingLayer l;
1926     ObClient *orig;
1927
1928     orig = self;
1929
1930     /* transients take on the layer of their parents */
1931     self = client_search_top_transient(self);
1932
1933     l = calc_layer(self);
1934
1935     client_calc_layer_recursive(self, orig, l, FALSE);
1936 }
1937
1938 gboolean client_should_show(ObClient *self)
1939 {
1940     if (self->iconic)
1941         return FALSE;
1942     if (client_normal(self) && screen_showing_desktop)
1943         return FALSE;
1944     /*
1945     if (self->transient_for) {
1946         if (self->transient_for != OB_TRAN_GROUP)
1947             return client_should_show(self->transient_for);
1948         else {
1949             GSList *it;
1950
1951             for (it = self->group->members; it; it = g_slist_next(it)) {
1952                 ObClient *c = it->data;
1953                 if (c != self && !c->transient_for) {
1954                     if (client_should_show(c))
1955                         return TRUE;
1956                 }
1957             }
1958         }
1959     }
1960     */
1961     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1962         return TRUE;
1963     
1964     return FALSE;
1965 }
1966
1967 static void client_showhide(ObClient *self)
1968 {
1969
1970     if (client_should_show(self))
1971         frame_show(self->frame);
1972     else
1973         frame_hide(self->frame);
1974 }
1975
1976 gboolean client_normal(ObClient *self) {
1977     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1978               self->type == OB_CLIENT_TYPE_DOCK ||
1979               self->type == OB_CLIENT_TYPE_SPLASH);
1980 }
1981
1982 static void client_apply_startup_state(ObClient *self)
1983 {
1984     /* these are in a carefully crafted order.. */
1985
1986     if (self->iconic) {
1987         self->iconic = FALSE;
1988         client_iconify(self, TRUE, FALSE);
1989     }
1990     if (self->fullscreen) {
1991         self->fullscreen = FALSE;
1992         client_fullscreen(self, TRUE, FALSE);
1993     }
1994     if (self->undecorated) {
1995         self->undecorated = FALSE;
1996         client_set_undecorated(self, TRUE);
1997     }
1998     if (self->shaded) {
1999         self->shaded = FALSE;
2000         client_shade(self, TRUE);
2001     }
2002     if (self->urgent)
2003         client_urgent_notify(self);
2004   
2005     if (self->max_vert && self->max_horz) {
2006         self->max_vert = self->max_horz = FALSE;
2007         client_maximize(self, TRUE, 0, FALSE);
2008     } else if (self->max_vert) {
2009         self->max_vert = FALSE;
2010         client_maximize(self, TRUE, 2, FALSE);
2011     } else if (self->max_horz) {
2012         self->max_horz = FALSE;
2013         client_maximize(self, TRUE, 1, FALSE);
2014     }
2015
2016     /* nothing to do for the other states:
2017        skip_taskbar
2018        skip_pager
2019        modal
2020        above
2021        below
2022     */
2023 }
2024
2025 void client_configure_full(ObClient *self, ObCorner anchor,
2026                            gint x, gint y, gint w, gint h,
2027                            gboolean user, gboolean final,
2028                            gboolean force_reply)
2029 {
2030     gint oldw, oldh;
2031     gboolean send_resize_client;
2032     gboolean moved = FALSE, resized = FALSE;
2033     guint fdecor = self->frame->decorations;
2034     gboolean fhorz = self->frame->max_horz;
2035
2036     /* make the frame recalculate its dimentions n shit without changing
2037        anything visible for real, this way the constraints below can work with
2038        the updated frame dimensions. */
2039     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2040
2041     /* gets the frame's position */
2042     frame_client_gravity(self->frame, &x, &y);
2043
2044     /* these positions are frame positions, not client positions */
2045
2046     /* set the size and position if fullscreen */
2047     if (self->fullscreen) {
2048         Rect *a;
2049         guint i;
2050
2051         i = client_monitor(self);
2052         a = screen_physical_area_monitor(i);
2053
2054         x = a->x;
2055         y = a->y;
2056         w = a->width;
2057         h = a->height;
2058
2059         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2060     } else {
2061         Rect *a;
2062
2063         a = screen_area_monitor(self->desktop, client_monitor(self));
2064
2065         /* set the size and position if maximized */
2066         if (self->max_horz) {
2067             x = a->x;
2068             w = a->width - self->frame->size.left - self->frame->size.right;
2069         }
2070         if (self->max_vert) {
2071             y = a->y;
2072             h = a->height - self->frame->size.top - self->frame->size.bottom;
2073         }
2074     }
2075
2076     /* gets the client's position */
2077     frame_frame_gravity(self->frame, &x, &y);
2078
2079     /* these override the above states! if you cant move you can't move! */
2080     if (user) {
2081         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2082             x = self->area.x;
2083             y = self->area.y;
2084         }
2085         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2086             w = self->area.width;
2087             h = self->area.height;
2088         }
2089     }
2090
2091     if (!(w == self->area.width && h == self->area.height)) {
2092         gint basew, baseh, minw, minh;
2093
2094         /* base size is substituted with min size if not specified */
2095         if (self->base_size.width || self->base_size.height) {
2096             basew = self->base_size.width;
2097             baseh = self->base_size.height;
2098         } else {
2099             basew = self->min_size.width;
2100             baseh = self->min_size.height;
2101         }
2102         /* min size is substituted with base size if not specified */
2103         if (self->min_size.width || self->min_size.height) {
2104             minw = self->min_size.width;
2105             minh = self->min_size.height;
2106         } else {
2107             minw = self->base_size.width;
2108             minh = self->base_size.height;
2109         }
2110
2111         /* if this is a user-requested resize, then check against min/max
2112            sizes */
2113
2114         /* smaller than min size or bigger than max size? */
2115         if (w > self->max_size.width) w = self->max_size.width;
2116         if (w < minw) w = minw;
2117         if (h > self->max_size.height) h = self->max_size.height;
2118         if (h < minh) h = minh;
2119
2120         w -= basew;
2121         h -= baseh;
2122
2123         /* keep to the increments */
2124         w /= self->size_inc.width;
2125         h /= self->size_inc.height;
2126
2127         /* you cannot resize to nothing */
2128         if (basew + w < 1) w = 1 - basew;
2129         if (baseh + h < 1) h = 1 - baseh;
2130   
2131         /* store the logical size */
2132         SIZE_SET(self->logical_size,
2133                  self->size_inc.width > 1 ? w : w + basew,
2134                  self->size_inc.height > 1 ? h : h + baseh);
2135
2136         w *= self->size_inc.width;
2137         h *= self->size_inc.height;
2138
2139         w += basew;
2140         h += baseh;
2141
2142         /* adjust the height to match the width for the aspect ratios.
2143            for this, min size is not substituted for base size ever. */
2144         w -= self->base_size.width;
2145         h -= self->base_size.height;
2146
2147         if (!self->fullscreen) {
2148             if (self->min_ratio)
2149                 if (h * self->min_ratio > w) {
2150                     h = (gint)(w / self->min_ratio);
2151
2152                     /* you cannot resize to nothing */
2153                     if (h < 1) {
2154                         h = 1;
2155                         w = (gint)(h * self->min_ratio);
2156                     }
2157                 }
2158             if (self->max_ratio)
2159                 if (h * self->max_ratio < w) {
2160                     h = (gint)(w / self->max_ratio);
2161
2162                     /* you cannot resize to nothing */
2163                     if (h < 1) {
2164                         h = 1;
2165                         w = (gint)(h * self->min_ratio);
2166                     }
2167                 }
2168         }
2169
2170         w += self->base_size.width;
2171         h += self->base_size.height;
2172     }
2173
2174     g_assert(w > 0);
2175     g_assert(h > 0);
2176
2177     switch (anchor) {
2178     case OB_CORNER_TOPLEFT:
2179         break;
2180     case OB_CORNER_TOPRIGHT:
2181         x -= w - self->area.width;
2182         break;
2183     case OB_CORNER_BOTTOMLEFT:
2184         y -= h - self->area.height;
2185         break;
2186     case OB_CORNER_BOTTOMRIGHT:
2187         x -= w - self->area.width;
2188         y -= h - self->area.height;
2189         break;
2190     }
2191
2192     moved = x != self->area.x || y != self->area.y;
2193     resized = w != self->area.width || h != self->area.height;
2194
2195     oldw = self->area.width;
2196     oldh = self->area.height;
2197     RECT_SET(self->area, x, y, w, h);
2198
2199     /* for app-requested resizes, always resize if 'resized' is true.
2200        for user-requested ones, only resize if final is true, or when
2201        resizing in redraw mode */
2202     send_resize_client = ((!user && resized) ||
2203                           (user && (final ||
2204                                     (resized && config_resize_redraw))));
2205
2206     /* if the client is enlarging, then resize the client before the frame */
2207     if (send_resize_client && user && (w > oldw || h > oldh))
2208         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2209
2210     /* move/resize the frame to match the request */
2211     if (self->frame) {
2212         if (self->decorations != fdecor || self->max_horz != fhorz)
2213             moved = resized = TRUE;
2214
2215         if (moved || resized)
2216             frame_adjust_area(self->frame, moved, resized, FALSE);
2217
2218         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2219         {
2220             XEvent event;
2221             event.type = ConfigureNotify;
2222             event.xconfigure.display = ob_display;
2223             event.xconfigure.event = self->window;
2224             event.xconfigure.window = self->window;
2225
2226             /* root window real coords */
2227             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2228                 self->border_width;
2229             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2230                 self->border_width;
2231             event.xconfigure.width = w;
2232             event.xconfigure.height = h;
2233             event.xconfigure.border_width = 0;
2234             event.xconfigure.above = self->frame->plate;
2235             event.xconfigure.override_redirect = FALSE;
2236             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2237                        FALSE, StructureNotifyMask, &event);
2238         }
2239     }
2240
2241     /* if the client is shrinking, then resize the frame before the client */
2242     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2243         XResizeWindow(ob_display, self->window, w, h);
2244
2245     XFlush(ob_display);
2246 }
2247
2248 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2249 {
2250     gint x, y, w, h;
2251
2252     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2253         self->fullscreen == fs) return;                   /* already done */
2254
2255     self->fullscreen = fs;
2256     client_change_state(self); /* change the state hints on the client,
2257                                   and adjust out layer/stacking */
2258
2259     if (fs) {
2260         if (savearea)
2261             self->pre_fullscreen_area = self->area;
2262
2263         /* these are not actually used cuz client_configure will set them
2264            as appropriate when the window is fullscreened */
2265         x = y = w = h = 0;
2266     } else {
2267         Rect *a;
2268
2269         if (self->pre_fullscreen_area.width > 0 &&
2270             self->pre_fullscreen_area.height > 0)
2271         {
2272             x = self->pre_fullscreen_area.x;
2273             y = self->pre_fullscreen_area.y;
2274             w = self->pre_fullscreen_area.width;
2275             h = self->pre_fullscreen_area.height;
2276             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2277         } else {
2278             /* pick some fallbacks... */
2279             a = screen_area_monitor(self->desktop, 0);
2280             x = a->x + a->width / 4;
2281             y = a->y + a->height / 4;
2282             w = a->width / 2;
2283             h = a->height / 2;
2284         }
2285     }
2286
2287     client_setup_decor_and_functions(self);
2288
2289     client_move_resize(self, x, y, w, h);
2290
2291     /* try focus us when we go into fullscreen mode */
2292     client_focus(self);
2293 }
2294
2295 static void client_iconify_recursive(ObClient *self,
2296                                      gboolean iconic, gboolean curdesk)
2297 {
2298     GSList *it;
2299     gboolean changed = FALSE;
2300
2301
2302     if (self->iconic != iconic) {
2303         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2304                  self->window);
2305
2306         self->iconic = iconic;
2307
2308         if (iconic) {
2309             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2310                 glong old;
2311
2312                 old = self->wmstate;
2313                 self->wmstate = IconicState;
2314                 if (old != self->wmstate)
2315                     PROP_MSG(self->window, kde_wm_change_state,
2316                              self->wmstate, 1, 0, 0);
2317
2318                 /* update the focus lists.. iconic windows go to the bottom of
2319                    the list, put the new iconic window at the 'top of the
2320                    bottom'. */
2321                 focus_order_to_top(self);
2322
2323                 changed = TRUE;
2324             }
2325         } else {
2326             glong old;
2327
2328             if (curdesk)
2329                 client_set_desktop(self, screen_desktop, FALSE);
2330
2331             old = self->wmstate;
2332             self->wmstate = self->shaded ? IconicState : NormalState;
2333             if (old != self->wmstate)
2334                 PROP_MSG(self->window, kde_wm_change_state,
2335                          self->wmstate, 1, 0, 0);
2336
2337             /* this puts it after the current focused window */
2338             focus_order_remove(self);
2339             focus_order_add_new(self);
2340
2341             changed = TRUE;
2342         }
2343     }
2344
2345     if (changed) {
2346         client_change_state(self);
2347         client_showhide(self);
2348         screen_update_areas();
2349     }
2350
2351     /* iconify all transients */
2352     for (it = self->transients; it; it = g_slist_next(it))
2353         if (it->data != self) client_iconify_recursive(it->data,
2354                                                        iconic, curdesk);
2355 }
2356
2357 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2358 {
2359     /* move up the transient chain as far as possible first */
2360     self = client_search_top_transient(self);
2361
2362     client_iconify_recursive(client_search_top_transient(self),
2363                              iconic, curdesk);
2364 }
2365
2366 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2367 {
2368     gint x, y, w, h;
2369      
2370     g_assert(dir == 0 || dir == 1 || dir == 2);
2371     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2372
2373     /* check if already done */
2374     if (max) {
2375         if (dir == 0 && self->max_horz && self->max_vert) return;
2376         if (dir == 1 && self->max_horz) return;
2377         if (dir == 2 && self->max_vert) return;
2378     } else {
2379         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2380         if (dir == 1 && !self->max_horz) return;
2381         if (dir == 2 && !self->max_vert) return;
2382     }
2383
2384     /* we just tell it to configure in the same place and client_configure
2385        worries about filling the screen with the window */
2386     x = self->area.x;
2387     y = self->area.y;
2388     w = self->area.width;
2389     h = self->area.height;
2390
2391     if (max) {
2392         if (savearea) {
2393             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2394                 RECT_SET(self->pre_max_area,
2395                          self->area.x, self->pre_max_area.y,
2396                          self->area.width, self->pre_max_area.height);
2397             }
2398             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2399                 RECT_SET(self->pre_max_area,
2400                          self->pre_max_area.x, self->area.y,
2401                          self->pre_max_area.width, self->area.height);
2402             }
2403         }
2404     } else {
2405         Rect *a;
2406
2407         a = screen_area_monitor(self->desktop, 0);
2408         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2409             if (self->pre_max_area.width > 0) {
2410                 x = self->pre_max_area.x;
2411                 w = self->pre_max_area.width;
2412
2413                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2414                          0, self->pre_max_area.height);
2415             } else {
2416                 /* pick some fallbacks... */
2417                 x = a->x + a->width / 4;
2418                 w = a->width / 2;
2419             }
2420         }
2421         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2422             if (self->pre_max_area.height > 0) {
2423                 y = self->pre_max_area.y;
2424                 h = self->pre_max_area.height;
2425
2426                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2427                          self->pre_max_area.width, 0);
2428             } else {
2429                 /* pick some fallbacks... */
2430                 y = a->y + a->height / 4;
2431                 h = a->height / 2;
2432             }
2433         }
2434     }
2435
2436     if (dir == 0 || dir == 1) /* horz */
2437         self->max_horz = max;
2438     if (dir == 0 || dir == 2) /* vert */
2439         self->max_vert = max;
2440
2441     client_change_state(self); /* change the state hints on the client */
2442
2443     client_setup_decor_and_functions(self);
2444
2445     client_move_resize(self, x, y, w, h);
2446 }
2447
2448 void client_shade(ObClient *self, gboolean shade)
2449 {
2450     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2451          shade) ||                         /* can't shade */
2452         self->shaded == shade) return;     /* already done */
2453
2454     /* when we're iconic, don't change the wmstate */
2455     if (!self->iconic) {
2456         glong old;
2457
2458         old = self->wmstate;
2459         self->wmstate = shade ? IconicState : NormalState;
2460         if (old != self->wmstate)
2461             PROP_MSG(self->window, kde_wm_change_state,
2462                      self->wmstate, 1, 0, 0);
2463     }
2464
2465     self->shaded = shade;
2466     client_change_state(self);
2467     /* resize the frame to just the titlebar */
2468     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2469 }
2470
2471 void client_close(ObClient *self)
2472 {
2473     XEvent ce;
2474
2475     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2476
2477     /* in the case that the client provides no means to requesting that it
2478        close, we just kill it */
2479     if (!self->delete_window)
2480         client_kill(self);
2481     
2482     /*
2483       XXX: itd be cool to do timeouts and shit here for killing the client's
2484       process off
2485       like... if the window is around after 5 seconds, then the close button
2486       turns a nice red, and if this function is called again, the client is
2487       explicitly killed.
2488     */
2489
2490     ce.xclient.type = ClientMessage;
2491     ce.xclient.message_type =  prop_atoms.wm_protocols;
2492     ce.xclient.display = ob_display;
2493     ce.xclient.window = self->window;
2494     ce.xclient.format = 32;
2495     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2496     ce.xclient.data.l[1] = event_lasttime;
2497     ce.xclient.data.l[2] = 0l;
2498     ce.xclient.data.l[3] = 0l;
2499     ce.xclient.data.l[4] = 0l;
2500     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2501 }
2502
2503 void client_kill(ObClient *self)
2504 {
2505     XKillClient(ob_display, self->window);
2506 }
2507
2508 void client_set_desktop_recursive(ObClient *self,
2509                                   guint target, gboolean donthide)
2510 {
2511     guint old;
2512     GSList *it;
2513
2514     if (target != self->desktop) {
2515
2516         ob_debug("Setting desktop %u\n", target+1);
2517
2518         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2519
2520         /* remove from the old desktop(s) */
2521         focus_order_remove(self);
2522
2523         old = self->desktop;
2524         self->desktop = target;
2525         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2526         /* the frame can display the current desktop state */
2527         frame_adjust_state(self->frame);
2528         /* 'move' the window to the new desktop */
2529         if (!donthide)
2530             client_showhide(self);
2531         /* raise if it was not already on the desktop */
2532         if (old != DESKTOP_ALL)
2533             client_raise(self);
2534         screen_update_areas();
2535
2536         /* add to the new desktop(s) */
2537         if (config_focus_new)
2538             focus_order_to_top(self);
2539         else
2540             focus_order_to_bottom(self);
2541     }
2542
2543     /* move all transients */
2544     for (it = self->transients; it; it = g_slist_next(it))
2545         if (it->data != self) client_set_desktop_recursive(it->data,
2546                                                            target, donthide);
2547 }
2548
2549 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2550 {
2551     client_set_desktop_recursive(client_search_top_transient(self),
2552                                  target, donthide);
2553 }
2554
2555 ObClient *client_search_modal_child(ObClient *self)
2556 {
2557     GSList *it;
2558     ObClient *ret;
2559   
2560     for (it = self->transients; it; it = g_slist_next(it)) {
2561         ObClient *c = it->data;
2562         if ((ret = client_search_modal_child(c))) return ret;
2563         if (c->modal) return c;
2564     }
2565     return NULL;
2566 }
2567
2568 gboolean client_validate(ObClient *self)
2569 {
2570     XEvent e; 
2571
2572     XSync(ob_display, FALSE); /* get all events on the server */
2573
2574     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2575         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2576         XPutBackEvent(ob_display, &e);
2577         return FALSE;
2578     }
2579
2580     return TRUE;
2581 }
2582
2583 void client_set_wm_state(ObClient *self, glong state)
2584 {
2585     if (state == self->wmstate) return; /* no change */
2586   
2587     switch (state) {
2588     case IconicState:
2589         client_iconify(self, TRUE, TRUE);
2590         break;
2591     case NormalState:
2592         client_iconify(self, FALSE, TRUE);
2593         break;
2594     }
2595 }
2596
2597 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2598 {
2599     gboolean shaded = self->shaded;
2600     gboolean fullscreen = self->fullscreen;
2601     gboolean undecorated = self->undecorated;
2602     gboolean max_horz = self->max_horz;
2603     gboolean max_vert = self->max_vert;
2604     gboolean modal = self->modal;
2605     gboolean iconic = self->iconic;
2606     gint i;
2607
2608     if (!(action == prop_atoms.net_wm_state_add ||
2609           action == prop_atoms.net_wm_state_remove ||
2610           action == prop_atoms.net_wm_state_toggle))
2611         /* an invalid action was passed to the client message, ignore it */
2612         return; 
2613
2614     for (i = 0; i < 2; ++i) {
2615         Atom state = i == 0 ? data1 : data2;
2616     
2617         if (!state) continue;
2618
2619         /* if toggling, then pick whether we're adding or removing */
2620         if (action == prop_atoms.net_wm_state_toggle) {
2621             if (state == prop_atoms.net_wm_state_modal)
2622                 action = modal ? prop_atoms.net_wm_state_remove :
2623                     prop_atoms.net_wm_state_add;
2624             else if (state == prop_atoms.net_wm_state_maximized_vert)
2625                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2626                     prop_atoms.net_wm_state_add;
2627             else if (state == prop_atoms.net_wm_state_maximized_horz)
2628                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2629                     prop_atoms.net_wm_state_add;
2630             else if (state == prop_atoms.net_wm_state_shaded)
2631                 action = shaded ? prop_atoms.net_wm_state_remove :
2632                     prop_atoms.net_wm_state_add;
2633             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2634                 action = self->skip_taskbar ?
2635                     prop_atoms.net_wm_state_remove :
2636                     prop_atoms.net_wm_state_add;
2637             else if (state == prop_atoms.net_wm_state_skip_pager)
2638                 action = self->skip_pager ?
2639                     prop_atoms.net_wm_state_remove :
2640                     prop_atoms.net_wm_state_add;
2641             else if (state == prop_atoms.net_wm_state_hidden)
2642                 action = self->iconic ?
2643                     prop_atoms.net_wm_state_remove :
2644                     prop_atoms.net_wm_state_add;
2645             else if (state == prop_atoms.net_wm_state_fullscreen)
2646                 action = fullscreen ?
2647                     prop_atoms.net_wm_state_remove :
2648                     prop_atoms.net_wm_state_add;
2649             else if (state == prop_atoms.net_wm_state_above)
2650                 action = self->above ? prop_atoms.net_wm_state_remove :
2651                     prop_atoms.net_wm_state_add;
2652             else if (state == prop_atoms.net_wm_state_below)
2653                 action = self->below ? prop_atoms.net_wm_state_remove :
2654                     prop_atoms.net_wm_state_add;
2655             else if (state == prop_atoms.ob_wm_state_undecorated)
2656                 action = undecorated ? prop_atoms.net_wm_state_remove :
2657                     prop_atoms.net_wm_state_add;
2658         }
2659     
2660         if (action == prop_atoms.net_wm_state_add) {
2661             if (state == prop_atoms.net_wm_state_modal) {
2662                 modal = TRUE;
2663             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2664                 max_vert = TRUE;
2665             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2666                 max_horz = TRUE;
2667             } else if (state == prop_atoms.net_wm_state_shaded) {
2668                 shaded = TRUE;
2669             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2670                 self->skip_taskbar = TRUE;
2671             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2672                 self->skip_pager = TRUE;
2673             } else if (state == prop_atoms.net_wm_state_hidden) {
2674                 iconic = TRUE;
2675             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2676                 fullscreen = TRUE;
2677             } else if (state == prop_atoms.net_wm_state_above) {
2678                 self->above = TRUE;
2679                 self->below = FALSE;
2680             } else if (state == prop_atoms.net_wm_state_below) {
2681                 self->above = FALSE;
2682                 self->below = TRUE;
2683             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2684                 undecorated = TRUE;
2685             }
2686
2687         } else { /* action == prop_atoms.net_wm_state_remove */
2688             if (state == prop_atoms.net_wm_state_modal) {
2689                 modal = FALSE;
2690             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2691                 max_vert = FALSE;
2692             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2693                 max_horz = FALSE;
2694             } else if (state == prop_atoms.net_wm_state_shaded) {
2695                 shaded = FALSE;
2696             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2697                 self->skip_taskbar = FALSE;
2698             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2699                 self->skip_pager = FALSE;
2700             } else if (state == prop_atoms.net_wm_state_hidden) {
2701                 iconic = FALSE;
2702             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2703                 fullscreen = FALSE;
2704             } else if (state == prop_atoms.net_wm_state_above) {
2705                 self->above = FALSE;
2706             } else if (state == prop_atoms.net_wm_state_below) {
2707                 self->below = FALSE;
2708             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2709                 undecorated = FALSE;
2710             }
2711         }
2712     }
2713     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2714         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2715             /* toggling both */
2716             if (max_horz == max_vert) { /* both going the same way */
2717                 client_maximize(self, max_horz, 0, TRUE);
2718             } else {
2719                 client_maximize(self, max_horz, 1, TRUE);
2720                 client_maximize(self, max_vert, 2, TRUE);
2721             }
2722         } else {
2723             /* toggling one */
2724             if (max_horz != self->max_horz)
2725                 client_maximize(self, max_horz, 1, TRUE);
2726             else
2727                 client_maximize(self, max_vert, 2, TRUE);
2728         }
2729     }
2730     /* change fullscreen state before shading, as it will affect if the window
2731        can shade or not */
2732     if (fullscreen != self->fullscreen)
2733         client_fullscreen(self, fullscreen, TRUE);
2734     if (shaded != self->shaded)
2735         client_shade(self, shaded);
2736     if (undecorated != self->undecorated)
2737         client_set_undecorated(self, undecorated);
2738     if (modal != self->modal) {
2739         self->modal = modal;
2740         /* when a window changes modality, then its stacking order with its
2741            transients needs to change */
2742         client_raise(self);
2743     }
2744     if (iconic != self->iconic)
2745         client_iconify(self, iconic, FALSE);
2746
2747     client_calc_layer(self);
2748     client_change_state(self); /* change the hint to reflect these changes */
2749 }
2750
2751 ObClient *client_focus_target(ObClient *self)
2752 {
2753     ObClient *child;
2754      
2755     /* if we have a modal child, then focus it, not us */
2756     child = client_search_modal_child(client_search_top_transient(self));
2757     if (child) return child;
2758     return self;
2759 }
2760
2761 gboolean client_can_focus(ObClient *self)
2762 {
2763     XEvent ev;
2764
2765     /* choose the correct target */
2766     self = client_focus_target(self);
2767
2768     if (!self->frame->visible)
2769         return FALSE;
2770
2771     if (!(self->can_focus || self->focus_notify))
2772         return FALSE;
2773
2774     /* do a check to see if the window has already been unmapped or destroyed
2775        do this intelligently while watching out for unmaps we've generated
2776        (ignore_unmaps > 0) */
2777     if (XCheckTypedWindowEvent(ob_display, self->window,
2778                                DestroyNotify, &ev)) {
2779         XPutBackEvent(ob_display, &ev);
2780         return FALSE;
2781     }
2782     while (XCheckTypedWindowEvent(ob_display, self->window,
2783                                   UnmapNotify, &ev)) {
2784         if (self->ignore_unmaps) {
2785             self->ignore_unmaps--;
2786         } else {
2787             XPutBackEvent(ob_display, &ev);
2788             return FALSE;
2789         }
2790     }
2791
2792     return TRUE;
2793 }
2794
2795 gboolean client_focus(ObClient *self)
2796 {
2797     /* choose the correct target */
2798     self = client_focus_target(self);
2799
2800     if (!client_can_focus(self)) {
2801         if (!self->frame->visible) {
2802             /* update the focus lists */
2803             focus_order_to_top(self);
2804         }
2805         return FALSE;
2806     }
2807
2808     if (self->can_focus) {
2809         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2810            I choose to use it always, hopefully to find errors quicker, if any
2811            are left. (I hate X. I hate focus events.)
2812            
2813            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2814            #799. So now it is RevertToNone again.
2815         */
2816         XSetInputFocus(ob_display, self->window, RevertToNone,
2817                        event_lasttime);
2818     }
2819
2820     if (self->focus_notify) {
2821         XEvent ce;
2822         ce.xclient.type = ClientMessage;
2823         ce.xclient.message_type = prop_atoms.wm_protocols;
2824         ce.xclient.display = ob_display;
2825         ce.xclient.window = self->window;
2826         ce.xclient.format = 32;
2827         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2828         ce.xclient.data.l[1] = event_lasttime;
2829         ce.xclient.data.l[2] = 0l;
2830         ce.xclient.data.l[3] = 0l;
2831         ce.xclient.data.l[4] = 0l;
2832         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2833     }
2834
2835 #ifdef DEBUG_FOCUS
2836     ob_debug("%sively focusing %lx at %d\n",
2837              (self->can_focus ? "act" : "pass"),
2838              self->window, (gint) event_lasttime);
2839 #endif
2840
2841     /* Cause the FocusIn to come back to us. Important for desktop switches,
2842        since otherwise we'll have no FocusIn on the queue and send it off to
2843        the focus_backup. */
2844     XSync(ob_display, FALSE);
2845     return TRUE;
2846 }
2847
2848 /* Used when the current client is closed, focus_last will then prevent
2849  * focus from going to the mouse pointer */
2850 void client_unfocus(ObClient *self)
2851 {
2852     if (focus_client == self) {
2853 #ifdef DEBUG_FOCUS
2854         ob_debug("client_unfocus for %lx\n", self->window);
2855 #endif
2856         focus_fallback(OB_FOCUS_FALLBACK_CLOSED);
2857     }
2858 }
2859
2860 void client_activate(ObClient *self, gboolean here)
2861 {
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 }