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