]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
dont use the keepBorder option for mwm hinted windows after all, but do honor the...
[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, but keep the border if that's
1307                    specified */
1308                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1309                     self->decorations = OB_FRAME_DECOR_BORDER;
1310                 else
1311                     self->decorations = 0;
1312         }
1313     }
1314
1315     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1316         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1317             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1318                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1319             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1320                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1321             /* dont let mwm hints kill any buttons
1322                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1323                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1324                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1325                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1326             */
1327             /* dont let mwm hints kill the close button
1328                if (! (self->mwmhints.functions & MwmFunc_Close))
1329                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1330         }
1331     }
1332
1333     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1334         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1335     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1336         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1337     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1338         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1339
1340     /* can't maximize without moving/resizing */
1341     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1342           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1343           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1344         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1345         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1346     }
1347
1348     /* kill the handle on fully maxed windows */
1349     if (self->max_vert && self->max_horz)
1350         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1351
1352     /* finally, the user can have requested no decorations, which overrides
1353        everything (but doesnt give it a border if it doesnt have one) */
1354     if (self->undecorated) {
1355         if (config_theme_keepborder)
1356             self->decorations &= OB_FRAME_DECOR_BORDER;
1357         else
1358             self->decorations = 0;
1359     }
1360
1361     /* if we don't have a titlebar, then we cannot shade! */
1362     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1363         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1364
1365     /* now we need to check against rules for the client's current state */
1366     if (self->fullscreen) {
1367         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1368                             OB_CLIENT_FUNC_FULLSCREEN |
1369                             OB_CLIENT_FUNC_ICONIFY);
1370         self->decorations = 0;
1371     }
1372
1373     client_change_allowed_actions(self);
1374
1375     if (self->frame) {
1376         /* adjust the client's decorations, etc. */
1377         client_reconfigure(self);
1378     }
1379 }
1380
1381 static void client_change_allowed_actions(ObClient *self)
1382 {
1383     gulong actions[9];
1384     gint num = 0;
1385
1386     /* desktop windows are kept on all desktops */
1387     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1388         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1389
1390     if (self->functions & OB_CLIENT_FUNC_SHADE)
1391         actions[num++] = prop_atoms.net_wm_action_shade;
1392     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1393         actions[num++] = prop_atoms.net_wm_action_close;
1394     if (self->functions & OB_CLIENT_FUNC_MOVE)
1395         actions[num++] = prop_atoms.net_wm_action_move;
1396     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1397         actions[num++] = prop_atoms.net_wm_action_minimize;
1398     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1399         actions[num++] = prop_atoms.net_wm_action_resize;
1400     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1401         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1402     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1403         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1404         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1405     }
1406
1407     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1408
1409     /* make sure the window isn't breaking any rules now */
1410
1411     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1412         if (self->frame) client_shade(self, FALSE);
1413         else self->shaded = FALSE;
1414     }
1415     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1416         if (self->frame) client_iconify(self, FALSE, TRUE);
1417         else self->iconic = FALSE;
1418     }
1419     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1420         if (self->frame) client_fullscreen(self, FALSE, TRUE);
1421         else self->fullscreen = FALSE;
1422     }
1423     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1424                                                          self->max_vert)) {
1425         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1426         else self->max_vert = self->max_horz = FALSE;
1427     }
1428 }
1429
1430 void client_reconfigure(ObClient *self)
1431 {
1432     /* by making this pass FALSE for user, we avoid the emacs event storm where
1433        every configurenotify causes an update in its normal hints, i think this
1434        is generally what we want anyways... */
1435     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1436                      self->area.width, self->area.height, FALSE, TRUE);
1437 }
1438
1439 void client_update_wmhints(ObClient *self)
1440 {
1441     XWMHints *hints;
1442     gboolean ur = FALSE;
1443     GSList *it;
1444
1445     /* assume a window takes input if it doesnt specify */
1446     self->can_focus = TRUE;
1447   
1448     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1449         if (hints->flags & InputHint)
1450             self->can_focus = hints->input;
1451
1452         /* only do this when first managing the window *AND* when we aren't
1453            starting up! */
1454         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1455             if (hints->flags & StateHint)
1456                 self->iconic = hints->initial_state == IconicState;
1457
1458         if (hints->flags & XUrgencyHint)
1459             ur = TRUE;
1460
1461         if (!(hints->flags & WindowGroupHint))
1462             hints->window_group = None;
1463
1464         /* did the group state change? */
1465         if (hints->window_group !=
1466             (self->group ? self->group->leader : None)) {
1467             /* remove from the old group if there was one */
1468             if (self->group != NULL) {
1469                 /* remove transients of the group */
1470                 for (it = self->group->members; it; it = g_slist_next(it))
1471                     self->transients = g_slist_remove(self->transients,
1472                                                       it->data);
1473
1474                 /* remove myself from parents in the group */
1475                 if (self->transient_for == OB_TRAN_GROUP) {
1476                     for (it = self->group->members; it;
1477                          it = g_slist_next(it))
1478                     {
1479                         ObClient *c = it->data;
1480
1481                         if (c != self && !c->transient_for)
1482                             c->transients = g_slist_remove(c->transients,
1483                                                            self);
1484                     }
1485                 }
1486
1487                 group_remove(self->group, self);
1488                 self->group = NULL;
1489             }
1490             if (hints->window_group != None) {
1491                 self->group = group_add(hints->window_group, self);
1492
1493                 /* i can only have transients from the group if i am not
1494                    transient myself */
1495                 if (!self->transient_for) {
1496                     /* add other transients of the group that are already
1497                        set up */
1498                     for (it = self->group->members; it;
1499                          it = g_slist_next(it))
1500                     {
1501                         ObClient *c = it->data;
1502                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1503                             self->transients =
1504                                 g_slist_append(self->transients, c);
1505                     }
1506                 }
1507             }
1508
1509             /* because the self->transient flag wont change from this call,
1510                we don't need to update the window's type and such, only its
1511                transient_for, and the transients lists of other windows in
1512                the group may be affected */
1513             client_update_transient_for(self);
1514         }
1515
1516         /* the WM_HINTS can contain an icon */
1517         client_update_icons(self);
1518
1519         XFree(hints);
1520     }
1521
1522     if (ur != self->urgent) {
1523         self->urgent = ur;
1524         /* fire the urgent callback if we're mapped, otherwise, wait until
1525            after we're mapped */
1526         if (self->frame)
1527             client_urgent_notify(self);
1528     }
1529 }
1530
1531 void client_update_title(ObClient *self)
1532 {
1533     GList *it;
1534     guint32 nums;
1535     guint i;
1536     gchar *data = NULL;
1537     gboolean read_title;
1538     gchar *old_title;
1539
1540     old_title = self->title;
1541      
1542     /* try netwm */
1543     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1544         /* try old x stuff */
1545         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1546               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1547             // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1548             if (self->transient) {
1549                 data = g_strdup("");
1550                 goto no_number;
1551             } else
1552                 data = g_strdup("Unnamed Window");
1553         }
1554     }
1555
1556     if (config_title_number) {
1557
1558         /* did the title change? then reset the title_count */
1559         if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1560             self->title_count = 1;
1561
1562         /* look for duplicates and append a number */
1563         nums = 0;
1564         for (it = client_list; it; it = g_list_next(it))
1565             if (it->data != self) {
1566                 ObClient *c = it->data;
1567                 if (0 == strncmp(c->title, data, strlen(data)))
1568                     nums |= 1 << c->title_count;
1569             }
1570         /* find first free number */
1571         for (i = 1; i <= 32; ++i)
1572             if (!(nums & (1 << i))) {
1573                 if (self->title_count == 1 || i == 1)
1574                     self->title_count = i;
1575                 break;
1576             }
1577         /* dont display the number for the first window */
1578         if (self->title_count > 1) {
1579             gchar *ndata;
1580             ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1581             g_free(data);
1582             data = ndata;
1583         }
1584     } else
1585         self->title_count = 1;
1586
1587 no_number:
1588     PROP_SETS(self->window, net_wm_visible_name, data);
1589     self->title = data;
1590
1591     if (self->frame)
1592         frame_adjust_title(self->frame);
1593
1594     g_free(old_title);
1595
1596     /* update the icon title */
1597     data = NULL;
1598     g_free(self->icon_title);
1599
1600     read_title = TRUE;
1601     /* try netwm */
1602     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1603         /* try old x stuff */
1604         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data)
1605               || PROP_GETS(self->window, wm_icon_name, utf8, &data))) {
1606             data = g_strdup(self->title);
1607             read_title = FALSE;
1608         }
1609
1610     /* append the title count, dont display the number for the first window.
1611      * We don't need to check for config_title_number here since title_count
1612      * is not set above 1 then. */
1613     if (read_title && self->title_count > 1) {
1614         gchar *vdata, *ndata;
1615         ndata = g_strdup_printf(" - [%u]", self->title_count);
1616         vdata = g_strconcat(data, ndata, NULL);
1617         g_free(ndata);
1618         g_free(data);
1619         data = vdata;
1620     }
1621
1622     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1623
1624     self->icon_title = data;
1625 }
1626
1627 void client_update_class(ObClient *self)
1628 {
1629     gchar **data;
1630     gchar *s;
1631
1632     if (self->name) g_free(self->name);
1633     if (self->class) g_free(self->class);
1634     if (self->role) g_free(self->role);
1635
1636     self->name = self->class = self->role = NULL;
1637
1638     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1639         if (data[0]) {
1640             self->name = g_strdup(data[0]);
1641             if (data[1])
1642                 self->class = g_strdup(data[1]);
1643         }
1644         g_strfreev(data);     
1645     }
1646
1647     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1648         self->role = s;
1649
1650     if (self->name == NULL) self->name = g_strdup("");
1651     if (self->class == NULL) self->class = g_strdup("");
1652     if (self->role == NULL) self->role = g_strdup("");
1653 }
1654
1655 void client_update_strut(ObClient *self)
1656 {
1657     guint num;
1658     guint32 *data;
1659     gboolean got = FALSE;
1660     StrutPartial strut;
1661
1662     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1663                     &data, &num)) {
1664         if (num == 12) {
1665             got = TRUE;
1666             STRUT_PARTIAL_SET(strut,
1667                               data[0], data[2], data[1], data[3],
1668                               data[4], data[5], data[8], data[9],
1669                               data[6], data[7], data[10], data[11]);
1670         }
1671         g_free(data);
1672     }
1673
1674     if (!got &&
1675         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1676         if (num == 4) {
1677             const Rect *a;
1678
1679             got = TRUE;
1680
1681             /* use the screen's width/height */
1682             a = screen_physical_area();
1683
1684             STRUT_PARTIAL_SET(strut,
1685                               data[0], data[2], data[1], data[3],
1686                               a->y, a->y + a->height - 1,
1687                               a->x, a->x + a->width - 1,
1688                               a->y, a->y + a->height - 1,
1689                               a->x, a->x + a->width - 1);
1690         }
1691         g_free(data);
1692     }
1693
1694     if (!got)
1695         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1696                           0, 0, 0, 0, 0, 0, 0, 0);
1697
1698     if (!STRUT_EQUAL(strut, self->strut)) {
1699         self->strut = strut;
1700
1701         /* updating here is pointless while we're being mapped cuz we're not in
1702            the client list yet */
1703         if (self->frame)
1704             screen_update_areas();
1705     }
1706 }
1707
1708 void client_update_icons(ObClient *self)
1709 {
1710     guint num;
1711     guint32 *data;
1712     guint w, h, i, j;
1713
1714     for (i = 0; i < self->nicons; ++i)
1715         g_free(self->icons[i].data);
1716     if (self->nicons > 0)
1717         g_free(self->icons);
1718     self->nicons = 0;
1719
1720     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1721         /* figure out how many valid icons are in here */
1722         i = 0;
1723         while (num - i > 2) {
1724             w = data[i++];
1725             h = data[i++];
1726             i += w * h;
1727             if (i > num || w*h == 0) break;
1728             ++self->nicons;
1729         }
1730
1731         self->icons = g_new(ObClientIcon, self->nicons);
1732     
1733         /* store the icons */
1734         i = 0;
1735         for (j = 0; j < self->nicons; ++j) {
1736             guint x, y, t;
1737
1738             w = self->icons[j].width = data[i++];
1739             h = self->icons[j].height = data[i++];
1740
1741             if (w*h == 0) continue;
1742
1743             self->icons[j].data = g_new(RrPixel32, w * h);
1744             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1745                 if (x >= w) {
1746                     x = 0;
1747                     ++y;
1748                 }
1749                 self->icons[j].data[t] =
1750                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1751                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1752                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1753                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1754             }
1755             g_assert(i <= num);
1756         }
1757
1758         g_free(data);
1759     } else if (PROP_GETA32(self->window, kwm_win_icon,
1760                            kwm_win_icon, &data, &num)) {
1761         if (num == 2) {
1762             self->nicons++;
1763             self->icons = g_new(ObClientIcon, self->nicons);
1764             xerror_set_ignore(TRUE);
1765             if (!RrPixmapToRGBA(ob_rr_inst,
1766                                 data[0], data[1],
1767                                 &self->icons[self->nicons-1].width,
1768                                 &self->icons[self->nicons-1].height,
1769                                 &self->icons[self->nicons-1].data)) {
1770                 g_free(&self->icons[self->nicons-1]);
1771                 self->nicons--;
1772             }
1773             xerror_set_ignore(FALSE);
1774         }
1775         g_free(data);
1776     } else {
1777         XWMHints *hints;
1778
1779         if ((hints = XGetWMHints(ob_display, self->window))) {
1780             if (hints->flags & IconPixmapHint) {
1781                 self->nicons++;
1782                 self->icons = g_new(ObClientIcon, self->nicons);
1783                 xerror_set_ignore(TRUE);
1784                 if (!RrPixmapToRGBA(ob_rr_inst,
1785                                     hints->icon_pixmap,
1786                                     (hints->flags & IconMaskHint ?
1787                                      hints->icon_mask : None),
1788                                     &self->icons[self->nicons-1].width,
1789                                     &self->icons[self->nicons-1].height,
1790                                     &self->icons[self->nicons-1].data)){
1791                     g_free(&self->icons[self->nicons-1]);
1792                     self->nicons--;
1793                 }
1794                 xerror_set_ignore(FALSE);
1795             }
1796             XFree(hints);
1797         }
1798     }
1799
1800     if (self->frame)
1801         frame_adjust_icon(self->frame);
1802 }
1803
1804 static void client_change_state(ObClient *self)
1805 {
1806     gulong state[2];
1807     gulong netstate[11];
1808     guint num;
1809
1810     state[0] = self->wmstate;
1811     state[1] = None;
1812     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1813
1814     num = 0;
1815     if (self->modal)
1816         netstate[num++] = prop_atoms.net_wm_state_modal;
1817     if (self->shaded)
1818         netstate[num++] = prop_atoms.net_wm_state_shaded;
1819     if (self->iconic)
1820         netstate[num++] = prop_atoms.net_wm_state_hidden;
1821     if (self->skip_taskbar)
1822         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1823     if (self->skip_pager)
1824         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1825     if (self->fullscreen)
1826         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1827     if (self->max_vert)
1828         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1829     if (self->max_horz)
1830         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1831     if (self->above)
1832         netstate[num++] = prop_atoms.net_wm_state_above;
1833     if (self->below)
1834         netstate[num++] = prop_atoms.net_wm_state_below;
1835     if (self->undecorated)
1836         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1837     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1838
1839     client_calc_layer(self);
1840
1841     if (self->frame)
1842         frame_adjust_state(self->frame);
1843 }
1844
1845 ObClient *client_search_focus_tree(ObClient *self)
1846 {
1847     GSList *it;
1848     ObClient *ret;
1849
1850     for (it = self->transients; it; it = g_slist_next(it)) {
1851         if (client_focused(it->data)) return it->data;
1852         if ((ret = client_search_focus_tree(it->data))) return ret;
1853     }
1854     return NULL;
1855 }
1856
1857 ObClient *client_search_focus_tree_full(ObClient *self)
1858 {
1859     if (self->transient_for) {
1860         if (self->transient_for != OB_TRAN_GROUP) {
1861             return client_search_focus_tree_full(self->transient_for);
1862         } else {
1863             GSList *it;
1864             gboolean recursed = FALSE;
1865         
1866             for (it = self->group->members; it; it = g_slist_next(it))
1867                 if (!((ObClient*)it->data)->transient_for) {
1868                     ObClient *c;
1869                     if ((c = client_search_focus_tree_full(it->data)))
1870                         return c;
1871                     recursed = TRUE;
1872                 }
1873             if (recursed)
1874                 return NULL;
1875         }
1876     }
1877
1878     /* this function checks the whole tree, the client_search_focus_tree~
1879        does not, so we need to check this window */
1880     if (client_focused(self))
1881         return self;
1882     return client_search_focus_tree(self);
1883 }
1884
1885 static ObStackingLayer calc_layer(ObClient *self)
1886 {
1887     ObStackingLayer l;
1888
1889     if (self->fullscreen &&
1890         (client_focused(self) || client_search_focus_tree(self)))
1891         l = OB_STACKING_LAYER_FULLSCREEN;
1892     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1893         l = OB_STACKING_LAYER_DESKTOP;
1894     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1895         if (self->below) l = OB_STACKING_LAYER_NORMAL;
1896         else l = OB_STACKING_LAYER_ABOVE;
1897     }
1898     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1899     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1900     else l = OB_STACKING_LAYER_NORMAL;
1901
1902     return l;
1903 }
1904
1905 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1906                                         ObStackingLayer l, gboolean raised)
1907 {
1908     ObStackingLayer old, own;
1909     GSList *it;
1910
1911     old = self->layer;
1912     own = calc_layer(self);
1913     self->layer = l > own ? l : own;
1914
1915     for (it = self->transients; it; it = g_slist_next(it))
1916         client_calc_layer_recursive(it->data, orig,
1917                                     l, raised ? raised : l != old);
1918
1919     if (!raised && l != old)
1920         if (orig->frame) { /* only restack if the original window is managed */
1921             stacking_remove(CLIENT_AS_WINDOW(self));
1922             stacking_add(CLIENT_AS_WINDOW(self));
1923         }
1924 }
1925
1926 void client_calc_layer(ObClient *self)
1927 {
1928     ObStackingLayer l;
1929     ObClient *orig;
1930
1931     orig = self;
1932
1933     /* transients take on the layer of their parents */
1934     self = client_search_top_transient(self);
1935
1936     l = calc_layer(self);
1937
1938     client_calc_layer_recursive(self, orig, l, FALSE);
1939 }
1940
1941 gboolean client_should_show(ObClient *self)
1942 {
1943     if (self->iconic)
1944         return FALSE;
1945     if (client_normal(self) && screen_showing_desktop)
1946         return FALSE;
1947     /*
1948     if (self->transient_for) {
1949         if (self->transient_for != OB_TRAN_GROUP)
1950             return client_should_show(self->transient_for);
1951         else {
1952             GSList *it;
1953
1954             for (it = self->group->members; it; it = g_slist_next(it)) {
1955                 ObClient *c = it->data;
1956                 if (c != self && !c->transient_for) {
1957                     if (client_should_show(c))
1958                         return TRUE;
1959                 }
1960             }
1961         }
1962     }
1963     */
1964     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1965         return TRUE;
1966     
1967     return FALSE;
1968 }
1969
1970 static void client_showhide(ObClient *self)
1971 {
1972
1973     if (client_should_show(self))
1974         frame_show(self->frame);
1975     else
1976         frame_hide(self->frame);
1977 }
1978
1979 gboolean client_normal(ObClient *self) {
1980     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1981               self->type == OB_CLIENT_TYPE_DOCK ||
1982               self->type == OB_CLIENT_TYPE_SPLASH);
1983 }
1984
1985 static void client_apply_startup_state(ObClient *self)
1986 {
1987     /* these are in a carefully crafted order.. */
1988
1989     if (self->iconic) {
1990         self->iconic = FALSE;
1991         client_iconify(self, TRUE, FALSE);
1992     }
1993     if (self->fullscreen) {
1994         self->fullscreen = FALSE;
1995         client_fullscreen(self, TRUE, FALSE);
1996     }
1997     if (self->undecorated) {
1998         self->undecorated = FALSE;
1999         client_set_undecorated(self, TRUE);
2000     }
2001     if (self->shaded) {
2002         self->shaded = FALSE;
2003         client_shade(self, TRUE);
2004     }
2005     if (self->urgent)
2006         client_urgent_notify(self);
2007   
2008     if (self->max_vert && self->max_horz) {
2009         self->max_vert = self->max_horz = FALSE;
2010         client_maximize(self, TRUE, 0, FALSE);
2011     } else if (self->max_vert) {
2012         self->max_vert = FALSE;
2013         client_maximize(self, TRUE, 2, FALSE);
2014     } else if (self->max_horz) {
2015         self->max_horz = FALSE;
2016         client_maximize(self, TRUE, 1, FALSE);
2017     }
2018
2019     /* nothing to do for the other states:
2020        skip_taskbar
2021        skip_pager
2022        modal
2023        above
2024        below
2025     */
2026 }
2027
2028 void client_configure_full(ObClient *self, ObCorner anchor,
2029                            gint x, gint y, gint w, gint h,
2030                            gboolean user, gboolean final,
2031                            gboolean force_reply)
2032 {
2033     gint oldw, oldh;
2034     gboolean send_resize_client;
2035     gboolean moved = FALSE, resized = FALSE;
2036     guint fdecor = self->frame->decorations;
2037     gboolean fhorz = self->frame->max_horz;
2038
2039     /* make the frame recalculate its dimentions n shit without changing
2040        anything visible for real, this way the constraints below can work with
2041        the updated frame dimensions. */
2042     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2043
2044     /* gets the frame's position */
2045     frame_client_gravity(self->frame, &x, &y);
2046
2047     /* these positions are frame positions, not client positions */
2048
2049     /* set the size and position if fullscreen */
2050     if (self->fullscreen) {
2051         Rect *a;
2052         guint i;
2053
2054         i = client_monitor(self);
2055         a = screen_physical_area_monitor(i);
2056
2057         x = a->x;
2058         y = a->y;
2059         w = a->width;
2060         h = a->height;
2061
2062         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2063     } else {
2064         Rect *a;
2065
2066         a = screen_area_monitor(self->desktop, client_monitor(self));
2067
2068         /* set the size and position if maximized */
2069         if (self->max_horz) {
2070             x = a->x;
2071             w = a->width - self->frame->size.left - self->frame->size.right;
2072         }
2073         if (self->max_vert) {
2074             y = a->y;
2075             h = a->height - self->frame->size.top - self->frame->size.bottom;
2076         }
2077     }
2078
2079     /* gets the client's position */
2080     frame_frame_gravity(self->frame, &x, &y);
2081
2082     /* these override the above states! if you cant move you can't move! */
2083     if (user) {
2084         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2085             x = self->area.x;
2086             y = self->area.y;
2087         }
2088         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2089             w = self->area.width;
2090             h = self->area.height;
2091         }
2092     }
2093
2094     if (!(w == self->area.width && h == self->area.height)) {
2095         gint basew, baseh, minw, minh;
2096
2097         /* base size is substituted with min size if not specified */
2098         if (self->base_size.width || self->base_size.height) {
2099             basew = self->base_size.width;
2100             baseh = self->base_size.height;
2101         } else {
2102             basew = self->min_size.width;
2103             baseh = self->min_size.height;
2104         }
2105         /* min size is substituted with base size if not specified */
2106         if (self->min_size.width || self->min_size.height) {
2107             minw = self->min_size.width;
2108             minh = self->min_size.height;
2109         } else {
2110             minw = self->base_size.width;
2111             minh = self->base_size.height;
2112         }
2113
2114         /* if this is a user-requested resize, then check against min/max
2115            sizes */
2116
2117         /* smaller than min size or bigger than max size? */
2118         if (w > self->max_size.width) w = self->max_size.width;
2119         if (w < minw) w = minw;
2120         if (h > self->max_size.height) h = self->max_size.height;
2121         if (h < minh) h = minh;
2122
2123         w -= basew;
2124         h -= baseh;
2125
2126         /* keep to the increments */
2127         w /= self->size_inc.width;
2128         h /= self->size_inc.height;
2129
2130         /* you cannot resize to nothing */
2131         if (basew + w < 1) w = 1 - basew;
2132         if (baseh + h < 1) h = 1 - baseh;
2133   
2134         /* store the logical size */
2135         SIZE_SET(self->logical_size,
2136                  self->size_inc.width > 1 ? w : w + basew,
2137                  self->size_inc.height > 1 ? h : h + baseh);
2138
2139         w *= self->size_inc.width;
2140         h *= self->size_inc.height;
2141
2142         w += basew;
2143         h += baseh;
2144
2145         /* adjust the height to match the width for the aspect ratios.
2146            for this, min size is not substituted for base size ever. */
2147         w -= self->base_size.width;
2148         h -= self->base_size.height;
2149
2150         if (!self->fullscreen) {
2151             if (self->min_ratio)
2152                 if (h * self->min_ratio > w) {
2153                     h = (gint)(w / self->min_ratio);
2154
2155                     /* you cannot resize to nothing */
2156                     if (h < 1) {
2157                         h = 1;
2158                         w = (gint)(h * self->min_ratio);
2159                     }
2160                 }
2161             if (self->max_ratio)
2162                 if (h * self->max_ratio < w) {
2163                     h = (gint)(w / self->max_ratio);
2164
2165                     /* you cannot resize to nothing */
2166                     if (h < 1) {
2167                         h = 1;
2168                         w = (gint)(h * self->min_ratio);
2169                     }
2170                 }
2171         }
2172
2173         w += self->base_size.width;
2174         h += self->base_size.height;
2175     }
2176
2177     g_assert(w > 0);
2178     g_assert(h > 0);
2179
2180     switch (anchor) {
2181     case OB_CORNER_TOPLEFT:
2182         break;
2183     case OB_CORNER_TOPRIGHT:
2184         x -= w - self->area.width;
2185         break;
2186     case OB_CORNER_BOTTOMLEFT:
2187         y -= h - self->area.height;
2188         break;
2189     case OB_CORNER_BOTTOMRIGHT:
2190         x -= w - self->area.width;
2191         y -= h - self->area.height;
2192         break;
2193     }
2194
2195     moved = x != self->area.x || y != self->area.y;
2196     resized = w != self->area.width || h != self->area.height;
2197
2198     oldw = self->area.width;
2199     oldh = self->area.height;
2200     RECT_SET(self->area, x, y, w, h);
2201
2202     /* for app-requested resizes, always resize if 'resized' is true.
2203        for user-requested ones, only resize if final is true, or when
2204        resizing in redraw mode */
2205     send_resize_client = ((!user && resized) ||
2206                           (user && (final ||
2207                                     (resized && config_resize_redraw))));
2208
2209     /* if the client is enlarging, then resize the client before the frame */
2210     if (send_resize_client && user && (w > oldw || h > oldh))
2211         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2212
2213     /* move/resize the frame to match the request */
2214     if (self->frame) {
2215         if (self->decorations != fdecor || self->max_horz != fhorz)
2216             moved = resized = TRUE;
2217
2218         if (moved || resized)
2219             frame_adjust_area(self->frame, moved, resized, FALSE);
2220
2221         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2222         {
2223             XEvent event;
2224             event.type = ConfigureNotify;
2225             event.xconfigure.display = ob_display;
2226             event.xconfigure.event = self->window;
2227             event.xconfigure.window = self->window;
2228
2229             /* root window real coords */
2230             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2231                 self->border_width;
2232             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2233                 self->border_width;
2234             event.xconfigure.width = w;
2235             event.xconfigure.height = h;
2236             event.xconfigure.border_width = 0;
2237             event.xconfigure.above = self->frame->plate;
2238             event.xconfigure.override_redirect = FALSE;
2239             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2240                        FALSE, StructureNotifyMask, &event);
2241         }
2242     }
2243
2244     /* if the client is shrinking, then resize the frame before the client */
2245     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2246         XResizeWindow(ob_display, self->window, w, h);
2247
2248     XFlush(ob_display);
2249 }
2250
2251 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2252 {
2253     gint x, y, w, h;
2254
2255     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2256         self->fullscreen == fs) return;                   /* already done */
2257
2258     self->fullscreen = fs;
2259     client_change_state(self); /* change the state hints on the client,
2260                                   and adjust out layer/stacking */
2261
2262     if (fs) {
2263         if (savearea)
2264             self->pre_fullscreen_area = self->area;
2265
2266         /* these are not actually used cuz client_configure will set them
2267            as appropriate when the window is fullscreened */
2268         x = y = w = h = 0;
2269     } else {
2270         Rect *a;
2271
2272         if (self->pre_fullscreen_area.width > 0 &&
2273             self->pre_fullscreen_area.height > 0)
2274         {
2275             x = self->pre_fullscreen_area.x;
2276             y = self->pre_fullscreen_area.y;
2277             w = self->pre_fullscreen_area.width;
2278             h = self->pre_fullscreen_area.height;
2279             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2280         } else {
2281             /* pick some fallbacks... */
2282             a = screen_area_monitor(self->desktop, 0);
2283             x = a->x + a->width / 4;
2284             y = a->y + a->height / 4;
2285             w = a->width / 2;
2286             h = a->height / 2;
2287         }
2288     }
2289
2290     client_setup_decor_and_functions(self);
2291
2292     client_move_resize(self, x, y, w, h);
2293
2294     /* try focus us when we go into fullscreen mode */
2295     client_focus(self);
2296 }
2297
2298 static void client_iconify_recursive(ObClient *self,
2299                                      gboolean iconic, gboolean curdesk)
2300 {
2301     GSList *it;
2302     gboolean changed = FALSE;
2303
2304
2305     if (self->iconic != iconic) {
2306         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2307                  self->window);
2308
2309         self->iconic = iconic;
2310
2311         if (iconic) {
2312             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2313                 glong old;
2314
2315                 old = self->wmstate;
2316                 self->wmstate = IconicState;
2317                 if (old != self->wmstate)
2318                     PROP_MSG(self->window, kde_wm_change_state,
2319                              self->wmstate, 1, 0, 0);
2320
2321                 /* update the focus lists.. iconic windows go to the bottom of
2322                    the list, put the new iconic window at the 'top of the
2323                    bottom'. */
2324                 focus_order_to_top(self);
2325
2326                 changed = TRUE;
2327             }
2328         } else {
2329             glong old;
2330
2331             if (curdesk)
2332                 client_set_desktop(self, screen_desktop, FALSE);
2333
2334             old = self->wmstate;
2335             self->wmstate = self->shaded ? IconicState : NormalState;
2336             if (old != self->wmstate)
2337                 PROP_MSG(self->window, kde_wm_change_state,
2338                          self->wmstate, 1, 0, 0);
2339
2340             /* this puts it after the current focused window */
2341             focus_order_remove(self);
2342             focus_order_add_new(self);
2343
2344             changed = TRUE;
2345         }
2346     }
2347
2348     if (changed) {
2349         client_change_state(self);
2350         client_showhide(self);
2351         screen_update_areas();
2352     }
2353
2354     /* iconify all transients */
2355     for (it = self->transients; it; it = g_slist_next(it))
2356         if (it->data != self) client_iconify_recursive(it->data,
2357                                                        iconic, curdesk);
2358 }
2359
2360 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2361 {
2362     /* move up the transient chain as far as possible first */
2363     self = client_search_top_transient(self);
2364
2365     client_iconify_recursive(client_search_top_transient(self),
2366                              iconic, curdesk);
2367 }
2368
2369 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2370 {
2371     gint x, y, w, h;
2372      
2373     g_assert(dir == 0 || dir == 1 || dir == 2);
2374     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2375
2376     /* check if already done */
2377     if (max) {
2378         if (dir == 0 && self->max_horz && self->max_vert) return;
2379         if (dir == 1 && self->max_horz) return;
2380         if (dir == 2 && self->max_vert) return;
2381     } else {
2382         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2383         if (dir == 1 && !self->max_horz) return;
2384         if (dir == 2 && !self->max_vert) return;
2385     }
2386
2387     /* we just tell it to configure in the same place and client_configure
2388        worries about filling the screen with the window */
2389     x = self->area.x;
2390     y = self->area.y;
2391     w = self->area.width;
2392     h = self->area.height;
2393
2394     if (max) {
2395         if (savearea) {
2396             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2397                 RECT_SET(self->pre_max_area,
2398                          self->area.x, self->pre_max_area.y,
2399                          self->area.width, self->pre_max_area.height);
2400             }
2401             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2402                 RECT_SET(self->pre_max_area,
2403                          self->pre_max_area.x, self->area.y,
2404                          self->pre_max_area.width, self->area.height);
2405             }
2406         }
2407     } else {
2408         Rect *a;
2409
2410         a = screen_area_monitor(self->desktop, 0);
2411         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2412             if (self->pre_max_area.width > 0) {
2413                 x = self->pre_max_area.x;
2414                 w = self->pre_max_area.width;
2415
2416                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2417                          0, self->pre_max_area.height);
2418             } else {
2419                 /* pick some fallbacks... */
2420                 x = a->x + a->width / 4;
2421                 w = a->width / 2;
2422             }
2423         }
2424         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2425             if (self->pre_max_area.height > 0) {
2426                 y = self->pre_max_area.y;
2427                 h = self->pre_max_area.height;
2428
2429                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2430                          self->pre_max_area.width, 0);
2431             } else {
2432                 /* pick some fallbacks... */
2433                 y = a->y + a->height / 4;
2434                 h = a->height / 2;
2435             }
2436         }
2437     }
2438
2439     if (dir == 0 || dir == 1) /* horz */
2440         self->max_horz = max;
2441     if (dir == 0 || dir == 2) /* vert */
2442         self->max_vert = max;
2443
2444     client_change_state(self); /* change the state hints on the client */
2445
2446     client_setup_decor_and_functions(self);
2447
2448     client_move_resize(self, x, y, w, h);
2449 }
2450
2451 void client_shade(ObClient *self, gboolean shade)
2452 {
2453     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2454          shade) ||                         /* can't shade */
2455         self->shaded == shade) return;     /* already done */
2456
2457     /* when we're iconic, don't change the wmstate */
2458     if (!self->iconic) {
2459         glong old;
2460
2461         old = self->wmstate;
2462         self->wmstate = shade ? IconicState : NormalState;
2463         if (old != self->wmstate)
2464             PROP_MSG(self->window, kde_wm_change_state,
2465                      self->wmstate, 1, 0, 0);
2466     }
2467
2468     self->shaded = shade;
2469     client_change_state(self);
2470     /* resize the frame to just the titlebar */
2471     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2472 }
2473
2474 void client_close(ObClient *self)
2475 {
2476     XEvent ce;
2477
2478     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2479
2480     /* in the case that the client provides no means to requesting that it
2481        close, we just kill it */
2482     if (!self->delete_window)
2483         client_kill(self);
2484     
2485     /*
2486       XXX: itd be cool to do timeouts and shit here for killing the client's
2487       process off
2488       like... if the window is around after 5 seconds, then the close button
2489       turns a nice red, and if this function is called again, the client is
2490       explicitly killed.
2491     */
2492
2493     ce.xclient.type = ClientMessage;
2494     ce.xclient.message_type =  prop_atoms.wm_protocols;
2495     ce.xclient.display = ob_display;
2496     ce.xclient.window = self->window;
2497     ce.xclient.format = 32;
2498     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2499     ce.xclient.data.l[1] = event_lasttime;
2500     ce.xclient.data.l[2] = 0l;
2501     ce.xclient.data.l[3] = 0l;
2502     ce.xclient.data.l[4] = 0l;
2503     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2504 }
2505
2506 void client_kill(ObClient *self)
2507 {
2508     XKillClient(ob_display, self->window);
2509 }
2510
2511 void client_set_desktop_recursive(ObClient *self,
2512                                   guint target, gboolean donthide)
2513 {
2514     guint old;
2515     GSList *it;
2516
2517     if (target != self->desktop) {
2518
2519         ob_debug("Setting desktop %u\n", target+1);
2520
2521         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2522
2523         /* remove from the old desktop(s) */
2524         focus_order_remove(self);
2525
2526         old = self->desktop;
2527         self->desktop = target;
2528         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2529         /* the frame can display the current desktop state */
2530         frame_adjust_state(self->frame);
2531         /* 'move' the window to the new desktop */
2532         if (!donthide)
2533             client_showhide(self);
2534         /* raise if it was not already on the desktop */
2535         if (old != DESKTOP_ALL)
2536             client_raise(self);
2537         screen_update_areas();
2538
2539         /* add to the new desktop(s) */
2540         if (config_focus_new)
2541             focus_order_to_top(self);
2542         else
2543             focus_order_to_bottom(self);
2544     }
2545
2546     /* move all transients */
2547     for (it = self->transients; it; it = g_slist_next(it))
2548         if (it->data != self) client_set_desktop_recursive(it->data,
2549                                                            target, donthide);
2550 }
2551
2552 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2553 {
2554     client_set_desktop_recursive(client_search_top_transient(self),
2555                                  target, donthide);
2556 }
2557
2558 ObClient *client_search_modal_child(ObClient *self)
2559 {
2560     GSList *it;
2561     ObClient *ret;
2562   
2563     for (it = self->transients; it; it = g_slist_next(it)) {
2564         ObClient *c = it->data;
2565         if ((ret = client_search_modal_child(c))) return ret;
2566         if (c->modal) return c;
2567     }
2568     return NULL;
2569 }
2570
2571 gboolean client_validate(ObClient *self)
2572 {
2573     XEvent e; 
2574
2575     XSync(ob_display, FALSE); /* get all events on the server */
2576
2577     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2578         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2579         XPutBackEvent(ob_display, &e);
2580         return FALSE;
2581     }
2582
2583     return TRUE;
2584 }
2585
2586 void client_set_wm_state(ObClient *self, glong state)
2587 {
2588     if (state == self->wmstate) return; /* no change */
2589   
2590     switch (state) {
2591     case IconicState:
2592         client_iconify(self, TRUE, TRUE);
2593         break;
2594     case NormalState:
2595         client_iconify(self, FALSE, TRUE);
2596         break;
2597     }
2598 }
2599
2600 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2601 {
2602     gboolean shaded = self->shaded;
2603     gboolean fullscreen = self->fullscreen;
2604     gboolean undecorated = self->undecorated;
2605     gboolean max_horz = self->max_horz;
2606     gboolean max_vert = self->max_vert;
2607     gboolean modal = self->modal;
2608     gboolean iconic = self->iconic;
2609     gint i;
2610
2611     if (!(action == prop_atoms.net_wm_state_add ||
2612           action == prop_atoms.net_wm_state_remove ||
2613           action == prop_atoms.net_wm_state_toggle))
2614         /* an invalid action was passed to the client message, ignore it */
2615         return; 
2616
2617     for (i = 0; i < 2; ++i) {
2618         Atom state = i == 0 ? data1 : data2;
2619     
2620         if (!state) continue;
2621
2622         /* if toggling, then pick whether we're adding or removing */
2623         if (action == prop_atoms.net_wm_state_toggle) {
2624             if (state == prop_atoms.net_wm_state_modal)
2625                 action = modal ? prop_atoms.net_wm_state_remove :
2626                     prop_atoms.net_wm_state_add;
2627             else if (state == prop_atoms.net_wm_state_maximized_vert)
2628                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2629                     prop_atoms.net_wm_state_add;
2630             else if (state == prop_atoms.net_wm_state_maximized_horz)
2631                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2632                     prop_atoms.net_wm_state_add;
2633             else if (state == prop_atoms.net_wm_state_shaded)
2634                 action = shaded ? prop_atoms.net_wm_state_remove :
2635                     prop_atoms.net_wm_state_add;
2636             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2637                 action = self->skip_taskbar ?
2638                     prop_atoms.net_wm_state_remove :
2639                     prop_atoms.net_wm_state_add;
2640             else if (state == prop_atoms.net_wm_state_skip_pager)
2641                 action = self->skip_pager ?
2642                     prop_atoms.net_wm_state_remove :
2643                     prop_atoms.net_wm_state_add;
2644             else if (state == prop_atoms.net_wm_state_hidden)
2645                 action = self->iconic ?
2646                     prop_atoms.net_wm_state_remove :
2647                     prop_atoms.net_wm_state_add;
2648             else if (state == prop_atoms.net_wm_state_fullscreen)
2649                 action = fullscreen ?
2650                     prop_atoms.net_wm_state_remove :
2651                     prop_atoms.net_wm_state_add;
2652             else if (state == prop_atoms.net_wm_state_above)
2653                 action = self->above ? prop_atoms.net_wm_state_remove :
2654                     prop_atoms.net_wm_state_add;
2655             else if (state == prop_atoms.net_wm_state_below)
2656                 action = self->below ? prop_atoms.net_wm_state_remove :
2657                     prop_atoms.net_wm_state_add;
2658             else if (state == prop_atoms.ob_wm_state_undecorated)
2659                 action = undecorated ? prop_atoms.net_wm_state_remove :
2660                     prop_atoms.net_wm_state_add;
2661         }
2662     
2663         if (action == prop_atoms.net_wm_state_add) {
2664             if (state == prop_atoms.net_wm_state_modal) {
2665                 modal = TRUE;
2666             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2667                 max_vert = TRUE;
2668             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2669                 max_horz = TRUE;
2670             } else if (state == prop_atoms.net_wm_state_shaded) {
2671                 shaded = TRUE;
2672             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2673                 self->skip_taskbar = TRUE;
2674             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2675                 self->skip_pager = TRUE;
2676             } else if (state == prop_atoms.net_wm_state_hidden) {
2677                 iconic = TRUE;
2678             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2679                 fullscreen = TRUE;
2680             } else if (state == prop_atoms.net_wm_state_above) {
2681                 self->above = TRUE;
2682                 self->below = FALSE;
2683             } else if (state == prop_atoms.net_wm_state_below) {
2684                 self->above = FALSE;
2685                 self->below = TRUE;
2686             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2687                 undecorated = TRUE;
2688             }
2689
2690         } else { /* action == prop_atoms.net_wm_state_remove */
2691             if (state == prop_atoms.net_wm_state_modal) {
2692                 modal = FALSE;
2693             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2694                 max_vert = FALSE;
2695             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2696                 max_horz = FALSE;
2697             } else if (state == prop_atoms.net_wm_state_shaded) {
2698                 shaded = FALSE;
2699             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2700                 self->skip_taskbar = FALSE;
2701             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2702                 self->skip_pager = FALSE;
2703             } else if (state == prop_atoms.net_wm_state_hidden) {
2704                 iconic = FALSE;
2705             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2706                 fullscreen = FALSE;
2707             } else if (state == prop_atoms.net_wm_state_above) {
2708                 self->above = FALSE;
2709             } else if (state == prop_atoms.net_wm_state_below) {
2710                 self->below = FALSE;
2711             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2712                 undecorated = FALSE;
2713             }
2714         }
2715     }
2716     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2717         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2718             /* toggling both */
2719             if (max_horz == max_vert) { /* both going the same way */
2720                 client_maximize(self, max_horz, 0, TRUE);
2721             } else {
2722                 client_maximize(self, max_horz, 1, TRUE);
2723                 client_maximize(self, max_vert, 2, TRUE);
2724             }
2725         } else {
2726             /* toggling one */
2727             if (max_horz != self->max_horz)
2728                 client_maximize(self, max_horz, 1, TRUE);
2729             else
2730                 client_maximize(self, max_vert, 2, TRUE);
2731         }
2732     }
2733     /* change fullscreen state before shading, as it will affect if the window
2734        can shade or not */
2735     if (fullscreen != self->fullscreen)
2736         client_fullscreen(self, fullscreen, TRUE);
2737     if (shaded != self->shaded)
2738         client_shade(self, shaded);
2739     if (undecorated != self->undecorated)
2740         client_set_undecorated(self, undecorated);
2741     if (modal != self->modal) {
2742         self->modal = modal;
2743         /* when a window changes modality, then its stacking order with its
2744            transients needs to change */
2745         client_raise(self);
2746     }
2747     if (iconic != self->iconic)
2748         client_iconify(self, iconic, FALSE);
2749
2750     client_calc_layer(self);
2751     client_change_state(self); /* change the hint to reflect these changes */
2752 }
2753
2754 ObClient *client_focus_target(ObClient *self)
2755 {
2756     ObClient *child;
2757      
2758     /* if we have a modal child, then focus it, not us */
2759     child = client_search_modal_child(client_search_top_transient(self));
2760     if (child) return child;
2761     return self;
2762 }
2763
2764 gboolean client_can_focus(ObClient *self)
2765 {
2766     XEvent ev;
2767
2768     /* choose the correct target */
2769     self = client_focus_target(self);
2770
2771     if (!self->frame->visible)
2772         return FALSE;
2773
2774     if (!(self->can_focus || self->focus_notify))
2775         return FALSE;
2776
2777     /* do a check to see if the window has already been unmapped or destroyed
2778        do this intelligently while watching out for unmaps we've generated
2779        (ignore_unmaps > 0) */
2780     if (XCheckTypedWindowEvent(ob_display, self->window,
2781                                DestroyNotify, &ev)) {
2782         XPutBackEvent(ob_display, &ev);
2783         return FALSE;
2784     }
2785     while (XCheckTypedWindowEvent(ob_display, self->window,
2786                                   UnmapNotify, &ev)) {
2787         if (self->ignore_unmaps) {
2788             self->ignore_unmaps--;
2789         } else {
2790             XPutBackEvent(ob_display, &ev);
2791             return FALSE;
2792         }
2793     }
2794
2795     return TRUE;
2796 }
2797
2798 gboolean client_focus(ObClient *self)
2799 {
2800     /* choose the correct target */
2801     self = client_focus_target(self);
2802
2803     if (!client_can_focus(self)) {
2804         if (!self->frame->visible) {
2805             /* update the focus lists */
2806             focus_order_to_top(self);
2807         }
2808         return FALSE;
2809     }
2810
2811     if (self->can_focus) {
2812         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2813            I choose to use it always, hopefully to find errors quicker, if any
2814            are left. (I hate X. I hate focus events.)
2815            
2816            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2817            #799. So now it is RevertToNone again.
2818         */
2819         XSetInputFocus(ob_display, self->window, RevertToNone,
2820                        event_lasttime);
2821     }
2822
2823     if (self->focus_notify) {
2824         XEvent ce;
2825         ce.xclient.type = ClientMessage;
2826         ce.xclient.message_type = prop_atoms.wm_protocols;
2827         ce.xclient.display = ob_display;
2828         ce.xclient.window = self->window;
2829         ce.xclient.format = 32;
2830         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2831         ce.xclient.data.l[1] = event_lasttime;
2832         ce.xclient.data.l[2] = 0l;
2833         ce.xclient.data.l[3] = 0l;
2834         ce.xclient.data.l[4] = 0l;
2835         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2836     }
2837
2838 #ifdef DEBUG_FOCUS
2839     ob_debug("%sively focusing %lx at %d\n",
2840              (self->can_focus ? "act" : "pass"),
2841              self->window, (gint) event_lasttime);
2842 #endif
2843
2844     /* Cause the FocusIn to come back to us. Important for desktop switches,
2845        since otherwise we'll have no FocusIn on the queue and send it off to
2846        the focus_backup. */
2847     XSync(ob_display, FALSE);
2848     return TRUE;
2849 }
2850
2851 /* Used when the current client is closed, focus_last will then prevent
2852  * focus from going to the mouse pointer */
2853 void client_unfocus(ObClient *self)
2854 {
2855     if (focus_client == self) {
2856 #ifdef DEBUG_FOCUS
2857         ob_debug("client_unfocus for %lx\n", self->window);
2858 #endif
2859         focus_fallback(OB_FOCUS_FALLBACK_CLOSED);
2860     }
2861 }
2862
2863 void client_activate(ObClient *self, gboolean here)
2864 {
2865     if (client_normal(self) && screen_showing_desktop)
2866         screen_show_desktop(FALSE);
2867     if (self->iconic)
2868         client_iconify(self, FALSE, here);
2869     if (self->desktop != DESKTOP_ALL &&
2870         self->desktop != screen_desktop) {
2871         if (here)
2872             client_set_desktop(self, screen_desktop, FALSE);
2873         else
2874             screen_set_desktop(self->desktop);
2875     } else if (!self->frame->visible)
2876         /* if its not visible for other reasons, then don't mess
2877            with it */
2878         return;
2879     if (self->shaded)
2880         client_shade(self, FALSE);
2881
2882     client_focus(self);
2883
2884     /* we do this an action here. this is rather important. this is because
2885        we want the results from the focus change to take place BEFORE we go
2886        about raising the window. when a fullscreen window loses focus, we need
2887        this or else the raise wont be able to raise above the to-lose-focus
2888        fullscreen window. */
2889     client_raise(self);
2890 }
2891
2892 void client_raise(ObClient *self)
2893 {
2894     action_run_string("Raise", self);
2895 }
2896
2897 void client_lower(ObClient *self)
2898 {
2899     action_run_string("Lower", self);
2900 }
2901
2902 gboolean client_focused(ObClient *self)
2903 {
2904     return self == focus_client;
2905 }
2906
2907 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2908 {
2909     guint i;
2910     /* si is the smallest image >= req */
2911     /* li is the largest image < req */
2912     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2913
2914     if (!self->nicons) {
2915         ObClientIcon *parent = NULL;
2916
2917         if (self->transient_for) {
2918             if (self->transient_for != OB_TRAN_GROUP)
2919                 parent = client_icon_recursive(self->transient_for, w, h);
2920             else {
2921                 GSList *it;
2922                 for (it = self->group->members; it; it = g_slist_next(it)) {
2923                     ObClient *c = it->data;
2924                     if (c != self && !c->transient_for) {
2925                         if ((parent = client_icon_recursive(c, w, h)))
2926                             break;
2927                     }
2928                 }
2929             }
2930         }
2931         
2932         return parent;
2933     }
2934
2935     for (i = 0; i < self->nicons; ++i) {
2936         size = self->icons[i].width * self->icons[i].height;
2937         if (size < smallest && size >= (unsigned)(w * h)) {
2938             smallest = size;
2939             si = i;
2940         }
2941         if (size > largest && size <= (unsigned)(w * h)) {
2942             largest = size;
2943             li = i;
2944         }
2945     }
2946     if (largest == 0) /* didnt find one smaller than the requested size */
2947         return &self->icons[si];
2948     return &self->icons[li];
2949 }
2950
2951 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2952 {
2953     ObClientIcon *ret;
2954     static ObClientIcon deficon;
2955
2956     if (!(ret = client_icon_recursive(self, w, h))) {
2957         deficon.width = deficon.height = 48;
2958         deficon.data = ob_rr_theme->def_win_icon;
2959         ret = &deficon;
2960     }
2961     return ret;
2962 }
2963
2964 /* this be mostly ripped from fvwm */
2965 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2966 {
2967     gint my_cx, my_cy, his_cx, his_cy;
2968     gint offset = 0;
2969     gint distance = 0;
2970     gint score, best_score;
2971     ObClient *best_client, *cur;
2972     GList *it;
2973
2974     if(!client_list)
2975         return NULL;
2976
2977     /* first, find the centre coords of the currently focused window */
2978     my_cx = c->frame->area.x + c->frame->area.width / 2;
2979     my_cy = c->frame->area.y + c->frame->area.height / 2;
2980
2981     best_score = -1;
2982     best_client = NULL;
2983
2984     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2985         cur = it->data;
2986
2987         /* the currently selected window isn't interesting */
2988         if(cur == c)
2989             continue;
2990         if (!client_normal(cur))
2991             continue;
2992         /* using c->desktop instead of screen_desktop doesn't work if the
2993          * current window was omnipresent, hope this doesn't have any other
2994          * side effects */
2995         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2996             continue;
2997         if(cur->iconic)
2998             continue;
2999         if(client_focus_target(cur) == cur &&
3000            !(cur->can_focus || cur->focus_notify))
3001             continue;
3002
3003         /* find the centre coords of this window, from the
3004          * currently focused window's point of view */
3005         his_cx = (cur->frame->area.x - my_cx)
3006             + cur->frame->area.width / 2;
3007         his_cy = (cur->frame->area.y - my_cy)
3008             + cur->frame->area.height / 2;
3009
3010         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3011            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3012             gint tx;
3013             /* Rotate the diagonals 45 degrees counterclockwise.
3014              * To do this, multiply the matrix /+h +h\ with the
3015              * vector (x y).                   \-h +h/
3016              * h = sqrt(0.5). We can set h := 1 since absolute
3017              * distance doesn't matter here. */
3018             tx = his_cx + his_cy;
3019             his_cy = -his_cx + his_cy;
3020             his_cx = tx;
3021         }
3022
3023         switch(dir) {
3024         case OB_DIRECTION_NORTH:
3025         case OB_DIRECTION_SOUTH:
3026         case OB_DIRECTION_NORTHEAST:
3027         case OB_DIRECTION_SOUTHWEST:
3028             offset = (his_cx < 0) ? -his_cx : his_cx;
3029             distance = ((dir == OB_DIRECTION_NORTH ||
3030                          dir == OB_DIRECTION_NORTHEAST) ?
3031                         -his_cy : his_cy);
3032             break;
3033         case OB_DIRECTION_EAST:
3034         case OB_DIRECTION_WEST:
3035         case OB_DIRECTION_SOUTHEAST:
3036         case OB_DIRECTION_NORTHWEST:
3037             offset = (his_cy < 0) ? -his_cy : his_cy;
3038             distance = ((dir == OB_DIRECTION_WEST ||
3039                          dir == OB_DIRECTION_NORTHWEST) ?
3040                         -his_cx : his_cx);
3041             break;
3042         }
3043
3044         /* the target must be in the requested direction */
3045         if(distance <= 0)
3046             continue;
3047
3048         /* Calculate score for this window.  The smaller the better. */
3049         score = distance + offset;
3050
3051         /* windows more than 45 degrees off the direction are
3052          * heavily penalized and will only be chosen if nothing
3053          * else within a million pixels */
3054         if(offset > distance)
3055             score += 1000000;
3056
3057         if(best_score == -1 || score < best_score)
3058             best_client = cur,
3059                 best_score = score;
3060     }
3061
3062     return best_client;
3063 }
3064
3065 void client_set_layer(ObClient *self, gint layer)
3066 {
3067     if (layer < 0) {
3068         self->below = TRUE;
3069         self->above = FALSE;
3070     } else if (layer == 0) {
3071         self->below = self->above = FALSE;
3072     } else {
3073         self->below = FALSE;
3074         self->above = TRUE;
3075     }
3076     client_calc_layer(self);
3077     client_change_state(self); /* reflect this in the state hints */
3078 }
3079
3080 void client_set_undecorated(ObClient *self, gboolean undecorated)
3081 {
3082     if (self->undecorated != undecorated) {
3083         self->undecorated = undecorated;
3084         client_setup_decor_and_functions(self);
3085         /* Make sure the client knows it might have moved. Maybe there is a
3086          * better way of doing this so only one client_configure is sent, but
3087          * since 125 of these are sent per second when moving the window (with
3088          * user = FALSE) i doubt it matters much.
3089          */
3090         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3091                          self->area.width, self->area.height, TRUE, TRUE);
3092         client_change_state(self); /* reflect this in the state hints */
3093     }
3094 }
3095
3096 /* Determines which physical monitor a client is on by calculating the
3097    area of the part of the client on each monitor.  The number of the
3098    monitor containing the greatest area of the client is returned.*/
3099 guint client_monitor(ObClient *self)
3100 {
3101     guint i;
3102     guint most = 0;
3103     guint mostv = 0;
3104
3105     for (i = 0; i < screen_num_monitors; ++i) {
3106         Rect *area = screen_physical_area_monitor(i);
3107         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3108             Rect r;
3109             guint v;
3110
3111             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3112             v = r.width * r.height;
3113
3114             if (v > mostv) {
3115                 mostv = v;
3116                 most = i;
3117             }
3118         }
3119     }
3120     return most;
3121 }
3122
3123 ObClient *client_search_top_transient(ObClient *self)
3124 {
3125     /* move up the transient chain as far as possible */
3126     if (self->transient_for) {
3127         if (self->transient_for != OB_TRAN_GROUP) {
3128             return client_search_top_transient(self->transient_for);
3129         } else {
3130             GSList *it;
3131
3132             g_assert(self->group);
3133
3134             for (it = self->group->members; it; it = g_slist_next(it)) {
3135                 ObClient *c = it->data;
3136
3137                 /* checking transient_for prevents infinate loops! */
3138                 if (c != self && !c->transient_for)
3139                     break;
3140             }
3141             if (it)
3142                 return it->data;
3143         }
3144     }
3145
3146     return self;
3147 }
3148
3149 ObClient *client_search_focus_parent(ObClient *self)
3150 {
3151     if (self->transient_for) {
3152         if (self->transient_for != OB_TRAN_GROUP) {
3153             if (client_focused(self->transient_for))
3154                 return self->transient_for;
3155         } else {
3156             GSList *it;
3157
3158             for (it = self->group->members; it; it = g_slist_next(it)) {
3159                 ObClient *c = it->data;
3160
3161                 /* checking transient_for prevents infinate loops! */
3162                 if (c != self && !c->transient_for)
3163                     if (client_focused(c))
3164                         return c;
3165             }
3166         }
3167     }
3168
3169     return NULL;
3170 }
3171
3172 ObClient *client_search_parent(ObClient *self, ObClient *search)
3173 {
3174     if (self->transient_for) {
3175         if (self->transient_for != OB_TRAN_GROUP) {
3176             if (self->transient_for == search)
3177                 return search;
3178         } else {
3179             GSList *it;
3180
3181             for (it = self->group->members; it; it = g_slist_next(it)) {
3182                 ObClient *c = it->data;
3183
3184                 /* checking transient_for prevents infinate loops! */
3185                 if (c != self && !c->transient_for)
3186                     if (c == search)
3187                         return search;
3188             }
3189         }
3190     }
3191
3192     return NULL;
3193 }
3194
3195 ObClient *client_search_transient(ObClient *self, ObClient *search)
3196 {
3197     GSList *sit;
3198
3199     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3200         if (sit->data == search)
3201             return search;
3202         if (client_search_transient(sit->data, search))
3203             return search;
3204     }
3205     return NULL;
3206 }
3207
3208 void client_update_sm_client_id(ObClient *self)
3209 {
3210     g_free(self->sm_client_id);
3211     self->sm_client_id = NULL;
3212
3213     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3214         self->group)
3215         PROP_GETS(self->group->leader, sm_client_id, locale,
3216                   &self->sm_client_id);
3217 }
3218
3219 /* finds the nearest edge in the given direction from the current client
3220  * note to self: the edge is the -frame- edge (the actual one), not the
3221  * client edge.
3222  */
3223 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3224 {
3225     gint dest, monitor_dest;
3226     gint my_edge_start, my_edge_end, my_offset;
3227     GList *it;
3228     Rect *a, *monitor;
3229     
3230     if(!client_list)
3231         return -1;
3232
3233     a = screen_area(c->desktop);
3234     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3235
3236     switch(dir) {
3237     case OB_DIRECTION_NORTH:
3238         my_edge_start = c->frame->area.x;
3239         my_edge_end = c->frame->area.x + c->frame->area.width;
3240         my_offset = c->frame->area.y;
3241         
3242         /* default: top of screen */
3243         dest = a->y;
3244         monitor_dest = monitor->y;
3245         /* if the monitor edge comes before the screen edge, */
3246         /* use that as the destination instead. (For xinerama) */
3247         if (monitor_dest != dest && my_offset > monitor_dest)
3248             dest = monitor_dest; 
3249
3250         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3251             gint his_edge_start, his_edge_end, his_offset;
3252             ObClient *cur = it->data;
3253
3254             if(cur == c)
3255                 continue;
3256             if(!client_normal(cur))
3257                 continue;
3258             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3259                 continue;
3260             if(cur->iconic)
3261                 continue;
3262             if(cur->layer < c->layer && !config_resist_layers_below)
3263                 continue;
3264
3265             his_edge_start = cur->frame->area.x;
3266             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3267             his_offset = cur->frame->area.y + cur->frame->area.height;
3268
3269             if(his_offset + 1 > my_offset)
3270                 continue;
3271
3272             if(his_offset < dest)
3273                 continue;
3274             
3275             if(his_edge_start >= my_edge_start &&
3276                his_edge_start <= my_edge_end)
3277                 dest = his_offset;
3278
3279             if(my_edge_start >= his_edge_start &&
3280                my_edge_start <= his_edge_end)
3281                 dest = his_offset;
3282
3283         }
3284         break;
3285     case OB_DIRECTION_SOUTH:
3286         my_edge_start = c->frame->area.x;
3287         my_edge_end = c->frame->area.x + c->frame->area.width;
3288         my_offset = c->frame->area.y + c->frame->area.height;
3289
3290         /* default: bottom of screen */
3291         dest = a->y + a->height;
3292         monitor_dest = monitor->y + monitor->height;
3293         /* if the monitor edge comes before the screen edge, */
3294         /* use that as the destination instead. (For xinerama) */
3295         if (monitor_dest != dest && my_offset < monitor_dest)
3296             dest = monitor_dest; 
3297
3298         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3299             gint his_edge_start, his_edge_end, his_offset;
3300             ObClient *cur = it->data;
3301
3302             if(cur == c)
3303                 continue;
3304             if(!client_normal(cur))
3305                 continue;
3306             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3307                 continue;
3308             if(cur->iconic)
3309                 continue;
3310             if(cur->layer < c->layer && !config_resist_layers_below)
3311                 continue;
3312
3313             his_edge_start = cur->frame->area.x;
3314             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3315             his_offset = cur->frame->area.y;
3316
3317
3318             if(his_offset - 1 < my_offset)
3319                 continue;
3320             
3321             if(his_offset > dest)
3322                 continue;
3323             
3324             if(his_edge_start >= my_edge_start &&
3325                his_edge_start <= my_edge_end)
3326                 dest = his_offset;
3327
3328             if(my_edge_start >= his_edge_start &&
3329                my_edge_start <= his_edge_end)
3330                 dest = his_offset;
3331
3332         }
3333         break;
3334     case OB_DIRECTION_WEST:
3335         my_edge_start = c->frame->area.y;
3336         my_edge_end = c->frame->area.y + c->frame->area.height;
3337         my_offset = c->frame->area.x;
3338
3339         /* default: leftmost egde of screen */
3340         dest = a->x;
3341         monitor_dest = monitor->x;
3342         /* if the monitor edge comes before the screen edge, */
3343         /* use that as the destination instead. (For xinerama) */
3344         if (monitor_dest != dest && my_offset > monitor_dest)
3345             dest = monitor_dest;            
3346
3347         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3348             gint his_edge_start, his_edge_end, his_offset;
3349             ObClient *cur = it->data;
3350
3351             if(cur == c)
3352                 continue;
3353             if(!client_normal(cur))
3354                 continue;
3355             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3356                 continue;
3357             if(cur->iconic)
3358                 continue;
3359             if(cur->layer < c->layer && !config_resist_layers_below)
3360                 continue;
3361
3362             his_edge_start = cur->frame->area.y;
3363             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3364             his_offset = cur->frame->area.x + cur->frame->area.width;
3365
3366             if(his_offset + 1 > my_offset)
3367                 continue;
3368             
3369             if(his_offset < dest)
3370                 continue;
3371             
3372             if(his_edge_start >= my_edge_start &&
3373                his_edge_start <= my_edge_end)
3374                 dest = his_offset;
3375
3376             if(my_edge_start >= his_edge_start &&
3377                my_edge_start <= his_edge_end)
3378                 dest = his_offset;
3379                 
3380
3381         }
3382         break;
3383     case OB_DIRECTION_EAST:
3384         my_edge_start = c->frame->area.y;
3385         my_edge_end = c->frame->area.y + c->frame->area.height;
3386         my_offset = c->frame->area.x + c->frame->area.width;
3387         
3388         /* default: rightmost edge of screen */
3389         dest = a->x + a->width;
3390         monitor_dest = monitor->x + monitor->width;
3391         /* if the monitor edge comes before the screen edge, */
3392         /* use that as the destination instead. (For xinerama) */
3393         if (monitor_dest != dest && my_offset < monitor_dest)
3394             dest = monitor_dest;            
3395
3396         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3397             gint his_edge_start, his_edge_end, his_offset;
3398             ObClient *cur = it->data;
3399
3400             if(cur == c)
3401                 continue;
3402             if(!client_normal(cur))
3403                 continue;
3404             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3405                 continue;
3406             if(cur->iconic)
3407                 continue;
3408             if(cur->layer < c->layer && !config_resist_layers_below)
3409                 continue;
3410
3411             his_edge_start = cur->frame->area.y;
3412             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3413             his_offset = cur->frame->area.x;
3414
3415             if(his_offset - 1 < my_offset)
3416                 continue;
3417             
3418             if(his_offset > dest)
3419                 continue;
3420             
3421             if(his_edge_start >= my_edge_start &&
3422                his_edge_start <= my_edge_end)
3423                 dest = his_offset;
3424
3425             if(my_edge_start >= his_edge_start &&
3426                my_edge_start <= his_edge_end)
3427                 dest = his_offset;
3428
3429         }
3430         break;
3431     case OB_DIRECTION_NORTHEAST:
3432     case OB_DIRECTION_SOUTHEAST:
3433     case OB_DIRECTION_NORTHWEST:
3434     case OB_DIRECTION_SOUTHWEST:
3435         /* not implemented */
3436     default:
3437         g_assert_not_reached();
3438         dest = 0; /* suppress warning */
3439     }
3440     return dest;
3441 }
3442
3443 ObClient* client_under_pointer()
3444 {
3445     gint x, y;
3446     GList *it;
3447     ObClient *ret = NULL;
3448
3449     if (screen_pointer_pos(&x, &y)) {
3450         for (it = stacking_list; it; it = g_list_next(it)) {
3451             if (WINDOW_IS_CLIENT(it->data)) {
3452                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3453                 if (c->frame->visible &&
3454                     RECT_CONTAINS(c->frame->area, x, y)) {
3455                     ret = c;
3456                     break;
3457                 }
3458             }
3459         }
3460     }
3461     return ret;
3462 }
3463
3464 gboolean client_has_group_siblings(ObClient *self)
3465 {
3466     return self->group && self->group->members->next;
3467 }