]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
Fix bug that openbox don't draw title on new window
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-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 "screen.h"
25 #include "moveresize.h"
26 #include "ping.h"
27 #include "place.h"
28 #include "engine_interface.h"
29 #include "session.h"
30 #include "event.h"
31 #include "grab.h"
32 #include "prompt.h"
33 #include "focus.h"
34 #include "stacking.h"
35 #include "openbox.h"
36 #include "group.h"
37 #include "config.h"
38 #include "menuframe.h"
39 #include "keyboard.h"
40 #include "mouse.h"
41 #include "render/render.h"
42 #include "gettext.h"
43 #include "obt/display.h"
44 #include "obt/prop.h"
45 #include "window.h"
46
47 #ifdef HAVE_UNISTD_H
48 #  include <unistd.h>
49 #endif
50
51 #ifdef HAVE_SIGNAL_H
52 #  include <signal.h> /* for kill() */
53 #endif
54
55 #include <glib.h>
56 #include <X11/Xutil.h>
57
58 /*! The event mask to grab on client windows */
59 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
60                           ColormapChangeMask)
61
62 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
63                                 ButtonMotionMask)
64
65 typedef struct
66 {
67     ObClientCallback func;
68     gpointer data;
69 } ClientCallback;
70
71 GList            *client_list          = NULL;
72
73 static GSList *client_destroy_notifies = NULL;
74
75 static void client_get_all(ObClient *self, gboolean real);
76 static void client_get_startup_id(ObClient *self);
77 static void client_get_session_ids(ObClient *self);
78 static void client_get_area(ObClient *self);
79 static void client_get_desktop(ObClient *self);
80 static void client_get_state(ObClient *self);
81 static void client_get_shaped(ObClient *self);
82 static void client_get_mwm_hints(ObClient *self);
83 static void client_get_colormap(ObClient *self);
84 static void client_set_desktop_recursive(ObClient *self,
85                                          guint target,
86                                          gboolean donthide,
87                                          gboolean dontraise);
88 static void client_change_allowed_actions(ObClient *self);
89 static void client_change_state(ObClient *self);
90 static void client_change_wm_state(ObClient *self);
91 static void client_apply_startup_state(ObClient *self,
92                                        gint x, gint y, gint w, gint h);
93 static void client_restore_session_state(ObClient *self);
94 static gboolean client_restore_session_stacking(ObClient *self);
95 static ObAppSettings *client_get_settings_state(ObClient *self);
96 static void client_update_transient_tree(ObClient *self,
97                                          ObGroup *oldgroup, ObGroup *newgroup,
98                                          gboolean oldgtran, gboolean newgtran,
99                                          ObClient* oldparent,
100                                          ObClient *newparent);
101 static void client_present(ObClient *self, gboolean here, gboolean raise,
102                            gboolean unshade);
103 static GSList *client_search_all_top_parents_internal(ObClient *self,
104                                                       gboolean bylayer,
105                                                       ObStackingLayer layer);
106 static void client_call_notifies(ObClient *self, GSList *list);
107 static void client_ping_event(ObClient *self, gboolean dead);
108 static void client_prompt_kill(ObClient *self);
109
110
111 void client_startup(gboolean reconfig)
112 {
113     if (reconfig) return;
114
115     client_set_list();
116 }
117
118 void client_shutdown(gboolean reconfig)
119 {
120     if (reconfig) return;
121 }
122
123 static void client_call_notifies(ObClient *self, GSList *list)
124 {
125     GSList *it;
126
127     for (it = list; it; it = g_slist_next(it)) {
128         ClientCallback *d = it->data;
129         d->func(self, d->data);
130     }
131 }
132
133 void client_add_destroy_notify(ObClientCallback func, gpointer data)
134 {
135     ClientCallback *d = g_new(ClientCallback, 1);
136     d->func = func;
137     d->data = data;
138     client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
139 }
140
141 void client_remove_destroy_notify(ObClientCallback func)
142 {
143     GSList *it;
144
145     for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
146         ClientCallback *d = it->data;
147         if (d->func == func) {
148             g_free(d);
149             client_destroy_notifies =
150                 g_slist_delete_link(client_destroy_notifies, it);
151             break;
152         }
153     }
154 }
155
156 void client_set_list(void)
157 {
158     Window *windows, *win_it;
159     GList *it;
160     guint size = g_list_length(client_list);
161
162     /* create an array of the window ids */
163     if (size > 0) {
164         windows = g_new(Window, size);
165         win_it = windows;
166         for (it = client_list; it; it = g_list_next(it), ++win_it)
167             *win_it = ((ObClient*)it->data)->window;
168     } else
169         windows = NULL;
170
171     OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST, WINDOW,
172                     (gulong*)windows, size);
173
174     if (windows)
175         g_free(windows);
176
177     stacking_set_list();
178 }
179
180 void client_manage(Window window, ObPrompt *prompt)
181 {
182     ObClient *self;
183     XSetWindowAttributes attrib_set;
184     gboolean activate = FALSE;
185     ObAppSettings *settings;
186     gboolean transient = FALSE;
187     Rect place, *monitor;
188     Time launch_time, map_time;
189
190     ob_debug("Managing window: 0x%lx", window);
191
192     map_time = event_get_server_time();
193
194     /* choose the events we want to receive on the CLIENT window
195        (ObPrompt windows can request events too) */
196     attrib_set.event_mask = CLIENT_EVENTMASK |
197         (prompt ? prompt->event_mask : 0);
198     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
199     XChangeWindowAttributes(obt_display, window,
200                             CWEventMask|CWDontPropagate, &attrib_set);
201
202     /* create the ObClient struct, and populate it from the hints on the
203        window */
204     self = g_new0(ObClient, 1);
205     self->obwin.type = OB_WINDOW_CLASS_CLIENT;
206     self->window = window;
207     self->prompt = prompt;
208
209     /* non-zero defaults */
210     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
211     self->gravity = NorthWestGravity;
212     self->desktop = screen_num_desktops; /* always an invalid value */
213
214     /* get all the stuff off the window */
215     client_get_all(self, TRUE);
216
217     ob_debug("Window type: %d", self->type);
218     ob_debug("Window group: 0x%x", self->group?self->group->leader:0);
219
220     /* now we have all of the window's information so we can set this up.
221        do this before creating the frame, so it can tell that we are still
222        mapping and doesn't go applying things right away */
223     client_setup_decor_and_functions(self, FALSE);
224
225     /* specify that if we exit, the window should not be destroyed and
226        should be reparented back to root automatically, unless we are managing
227        an internal ObPrompt window  */
228     if (!self->prompt)
229         XChangeSaveSet(obt_display, window, SetModeInsert);
230
231     /* create the decoration frame for the client window */
232     self->frame = render_plugin->frame_new(self);
233     render_plugin->frame_grab(self->frame, window_map);
234
235     /* we've grabbed everything and set everything that we need to at mapping
236        time now */
237     grab_server(FALSE);
238
239     /* per-app settings override stuff from client_get_all, and return the
240        settings for other uses too. the returned settings is a shallow copy,
241        that needs to be freed with g_free(). */
242     settings = client_get_settings_state(self);
243     /* the session should get the last say though */
244     client_restore_session_state(self);
245
246     /* tell startup notification that this app started */
247     launch_time = sn_app_started(self->startup_id, self->class, self->name);
248
249     /* do this after we have a frame.. it uses the frame to help determine the
250        WM_STATE to apply. */
251     client_change_state(self);
252
253     /* add ourselves to the focus order */
254     focus_order_add_new(self);
255
256     /* do this to add ourselves to the stacking list in a non-intrusive way */
257     client_calc_layer(self);
258
259     /* focus the new window? */
260     if (ob_state() != OB_STATE_STARTING &&
261         (!self->session || self->session->focused) &&
262         /* this means focus=true for window is same as config_focus_new=true */
263         ((config_focus_new || (settings && settings->focus == 1)) ||
264          client_search_focus_tree_full(self)) &&
265         /* this checks for focus=false for the window */
266         (!settings || settings->focus != 0) &&
267         focus_valid_target(self, FALSE, FALSE, TRUE, FALSE, FALSE))
268     {
269         activate = TRUE;
270     }
271
272     /* remove the client's border */
273     XSetWindowBorderWidth(obt_display, self->window, 0);
274
275     /* adjust the frame to the client's size before showing or placing
276        the window */
277     render_plugin->frame_set_client_area (self->frame, self->area);
278     render_plugin->frame_set_hover_flag (self->frame, OB_BUTTON_NONE);
279     render_plugin->frame_set_press_flag (self->frame, OB_BUTTON_NONE);
280     render_plugin->frame_set_is_focus (self->frame, FALSE);
281     render_plugin->frame_set_decorations (self->frame, self->decorations);
282     render_plugin->frame_update_title (self->frame, self->title);
283     render_plugin->frame_update_layout (self->frame, FALSE, TRUE);
284     render_plugin->frame_update_skin (self->frame);
285
286     /* where the frame was placed is where the window was originally */
287     place = self->area;
288     monitor = screen_physical_area_monitor(screen_find_monitor(&place));
289
290     /* figure out placement for the window if the window is new */
291     if (ob_state() == OB_STATE_RUNNING) {
292         ob_debug("Positioned: %s @ %d %d",
293                  (!self->positioned ? "no" :
294                   (self->positioned == PPosition ? "program specified" :
295                    (self->positioned == USPosition ? "user specified" :
296                     (self->positioned == (PPosition | USPosition) ?
297                      "program + user specified" :
298                      "BADNESS !?")))), place.x, place.y);
299
300         ob_debug("Sized: %s @ %d %d",
301                  (!self->sized ? "no" :
302                   (self->sized == PSize ? "program specified" :
303                    (self->sized == USSize ? "user specified" :
304                     (self->sized == (PSize | USSize) ?
305                      "program + user specified" :
306                      "BADNESS !?")))), place.width, place.height);
307
308         /* splash screens are also returned as TRUE for transient,
309            and so will be forced on screen below */
310         transient = place_client(self, &place.x, &place.y, settings);
311
312         /* make sure the window is visible. */
313         client_find_onscreen(self, &place.x, &place.y,
314                              place.width, place.height,
315                              /* non-normal clients has less rules, and
316                                 windows that are being restored from a
317                                 session do also. we can assume you want
318                                 it back where you saved it. Clients saying
319                                 they placed themselves are subjected to
320                                 harder rules, ones that are placed by
321                                 place.c or by the user are allowed partially
322                                 off-screen and on xinerama divides (ie,
323                                 it is up to the placement routines to avoid
324                                 the xinerama divides)
325
326                                 splash screens get "transient" set to TRUE by
327                                 the place_client call
328                              */
329                              ob_state() == OB_STATE_RUNNING &&
330                              (transient ||
331                               (!((self->positioned & USPosition) ||
332                                  (settings && settings->pos_given)) &&
333                                client_normal(self) &&
334                                !self->session &&
335                                /* don't move oldschool fullscreen windows to
336                                   fit inside the struts (fixes Acroread, which
337                                   makes its fullscreen window fit the screen
338                                   but it is not USSize'd or USPosition'd) */
339                                !(self->decorations == 0 &&
340                                  RECT_EQUAL(place, *monitor)))));
341     }
342
343     /* if the window isn't user-sized, then make it fit inside
344        the visible screen area on its monitor. Use basically the same rules
345        for forcing the window on screen in the client_find_onscreen call.
346
347        do this after place_client, it chooses the monitor!
348
349        splash screens get "transient" set to TRUE by
350        the place_client call
351     */
352     if (ob_state() == OB_STATE_RUNNING &&
353         (transient ||
354          (!(self->sized & USSize || self->positioned & USPosition) &&
355           client_normal(self) &&
356           !self->session &&
357           /* don't shrink oldschool fullscreen windows to fit inside the
358              struts (fixes Acroread, which makes its fullscreen window
359              fit the screen but it is not USSize'd or USPosition'd) */
360           !(self->decorations == 0 && RECT_EQUAL(place, *monitor)))))
361     {
362         Rect *a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &place);
363
364         Strut size = render_plugin->frame_get_size(self->frame);
365         /* get the size of the frame */
366         place.width += size.left + size.right;
367         place.height += size.top + size.bottom;
368
369         /* fit the window inside the area */
370         place.width = MIN(place.width, a->width);
371         place.height = MIN(place.height, a->height);
372
373         ob_debug("setting window size to %dx%d", place.width, place.height);
374
375         /* get the size of the client back */
376         place.width -= size.left + size.right;
377         place.height -= size.top + size.bottom;
378
379         g_free(a);
380     }
381
382     ob_debug("placing window 0x%x at %d, %d with size %d x %d. "
383              "some restrictions may apply",
384              self->window, place.x, place.y, place.width, place.height);
385     if (self->session)
386         ob_debug("  but session requested %d, %d  %d x %d instead, "
387                  "overriding",
388                  self->session->x, self->session->y,
389                  self->session->w, self->session->h);
390
391     /* do this after the window is placed, so the premax/prefullscreen numbers
392        won't be all wacko!!
393
394        this also places the window
395     */
396     client_apply_startup_state(self, place.x, place.y,
397                                place.width, place.height);
398
399     g_free(monitor);
400     monitor = NULL;
401
402     ob_debug_type(OB_DEBUG_FOCUS, "Going to try activate new window? %s",
403                   activate ? "yes" : "no");
404     if (activate) {
405         gboolean raise = FALSE;
406
407         /* This is focus stealing prevention */
408         ob_debug_type(OB_DEBUG_FOCUS,
409                       "Want to focus new window 0x%x at time %u "
410                       "launched at %u (last user interaction time %u)",
411                       self->window, map_time, launch_time,
412                       event_last_user_time);
413
414         if (menu_frame_visible || render_plugin->moveresize_in_progress) {
415             activate = FALSE;
416             raise = TRUE;
417             ob_debug_type(OB_DEBUG_FOCUS,
418                           "Not focusing the window because the user is inside "
419                           "an Openbox menu or is move/resizing a window and "
420                           "we don't want to interrupt them");
421         }
422
423         /* if it's on another desktop */
424         else if (!(self->desktop == screen_desktop ||
425                    self->desktop == DESKTOP_ALL) &&
426                  /* the timestamp is from before you changed desktops */
427                  launch_time && screen_desktop_user_time &&
428                  !event_time_after(launch_time, screen_desktop_user_time))
429         {
430             activate = FALSE;
431             raise = TRUE;
432             ob_debug_type(OB_DEBUG_FOCUS,
433                           "Not focusing the window because its on another "
434                           "desktop");
435         }
436         /* If something is focused, and it's not our relative... */
437         else if (focus_client && client_search_focus_tree_full(self) == NULL &&
438                  client_search_focus_group_full(self) == NULL)
439         {
440             /* If the user is working in another window right now, then don't
441                steal focus */
442             if (event_last_user_time && launch_time &&
443                 event_time_after(event_last_user_time, launch_time) &&
444                 event_last_user_time != launch_time &&
445                 event_time_after(event_last_user_time,
446                                  map_time - OB_EVENT_USER_TIME_DELAY))
447             {
448                 activate = FALSE;
449                 ob_debug_type(OB_DEBUG_FOCUS,
450                               "Not focusing the window because the user is "
451                               "working in another window");
452             }
453             /* If its a transient (and its parents aren't focused) */
454             else if (client_has_parent(self)) {
455                 activate = FALSE;
456                 ob_debug_type(OB_DEBUG_FOCUS,
457                               "Not focusing the window because it is a "
458                               "transient, and its relatives aren't focused");
459             }
460             /* Don't steal focus from globally active clients.
461                I stole this idea from KWin. It seems nice.
462              */
463             else if (!(focus_client->can_focus ||
464                        focus_client->focus_notify))
465             {
466                 activate = FALSE;
467                 ob_debug_type(OB_DEBUG_FOCUS,
468                               "Not focusing the window because a globally "
469                               "active client has focus");
470             }
471             /* Don't move focus if it's not going to go to this window
472                anyway */
473             else if (client_focus_target(self) != self) {
474                 activate = FALSE;
475                 raise = TRUE;
476                 ob_debug_type(OB_DEBUG_FOCUS,
477                               "Not focusing the window because another window "
478                               "would get the focus anyway");
479             }
480             else if (!(self->desktop == screen_desktop ||
481                        self->desktop == DESKTOP_ALL))
482             {
483                 activate = FALSE;
484                 raise = TRUE;
485                 ob_debug_type(OB_DEBUG_FOCUS,
486                               "Not focusing the window because it is on "
487                               "another desktop and no relatives are focused ");
488             }
489         }
490
491         if (!activate) {
492             ob_debug_type(OB_DEBUG_FOCUS,
493                           "Focus stealing prevention activated for %s at "
494                           "time %u (last user interactioon time %u)",
495                           self->title, map_time, event_last_user_time);
496             /* if the client isn't focused, then hilite it so the user
497                knows it is there */
498             client_hilite(self, TRUE);
499             /* we may want to raise it even tho we're not activating it */
500             if (raise && !client_restore_session_stacking(self))
501                 stacking_raise(CLIENT_AS_WINDOW(self));
502         }
503     }
504     else {
505         /* This may look rather odd. Well it's because new windows are added
506            to the stacking order non-intrusively. If we're not going to focus
507            the new window or hilite it, then we raise it to the top. This will
508            take affect for things that don't get focused like splash screens.
509            Also if you don't have focus_new enabled, then it's going to get
510            raised to the top. Legacy begets legacy I guess?
511         */
512         if (!client_restore_session_stacking(self))
513             stacking_raise(CLIENT_AS_WINDOW(self));
514     }
515
516     mouse_grab_for_client(self, TRUE);
517
518     /* this has to happen before we try focus the window, but we want it to
519        happen after the client's stacking has been determined or it looks bad
520     */
521     {
522         gulong ignore_start;
523         if (!config_focus_under_mouse)
524             ignore_start = event_start_ignore_all_enters();
525
526         client_show(self);
527
528         if (!config_focus_under_mouse)
529             event_end_ignore_all_enters(ignore_start);
530     }
531
532     if (activate) {
533         gboolean stacked = client_restore_session_stacking(self);
534         client_present(self, FALSE, !stacked, TRUE);
535     }
536
537     /* add to client list/map */
538     client_list = g_list_append(client_list, self);
539     window_add(&self->window, CLIENT_AS_WINDOW(self));
540
541     /* this has to happen after we're in the client_list */
542     if (STRUT_EXISTS(self->strut))
543         screen_update_areas();
544
545     /* update the list hints */
546     client_set_list();
547
548     /* watch for when the application stops responding.  only do this for
549        normal windows, i.e. windows which have titlebars and close buttons 
550        and things like that.
551        we don't need to stop pinging on unmanage, because it will be handled
552        automatically by the destroy callback!
553     */
554     if (self->ping && client_normal(self))
555         ping_start(self, client_ping_event);
556
557     /* free the ObAppSettings shallow copy */
558     g_free(settings);
559
560     ob_debug("Managed window 0x%lx plate 0x%x (%s)",
561              window, render_plugin->frame_get_window(self->frame), self->class);
562 }
563
564
565 ObClient *client_fake_manage(Window window)
566 {
567     ObClient *self;
568     ObAppSettings *settings;
569
570     ob_debug("Pretend-managing window: %lx", window);
571
572     /* do this minimal stuff to figure out the client's decorations */
573
574     self = g_new0(ObClient, 1);
575     self->window = window;
576
577     client_get_all(self, FALSE);
578     /* per-app settings override stuff, and return the settings for other
579        uses too. this returns a shallow copy that needs to be freed */
580     settings = client_get_settings_state(self);
581
582     client_setup_decor_and_functions(self, FALSE);
583
584     /* create the decoration frame for the client window and adjust its size */
585     self->frame = render_plugin->frame_new(self);
586     render_plugin->frame_set_decorations (self->frame, self->decorations);
587     render_plugin->frame_update_title (self->frame, self->title);
588     render_plugin->frame_update_layout (self->frame, FALSE, FALSE);
589     render_plugin->frame_update_skin (self->frame);
590
591     ob_debug("gave extents left %d right %d top %d bottom %d",
592              render_plugin->frame_get_size(self->frame).left, render_plugin->frame_get_size(self->frame).right,
593              render_plugin->frame_get_size(self->frame).top, render_plugin->frame_get_size(self->frame).bottom);
594
595     /* free the ObAppSettings shallow copy */
596     g_free(settings);
597
598     return self;
599 }
600
601 void client_unmanage_all(void)
602 {
603     while (client_list != NULL)
604         client_unmanage(client_list->data);
605 }
606
607 void client_unmanage(ObClient *self)
608 {
609     guint j;
610     GSList *it;
611     gulong ignore_start;
612
613     ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)",
614              self->window, render_plugin->frame_get_window(self->frame),
615              self->class, self->title ? self->title : "");
616
617     g_assert(self != NULL);
618
619     /* we dont want events no more. do this before hiding the frame so we
620        don't generate more events */
621     XSelectInput(obt_display, self->window, NoEventMask);
622
623     /* ignore enter events from the unmap so it doesnt mess with the focus */
624     if (!config_focus_under_mouse)
625         ignore_start = event_start_ignore_all_enters();
626
627     self->ignore_unmaps += render_plugin->frame_hide(self->frame);
628     /* flush to send the hide to the server quickly */
629     XFlush(obt_display);
630
631     if (!config_focus_under_mouse)
632         event_end_ignore_all_enters(ignore_start);
633
634     mouse_grab_for_client(self, FALSE);
635
636     /* remove the window from our save set, unless we are managing an internal
637        ObPrompt window */
638     if (!self->prompt)
639         XChangeSaveSet(obt_display, self->window, SetModeDelete);
640
641     /* update the focus lists */
642     focus_order_remove(self);
643     if (client_focused(self)) {
644         /* don't leave an invalid focus_client */
645         focus_client = NULL;
646     }
647
648     /* if we're prompting to kill the client, close that */
649     prompt_unref(self->kill_prompt);
650     self->kill_prompt = NULL;
651
652     client_list = g_list_remove(client_list, self);
653     stacking_remove(self);
654     window_remove(self->window);
655
656     /* once the client is out of the list, update the struts to remove its
657        influence */
658     if (STRUT_EXISTS(self->strut))
659         screen_update_areas();
660
661     client_call_notifies(self, client_destroy_notifies);
662
663     /* tell our parent(s) that we're gone */
664     for (it = self->parents; it; it = g_slist_next(it))
665         ((ObClient*)it->data)->transients =
666             g_slist_remove(((ObClient*)it->data)->transients,self);
667
668     /* tell our transients that we're gone */
669     for (it = self->transients; it; it = g_slist_next(it)) {
670         ((ObClient*)it->data)->parents =
671             g_slist_remove(((ObClient*)it->data)->parents, self);
672         /* we could be keeping our children in a higher layer */
673         client_calc_layer(it->data);
674     }
675
676     /* remove from its group */
677     if (self->group) {
678         group_remove(self->group, self);
679         self->group = NULL;
680     }
681
682     /* restore the window's original geometry so it is not lost */
683     {
684         Rect a;
685
686         a = self->area;
687
688         if (self->fullscreen)
689             a = self->pre_fullscreen_area;
690         else if (self->max_horz || self->max_vert) {
691             if (self->max_horz) {
692                 a.x = self->pre_max_area.x;
693                 a.width = self->pre_max_area.width;
694             }
695             if (self->max_vert) {
696                 a.y = self->pre_max_area.y;
697                 a.height = self->pre_max_area.height;
698             }
699         }
700
701         self->fullscreen = self->max_horz = self->max_vert = FALSE;
702         render_plugin->frame_trigger(self, OB_TRIGGER_UNMAX_VERT);
703         render_plugin->frame_trigger(self, OB_TRIGGER_UNMAX_HORZ);
704         /* let it be moved and resized no matter what */
705         self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE;
706         self->decorations = 0; /* unmanaged windows have no decor */
707
708         /* give the client its border back */
709         XSetWindowBorderWidth(obt_display, self->window, self->border_width);
710
711         client_move_resize(self, a.x, a.y, a.width, a.height);
712     }
713
714     /* reparent the window out of the frame, and free the frame */
715     render_plugin->frame_ungrab(self->frame, window_map);
716     render_plugin->frame_free(self->frame);
717     self->frame = NULL;
718
719     if (ob_state() != OB_STATE_EXITING) {
720         /* these values should not be persisted across a window
721            unmapping/mapping */
722         OBT_PROP_ERASE(self->window, NET_WM_DESKTOP);
723         OBT_PROP_ERASE(self->window, NET_WM_STATE);
724         OBT_PROP_ERASE(self->window, WM_STATE);
725     } else {
726         /* if we're left in an unmapped state, the client wont be mapped.
727            this is bad, since we will no longer be managing the window on
728            restart */
729         XMapWindow(obt_display, self->window);
730     }
731
732     /* these should not be left on the window ever.  other window managers
733        don't necessarily use them and it will mess them up (like compiz) */
734     OBT_PROP_ERASE(self->window, NET_WM_VISIBLE_NAME);
735     OBT_PROP_ERASE(self->window, NET_WM_VISIBLE_ICON_NAME);
736
737     /* update the list hints */
738     client_set_list();
739
740     ob_debug("Unmanaged window 0x%lx", self->window);
741
742     /* free all data allocated in the client struct */
743     g_slist_free(self->transients);
744     for (j = 0; j < self->nicons; ++j)
745         g_free(self->icons[j].data);
746     if (self->nicons > 0)
747         g_free(self->icons);
748     g_free(self->startup_id);
749     g_free(self->wm_command);
750     g_free(self->title);
751     g_free(self->icon_title);
752     g_free(self->original_title);
753     g_free(self->name);
754     g_free(self->class);
755     g_free(self->role);
756     g_free(self->client_machine);
757     g_free(self->sm_client_id);
758     g_free(self);
759 }
760
761 void client_fake_unmanage(ObClient *self)
762 {
763     /* this is all that got allocated to get the decorations */
764
765     render_plugin->frame_free(self->frame);
766     g_free(self);
767 }
768
769 /*! Returns a new structure containing the per-app settings for this client.
770   The returned structure needs to be freed with g_free. */
771 static ObAppSettings *client_get_settings_state(ObClient *self)
772 {
773     ObAppSettings *settings;
774     GSList *it;
775
776     settings = config_create_app_settings();
777
778     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
779         ObAppSettings *app = it->data;
780         gboolean match = TRUE;
781
782         g_assert(app->name != NULL || app->class != NULL);
783
784         /* we know that either name or class is not NULL so it will have to
785            match to use the rule */
786         if (app->name &&
787             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
788             match = FALSE;
789         else if (app->class &&
790                 !g_pattern_match(app->class,
791                                  strlen(self->class), self->class, NULL))
792             match = FALSE;
793         else if (app->role &&
794                  !g_pattern_match(app->role,
795                                   strlen(self->role), self->role, NULL))
796             match = FALSE;
797
798         if (match) {
799             ob_debug("Window matching: %s", app->name);
800
801             /* copy the settings to our struct, overriding the existing
802                settings if they are not defaults */
803             config_app_settings_copy_non_defaults(app, settings);
804         }
805     }
806
807     if (settings->shade != -1)
808         self->shaded = !!settings->shade;
809     if (settings->decor != -1)
810         self->undecorated = !settings->decor;
811     if (settings->iconic != -1)
812         self->iconic = !!settings->iconic;
813     if (settings->skip_pager != -1)
814         self->skip_pager = !!settings->skip_pager;
815     if (settings->skip_taskbar != -1)
816         self->skip_taskbar = !!settings->skip_taskbar;
817
818     if (settings->max_vert != -1)
819         self->max_vert = !!settings->max_vert;
820     if (settings->max_horz != -1)
821         self->max_horz = !!settings->max_horz;
822
823     if (settings->fullscreen != -1)
824         self->fullscreen = !!settings->fullscreen;
825
826     if (settings->desktop) {
827         if (settings->desktop == DESKTOP_ALL)
828             self->desktop = settings->desktop;
829         else if (settings->desktop > 0 &&
830                  settings->desktop <= screen_num_desktops)
831             self->desktop = settings->desktop - 1;
832     }
833
834     if (settings->layer == -1) {
835         self->below = TRUE;
836         self->above = FALSE;
837     }
838     else if (settings->layer == 0) {
839         self->below = FALSE;
840         self->above = FALSE;
841     }
842     else if (settings->layer == 1) {
843         self->below = FALSE;
844         self->above = TRUE;
845     }
846     return settings;
847 }
848
849 static void client_restore_session_state(ObClient *self)
850 {
851     GList *it;
852
853     ob_debug_type(OB_DEBUG_SM,
854                   "Restore session for client %s", self->title);
855
856     if (!(it = session_state_find(self))) {
857         ob_debug_type(OB_DEBUG_SM,
858                       "Session data not found for client %s", self->title);
859         return;
860     }
861
862     self->session = it->data;
863
864     ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s",
865                   self->title);
866
867     RECT_SET_POINT(self->area, self->session->x, self->session->y);
868     self->positioned = USPosition;
869     self->sized = USSize;
870     if (self->session->w > 0)
871         self->area.width = self->session->w;
872     if (self->session->h > 0)
873         self->area.height = self->session->h;
874     XResizeWindow(obt_display, self->window,
875                   self->area.width, self->area.height);
876
877     self->desktop = (self->session->desktop == DESKTOP_ALL ?
878                      self->session->desktop :
879                      MIN(screen_num_desktops - 1, self->session->desktop));
880     OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, self->desktop);
881
882     self->shaded = self->session->shaded;
883     self->iconic = self->session->iconic;
884     self->skip_pager = self->session->skip_pager;
885     self->skip_taskbar = self->session->skip_taskbar;
886     self->fullscreen = self->session->fullscreen;
887     self->above = self->session->above;
888     self->below = self->session->below;
889     self->max_horz = self->session->max_horz;
890     self->max_vert = self->session->max_vert;
891     self->undecorated = self->session->undecorated;
892
893     if (self->session->max_horz) {
894         render_plugin->frame_trigger(self->frame, OB_TRIGGER_MAX_HORZ);
895     }
896     else {
897         render_plugin->frame_trigger(self->frame, OB_TRIGGER_UNMAX_HORZ);
898     }
899     
900     if (self->session->max_vert) {
901         render_plugin->frame_trigger(self->frame, OB_TRIGGER_MAX_VERT);
902     }
903     else {
904         render_plugin->frame_trigger(self->frame, OB_TRIGGER_UNMAX_VERT);
905     }
906 }
907
908 static gboolean client_restore_session_stacking(ObClient *self)
909 {
910     GList *it, *mypos;
911
912     if (!self->session) return FALSE;
913
914     mypos = g_list_find(session_saved_state, self->session);
915     if (!mypos) return FALSE;
916
917     /* start above me and look for the first client */
918     for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
919         GList *cit;
920
921         for (cit = client_list; cit; cit = g_list_next(cit)) {
922             ObClient *c = cit->data;
923             /* found a client that was in the session, so go below it */
924             if (c->session == it->data) {
925                 stacking_below(CLIENT_AS_WINDOW(self),
926                                CLIENT_AS_WINDOW(cit->data));
927                 return TRUE;
928             }
929         }
930     }
931     return FALSE;
932 }
933
934 void client_move_onscreen(ObClient *self, gboolean rude)
935 {
936     gint x = self->area.x;
937     gint y = self->area.y;
938     if (client_find_onscreen(self, &x, &y,
939                              self->area.width,
940                              self->area.height, rude)) {
941         client_move(self, x, y);
942     }
943 }
944
945 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
946                               gboolean rude)
947 {
948     gint ox = *x, oy = *y;
949     gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
950     gint fw, fh;
951     Rect desired;
952     guint i;
953     gboolean found_mon;
954
955     RECT_SET(desired, *x, *y, w, h);
956     frame_rect_to_frame(self, &desired);
957
958     /* get where the frame would be */
959     frame_client_gravity(self, x, y);
960
961     Strut size = render_plugin->frame_get_size(self->frame);
962
963     /* get the requested size of the window with decorations */
964     fw = size.left + w + size.right;
965     fh = size.top + h + size.bottom;
966
967     /* If rudeness wasn't requested, then still be rude in a given direction
968        if the client is not moving, only resizing in that direction */
969     if (!rude) {
970         Point oldtl, oldtr, oldbl, oldbr;
971         Point newtl, newtr, newbl, newbr;
972         gboolean stationary_l, stationary_r, stationary_t, stationary_b;
973
974         Rect area = render_plugin->frame_get_window_area(self->frame);
975         POINT_SET(oldtl, area.x, area.y);
976         POINT_SET(oldbr, area.x + area.width - 1,
977                   area.y + area.height - 1);
978         POINT_SET(oldtr, oldbr.x, oldtl.y);
979         POINT_SET(oldbl, oldtl.x, oldbr.y);
980
981         POINT_SET(newtl, *x, *y);
982         POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
983         POINT_SET(newtr, newbr.x, newtl.y);
984         POINT_SET(newbl, newtl.x, newbr.y);
985
986         /* is it moving or just resizing from some corner? */
987         stationary_l = oldtl.x == newtl.x;
988         stationary_r = oldtr.x == newtr.x;
989         stationary_t = oldtl.y == newtl.y;
990         stationary_b = oldbl.y == newbl.y;
991
992         /* if left edge is growing and didnt move right edge */
993         if (stationary_r && newtl.x < oldtl.x)
994             rudel = TRUE;
995         /* if right edge is growing and didnt move left edge */
996         if (stationary_l && newtr.x > oldtr.x)
997             ruder = TRUE;
998         /* if top edge is growing and didnt move bottom edge */
999         if (stationary_b && newtl.y < oldtl.y)
1000             rudet = TRUE;
1001         /* if bottom edge is growing and didnt move top edge */
1002         if (stationary_t && newbl.y > oldbl.y)
1003             rudeb = TRUE;
1004     }
1005
1006     /* we iterate through every monitor that the window is at least partially
1007        on, to make sure it is obeying the rules on them all
1008
1009        if the window does not appear on any monitors, then use the first one
1010     */
1011     found_mon = FALSE;
1012     for (i = 0; i < screen_num_monitors; ++i) {
1013         Rect *a;
1014
1015         if (!screen_physical_area_monitor_contains(i, &desired)) {
1016             if (i < screen_num_monitors - 1 || found_mon)
1017                 continue;
1018
1019             /* the window is not inside any monitor! so just use the first
1020                one */
1021             a = screen_area(self->desktop, 0, NULL);
1022         } else {
1023             found_mon = TRUE;
1024             a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &desired);
1025         }
1026
1027         /* This makes sure windows aren't entirely outside of the screen so you
1028            can't see them at all.
1029            It makes sure 10% of the window is on the screen at least. At don't
1030            let it move itself off the top of the screen, which would hide the
1031            titlebar on you. (The user can still do this if they want too, it's
1032            only limiting the application.
1033         */
1034         if (client_normal(self)) {
1035             if (!self->strut.right && *x + fw/10 >= a->x + a->width - 1)
1036                 *x = a->x + a->width - fw/10;
1037             if (!self->strut.bottom && *y + fh/10 >= a->y + a->height - 1)
1038                 *y = a->y + a->height - fh/10;
1039             if (!self->strut.left && *x + fw*9/10 - 1 < a->x)
1040                 *x = a->x - fw*9/10;
1041             if (!self->strut.top && *y + fh*9/10 - 1 < a->y)
1042                 *y = a->y - fh*9/10;
1043         }
1044
1045         /* This here doesn't let windows even a pixel outside the
1046            struts/screen. When called from client_manage, programs placing
1047            themselves are forced completely onscreen, while things like
1048            xterm -geometry resolution-width/2 will work fine. Trying to
1049            place it completely offscreen will be handled in the above code.
1050            Sorry for this confused comment, i am tired. */
1051         if (rudel && !self->strut.left && *x < a->x) *x = a->x;
1052         if (ruder && !self->strut.right && *x + fw > a->x + a->width)
1053             *x = a->x + MAX(0, a->width - fw);
1054
1055         if (rudet && !self->strut.top && *y < a->y) *y = a->y;
1056         if (rudeb && !self->strut.bottom && *y + fh > a->y + a->height)
1057             *y = a->y + MAX(0, a->height - fh);
1058
1059         g_free(a);
1060     }
1061
1062     /* get where the client should be */
1063     frame_frame_gravity(self, x, y);
1064
1065     return ox != *x || oy != *y;
1066 }
1067
1068 static void client_get_all(ObClient *self, gboolean real)
1069 {
1070     /* this is needed for the frame to set itself up */
1071     client_get_area(self);
1072
1073     /* these things can change the decor and functions of the window */
1074
1075     client_get_mwm_hints(self);
1076     /* this can change the mwmhints for special cases */
1077     client_get_type_and_transientness(self);
1078     client_get_state(self);
1079     client_update_normal_hints(self);
1080
1081     /* get the session related properties, these can change decorations
1082        from per-app settings */
1083     client_get_session_ids(self);
1084
1085     /* now we got everything that can affect the decorations */
1086     if (!real)
1087         return;
1088
1089     /* get this early so we have it for debugging */
1090     client_update_title(self);
1091
1092     client_update_protocols(self);
1093
1094     client_update_wmhints(self);
1095     /* this may have already been called from client_update_wmhints */
1096     if (!self->parents && !self->transient_for_group)
1097         client_update_transient_for(self);
1098
1099     client_get_startup_id(self);
1100     client_get_desktop(self);/* uses transient data/group/startup id if a
1101                                 desktop is not specified */
1102     client_get_shaped(self);
1103
1104     {
1105         /* a couple type-based defaults for new windows */
1106
1107         /* this makes sure that these windows appear on all desktops */
1108         if (self->type == OB_CLIENT_TYPE_DESKTOP)
1109             self->desktop = DESKTOP_ALL;
1110     }
1111
1112 #ifdef SYNC
1113     client_update_sync_request_counter(self);
1114 #endif
1115
1116     client_get_colormap(self);
1117     client_update_strut(self);
1118     client_update_icons(self);
1119     client_update_icon_geometry(self);
1120 }
1121
1122 static void client_get_startup_id(ObClient *self)
1123 {
1124     if (!(OBT_PROP_GETS(self->window, NET_STARTUP_ID, utf8,
1125                         &self->startup_id)))
1126         if (self->group)
1127             OBT_PROP_GETS(self->group->leader,
1128                           NET_STARTUP_ID, utf8, &self->startup_id);
1129 }
1130
1131 static void client_get_area(ObClient *self)
1132 {
1133     XWindowAttributes wattrib;
1134     Status ret;
1135
1136     ret = XGetWindowAttributes(obt_display, self->window, &wattrib);
1137     g_assert(ret != BadWindow);
1138
1139     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1140     POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1141     self->border_width = wattrib.border_width;
1142
1143     ob_debug("client area: %d %d  %d %d  bw %d", wattrib.x, wattrib.y,
1144              wattrib.width, wattrib.height, wattrib.border_width);
1145 }
1146
1147 static void client_get_desktop(ObClient *self)
1148 {
1149     guint32 d = screen_num_desktops; /* an always-invalid value */
1150
1151     if (OBT_PROP_GET32(self->window, NET_WM_DESKTOP, CARDINAL, &d)) {
1152         if (d >= screen_num_desktops && d != DESKTOP_ALL)
1153             self->desktop = screen_num_desktops - 1;
1154         else
1155             self->desktop = d;
1156         ob_debug("client requested desktop 0x%x", self->desktop);
1157     } else {
1158         GSList *it;
1159         gboolean first = TRUE;
1160         guint all = screen_num_desktops; /* not a valid value */
1161
1162         /* if they are all on one desktop, then open it on the
1163            same desktop */
1164         for (it = self->parents; it; it = g_slist_next(it)) {
1165             ObClient *c = it->data;
1166
1167             if (c->desktop == DESKTOP_ALL) continue;
1168
1169             if (first) {
1170                 all = c->desktop;
1171                 first = FALSE;
1172             }
1173             else if (all != c->desktop)
1174                 all = screen_num_desktops; /* make it invalid */
1175         }
1176         if (all != screen_num_desktops) {
1177             self->desktop = all;
1178
1179             ob_debug("client desktop set from parents: 0x%x",
1180                      self->desktop);
1181         }
1182         /* try get from the startup-notification protocol */
1183         else if (sn_get_desktop(self->startup_id, &self->desktop)) {
1184             if (self->desktop >= screen_num_desktops &&
1185                 self->desktop != DESKTOP_ALL)
1186                 self->desktop = screen_num_desktops - 1;
1187             ob_debug("client desktop set from startup-notification: 0x%x",
1188                      self->desktop);
1189         }
1190         /* defaults to the current desktop */
1191         else {
1192             self->desktop = screen_desktop;
1193             ob_debug("client desktop set to the current desktop: %d",
1194                      self->desktop);
1195         }
1196     }
1197 }
1198
1199 static void client_get_state(ObClient *self)
1200 {
1201     guint32 *state;
1202     guint num;
1203
1204     if (OBT_PROP_GETA32(self->window, NET_WM_STATE, ATOM, &state, &num)) {
1205         gulong i;
1206         for (i = 0; i < num; ++i) {
1207             if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
1208                 self->modal = TRUE;
1209             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
1210                 self->shaded = TRUE;
1211             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
1212                 self->iconic = TRUE;
1213             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
1214                 self->skip_taskbar = TRUE;
1215             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
1216                 self->skip_pager = TRUE;
1217             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
1218                 self->fullscreen = TRUE;
1219             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
1220                 self->max_vert = TRUE;
1221                 render_plugin->frame_trigger(self->frame, OB_TRIGGER_MAX_VERT);
1222             }
1223             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
1224                 self->max_horz = TRUE;
1225                 render_plugin->frame_trigger(self->frame, OB_TRIGGER_MAX_HORZ);
1226             }
1227             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
1228                 self->above = TRUE;
1229             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
1230                 self->below = TRUE;
1231             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
1232                 self->demands_attention = TRUE;
1233             else if (state[i] == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
1234                 self->undecorated = TRUE;
1235         }
1236
1237         g_free(state);
1238     }
1239 }
1240
1241 static void client_get_shaped(ObClient *self)
1242 {
1243     self->shaped = FALSE;
1244 #ifdef   SHAPE
1245     if (obt_display_extension_shape) {
1246         gint foo;
1247         guint ufoo;
1248         gint s;
1249
1250         XShapeSelectInput(obt_display, self->window, ShapeNotifyMask);
1251
1252         XShapeQueryExtents(obt_display, self->window, &s, &foo,
1253                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1254                            &ufoo);
1255         self->shaped = (s != 0);
1256     }
1257 #endif
1258 }
1259
1260 void client_update_transient_for(ObClient *self)
1261 {
1262     Window t = None;
1263     ObClient *target = NULL;
1264     gboolean trangroup = FALSE;
1265
1266     if (XGetTransientForHint(obt_display, self->window, &t)) {
1267         if (t != self->window) { /* cant be transient to itself! */
1268             ObWindow *tw = window_find(t);
1269             /* if this happens then we need to check for it*/
1270             g_assert(tw != CLIENT_AS_WINDOW(self));
1271             if (tw && WINDOW_IS_CLIENT(tw)) {
1272                 /* watch out for windows with a parent that is something
1273                    different, like a dockapp for example */
1274                 target = WINDOW_AS_CLIENT(tw);
1275             }
1276         }
1277
1278         /* Setting the transient_for to Root is actually illegal, however
1279            applications from time have done this to specify transient for
1280            their group */
1281         if (!target && self->group && t == obt_root(ob_screen))
1282             trangroup = TRUE;
1283     } else if (self->group && self->transient)
1284         trangroup = TRUE;
1285
1286     client_update_transient_tree(self, self->group, self->group,
1287                                  self->transient_for_group, trangroup,
1288                                  client_direct_parent(self), target);
1289     self->transient_for_group = trangroup;
1290
1291 }
1292
1293 static void client_update_transient_tree(ObClient *self,
1294                                          ObGroup *oldgroup, ObGroup *newgroup,
1295                                          gboolean oldgtran, gboolean newgtran,
1296                                          ObClient* oldparent,
1297                                          ObClient *newparent)
1298 {
1299     GSList *it, *next;
1300     ObClient *c;
1301
1302     g_assert(!oldgtran || oldgroup);
1303     g_assert(!newgtran || newgroup);
1304     g_assert((!oldgtran && !oldparent) ||
1305              (oldgtran && !oldparent) ||
1306              (!oldgtran && oldparent));
1307     g_assert((!newgtran && !newparent) ||
1308              (newgtran && !newparent) ||
1309              (!newgtran && newparent));
1310
1311     /* * *
1312       Group transient windows are not allowed to have other group
1313       transient windows as their children.
1314       * * */
1315
1316
1317     /* No change has occured */
1318     if (oldgroup == newgroup &&
1319         oldgtran == newgtran &&
1320         oldparent == newparent) return;
1321
1322     /** Remove the client from the transient tree **/
1323
1324     for (it = self->transients; it; it = next) {
1325         next = g_slist_next(it);
1326         c = it->data;
1327         self->transients = g_slist_delete_link(self->transients, it);
1328         c->parents = g_slist_remove(c->parents, self);
1329     }
1330     for (it = self->parents; it; it = next) {
1331         next = g_slist_next(it);
1332         c = it->data;
1333         self->parents = g_slist_delete_link(self->parents, it);
1334         c->transients = g_slist_remove(c->transients, self);
1335     }
1336
1337     /** Re-add the client to the transient tree **/
1338
1339     /* If we're transient for a group then we need to add ourselves to all our
1340        parents */
1341     if (newgtran) {
1342         for (it = newgroup->members; it; it = g_slist_next(it)) {
1343             c = it->data;
1344             if (c != self &&
1345                 !client_search_top_direct_parent(c)->transient_for_group &&
1346                 client_normal(c))
1347             {
1348                 c->transients = g_slist_prepend(c->transients, self);
1349                 self->parents = g_slist_prepend(self->parents, c);
1350             }
1351         }
1352     }
1353
1354     /* If we are now transient for a single window we need to add ourselves to
1355        its children
1356
1357        WARNING: Cyclical transient ness is possible if two windows are
1358        transient for eachother.
1359     */
1360     else if (newparent &&
1361              /* don't make ourself its child if it is already our child */
1362              !client_is_direct_child(self, newparent) &&
1363              client_normal(newparent))
1364     {
1365         newparent->transients = g_slist_prepend(newparent->transients, self);
1366         self->parents = g_slist_prepend(self->parents, newparent);
1367     }
1368
1369     /* Add any group transient windows to our children. But if we're transient
1370        for the group, then other group transients are not our children.
1371
1372        WARNING: Cyclical transient-ness is possible. For e.g. if:
1373        A is transient for the group
1374        B is transient for A
1375        C is transient for B
1376        A can't be transient for C or we have a cycle
1377     */
1378     if (!newgtran && newgroup &&
1379         (!newparent ||
1380          !client_search_top_direct_parent(newparent)->transient_for_group) &&
1381         client_normal(self))
1382     {
1383         for (it = newgroup->members; it; it = g_slist_next(it)) {
1384             c = it->data;
1385             if (c != self && c->transient_for_group &&
1386                 /* Don't make it our child if it is already our parent */
1387                 !client_is_direct_child(c, self))
1388             {
1389                 self->transients = g_slist_prepend(self->transients, c);
1390                 c->parents = g_slist_prepend(c->parents, self);
1391             }
1392         }
1393     }
1394
1395     /** If we change our group transient-ness, our children change their
1396         effect group transient-ness, which affects how they relate to other
1397         group windows **/
1398
1399     for (it = self->transients; it; it = g_slist_next(it)) {
1400         c = it->data;
1401         if (!c->transient_for_group)
1402             client_update_transient_tree(c, c->group, c->group,
1403                                          c->transient_for_group,
1404                                          c->transient_for_group,
1405                                          client_direct_parent(c),
1406                                          client_direct_parent(c));
1407     }
1408 }
1409
1410 static void client_get_mwm_hints(ObClient *self)
1411 {
1412     guint num;
1413     guint32 *hints;
1414
1415     self->mwmhints.flags = 0; /* default to none */
1416
1417     if (OBT_PROP_GETA32(self->window, MOTIF_WM_HINTS, MOTIF_WM_HINTS,
1418                         &hints, &num)) {
1419         if (num >= OB_MWM_ELEMENTS) {
1420             self->mwmhints.flags = hints[0];
1421             self->mwmhints.functions = hints[1];
1422             self->mwmhints.decorations = hints[2];
1423         }
1424         g_free(hints);
1425     }
1426 }
1427
1428 void client_get_type_and_transientness(ObClient *self)
1429 {
1430     guint num, i;
1431     guint32 *val;
1432     Window t;
1433
1434     self->type = -1;
1435     self->transient = FALSE;
1436
1437     if (OBT_PROP_GETA32(self->window, NET_WM_WINDOW_TYPE, ATOM, &val, &num)) {
1438         /* use the first value that we know about in the array */
1439         for (i = 0; i < num; ++i) {
1440             if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DESKTOP))
1441                 self->type = OB_CLIENT_TYPE_DESKTOP;
1442             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DOCK))
1443                 self->type = OB_CLIENT_TYPE_DOCK;
1444             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_TOOLBAR))
1445                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1446             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_MENU))
1447                 self->type = OB_CLIENT_TYPE_MENU;
1448             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_UTILITY))
1449                 self->type = OB_CLIENT_TYPE_UTILITY;
1450             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_SPLASH))
1451                 self->type = OB_CLIENT_TYPE_SPLASH;
1452             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DIALOG))
1453                 self->type = OB_CLIENT_TYPE_DIALOG;
1454             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_NORMAL))
1455                 self->type = OB_CLIENT_TYPE_NORMAL;
1456             else if (val[i] == OBT_PROP_ATOM(KDE_NET_WM_WINDOW_TYPE_OVERRIDE))
1457             {
1458                 /* prevent this window from getting any decor or
1459                    functionality */
1460                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1461                                          OB_MWM_FLAG_DECORATIONS);
1462                 self->mwmhints.decorations = 0;
1463                 self->mwmhints.functions = 0;
1464             }
1465             if (self->type != (ObClientType) -1)
1466                 break; /* grab the first legit type */
1467         }
1468         g_free(val);
1469     }
1470
1471     if (XGetTransientForHint(obt_display, self->window, &t))
1472         self->transient = TRUE;
1473
1474     if (self->type == (ObClientType) -1) {
1475         /*the window type hint was not set, which means we either classify
1476           ourself as a normal window or a dialog, depending on if we are a
1477           transient. */
1478         if (self->transient)
1479             self->type = OB_CLIENT_TYPE_DIALOG;
1480         else
1481             self->type = OB_CLIENT_TYPE_NORMAL;
1482     }
1483
1484     /* then, based on our type, we can update our transientness.. */
1485     if (self->type == OB_CLIENT_TYPE_DIALOG ||
1486         self->type == OB_CLIENT_TYPE_TOOLBAR ||
1487         self->type == OB_CLIENT_TYPE_MENU ||
1488         self->type == OB_CLIENT_TYPE_UTILITY)
1489     {
1490         self->transient = TRUE;
1491     }
1492 }
1493
1494 void client_update_protocols(ObClient *self)
1495 {
1496     guint32 *proto;
1497     guint num_ret, i;
1498
1499     self->focus_notify = FALSE;
1500     self->delete_window = FALSE;
1501
1502     if (OBT_PROP_GETA32(self->window, WM_PROTOCOLS, ATOM, &proto, &num_ret)) {
1503         for (i = 0; i < num_ret; ++i) {
1504             if (proto[i] == OBT_PROP_ATOM(WM_DELETE_WINDOW))
1505                 /* this means we can request the window to close */
1506                 self->delete_window = TRUE;
1507             else if (proto[i] == OBT_PROP_ATOM(WM_TAKE_FOCUS))
1508                 /* if this protocol is requested, then the window will be
1509                    notified whenever we want it to receive focus */
1510                 self->focus_notify = TRUE;
1511             else if (proto[i] == OBT_PROP_ATOM(NET_WM_PING))
1512                 /* if this protocol is requested, then the window will allow
1513                    pings to determine if it is still alive */
1514                 self->ping = TRUE;
1515 #ifdef SYNC
1516             else if (proto[i] == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST))
1517                 /* if this protocol is requested, then resizing the
1518                    window will be synchronized between the frame and the
1519                    client */
1520                 self->sync_request = TRUE;
1521 #endif
1522         }
1523         g_free(proto);
1524     }
1525 }
1526
1527 #ifdef SYNC
1528 void client_update_sync_request_counter(ObClient *self)
1529 {
1530     guint32 i;
1531
1532     if (OBT_PROP_GET32(self->window, NET_WM_SYNC_REQUEST_COUNTER, CARDINAL,&i))
1533     {
1534         self->sync_counter = i;
1535     } else
1536         self->sync_counter = None;
1537 }
1538 #endif
1539
1540 static void client_get_colormap(ObClient *self)
1541 {
1542     XWindowAttributes wa;
1543
1544     if (XGetWindowAttributes(obt_display, self->window, &wa))
1545         client_update_colormap(self, wa.colormap);
1546 }
1547
1548 void client_update_colormap(ObClient *self, Colormap colormap)
1549 {
1550     if (colormap == self->colormap) return;
1551
1552     ob_debug("Setting client %s colormap: 0x%x", self->title, colormap);
1553
1554     if (client_focused(self)) {
1555         screen_install_colormap(self, FALSE); /* uninstall old one */
1556         self->colormap = colormap;
1557         screen_install_colormap(self, FALSE); /* install new one */
1558     } else
1559         self->colormap = colormap;
1560 }
1561
1562 void client_update_normal_hints(ObClient *self)
1563 {
1564     XSizeHints size;
1565     glong ret;
1566
1567     /* defaults */
1568     self->min_ratio = 0.0f;
1569     self->max_ratio = 0.0f;
1570     SIZE_SET(self->size_inc, 1, 1);
1571     SIZE_SET(self->base_size, 0, 0);
1572     SIZE_SET(self->min_size, 0, 0);
1573     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1574
1575     /* get the hints from the window */
1576     if (XGetWMNormalHints(obt_display, self->window, &size, &ret)) {
1577         /* normal windows can't request placement! har har
1578         if (!client_normal(self))
1579         */
1580         self->positioned = (size.flags & (PPosition|USPosition));
1581         self->sized = (size.flags & (PSize|USSize));
1582
1583         if (size.flags & PWinGravity)
1584             self->gravity = size.win_gravity;
1585
1586         if (size.flags & PAspect) {
1587             if (size.min_aspect.y)
1588                 self->min_ratio =
1589                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1590             if (size.max_aspect.y)
1591                 self->max_ratio =
1592                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1593         }
1594
1595         if (size.flags & PMinSize)
1596             SIZE_SET(self->min_size, size.min_width, size.min_height);
1597
1598         if (size.flags & PMaxSize)
1599             SIZE_SET(self->max_size, size.max_width, size.max_height);
1600
1601         if (size.flags & PBaseSize)
1602             SIZE_SET(self->base_size, size.base_width, size.base_height);
1603
1604         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1605             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1606
1607         ob_debug("Normal hints: min size (%d %d) max size (%d %d)",
1608                  self->min_size.width, self->min_size.height,
1609                  self->max_size.width, self->max_size.height);
1610         ob_debug("size inc (%d %d) base size (%d %d)",
1611                  self->size_inc.width, self->size_inc.height,
1612                  self->base_size.width, self->base_size.height);
1613     }
1614     else
1615         ob_debug("Normal hints: not set");
1616 }
1617
1618 void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
1619 {
1620     /* start with everything (cept fullscreen) */
1621     self->decorations =
1622         (OB_FRAME_DECOR_TITLEBAR |
1623          OB_FRAME_DECOR_HANDLE |
1624          OB_FRAME_DECOR_GRIPS |
1625          OB_FRAME_DECOR_BORDER |
1626          OB_FRAME_DECOR_ICON |
1627          OB_FRAME_DECOR_ALLDESKTOPS |
1628          OB_FRAME_DECOR_ICONIFY |
1629          OB_FRAME_DECOR_MAXIMIZE |
1630          OB_FRAME_DECOR_SHADE |
1631          OB_FRAME_DECOR_CLOSE);
1632     self->functions =
1633         (OB_CLIENT_FUNC_RESIZE |
1634          OB_CLIENT_FUNC_MOVE |
1635          OB_CLIENT_FUNC_ICONIFY |
1636          OB_CLIENT_FUNC_MAXIMIZE |
1637          OB_CLIENT_FUNC_SHADE |
1638          OB_CLIENT_FUNC_CLOSE |
1639          OB_CLIENT_FUNC_BELOW |
1640          OB_CLIENT_FUNC_ABOVE |
1641          OB_CLIENT_FUNC_UNDECORATE);
1642
1643     if (!(self->min_size.width < self->max_size.width ||
1644           self->min_size.height < self->max_size.height))
1645         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1646
1647     switch (self->type) {
1648     case OB_CLIENT_TYPE_NORMAL:
1649         /* normal windows retain all of the possible decorations and
1650            functionality, and can be fullscreen */
1651         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1652         break;
1653
1654     case OB_CLIENT_TYPE_DIALOG:
1655         /* sometimes apps make dialog windows fullscreen for some reason (for
1656            e.g. kpdf does this..) */
1657         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1658         break;
1659
1660     case OB_CLIENT_TYPE_UTILITY:
1661         /* these windows don't have anything added or removed by default */
1662         break;
1663
1664     case OB_CLIENT_TYPE_MENU:
1665     case OB_CLIENT_TYPE_TOOLBAR:
1666         /* these windows can't iconify or maximize */
1667         self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1668                                OB_FRAME_DECOR_MAXIMIZE);
1669         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1670                              OB_CLIENT_FUNC_MAXIMIZE);
1671         break;
1672
1673     case OB_CLIENT_TYPE_SPLASH:
1674         /* these don't get get any decorations, and the only thing you can
1675            do with them is move them */
1676         self->decorations = 0;
1677         self->functions = OB_CLIENT_FUNC_MOVE;
1678         break;
1679
1680     case OB_CLIENT_TYPE_DESKTOP:
1681         /* these windows are not manipulated by the window manager */
1682         self->decorations = 0;
1683         self->functions = 0;
1684         break;
1685
1686     case OB_CLIENT_TYPE_DOCK:
1687         /* these windows are not manipulated by the window manager, but they
1688            can set below layer which has a special meaning */
1689         self->decorations = 0;
1690         self->functions = OB_CLIENT_FUNC_BELOW;
1691         break;
1692     }
1693
1694     /* Mwm Hints are applied subtractively to what has already been chosen for
1695        decor and functionality */
1696     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1697         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1698             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1699                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1700             {
1701                 /* if the mwm hints request no handle or title, then all
1702                    decorations are disabled, but keep the border if that's
1703                    specified */
1704                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1705                     self->decorations = OB_FRAME_DECOR_BORDER;
1706                 else
1707                     self->decorations = 0;
1708             }
1709         }
1710     }
1711
1712     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1713         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1714             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1715                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1716             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1717                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1718             /* dont let mwm hints kill any buttons
1719                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1720                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1721                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1722                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1723             */
1724             /* dont let mwm hints kill the close button
1725                if (! (self->mwmhints.functions & MwmFunc_Close))
1726                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1727         }
1728     }
1729
1730     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1731         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1732     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1733         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1734     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1735         self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1736
1737     /* can't maximize without moving/resizing */
1738     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1739           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1740           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1741         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1742         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1743     }
1744
1745     if (self->max_horz && self->max_vert) {
1746         /* you can't resize fully maximized windows */
1747         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1748         /* kill the handle on fully maxed windows */
1749         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1750     }
1751
1752     /* If there are no decorations to remove, don't allow the user to try
1753        toggle the state */
1754     if (self->decorations == 0)
1755         self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1756
1757     /* finally, the user can have requested no decorations, which overrides
1758        everything (but doesnt give it a border if it doesnt have one) */
1759     if (self->undecorated)
1760         self->decorations = 0;
1761
1762     /* if we don't have a titlebar, then we cannot shade! */
1763     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1764         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1765
1766     /* now we need to check against rules for the client's current state */
1767     if (self->fullscreen) {
1768         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1769                             OB_CLIENT_FUNC_FULLSCREEN |
1770                             OB_CLIENT_FUNC_ICONIFY);
1771         self->decorations = 0;
1772     }
1773
1774     client_change_allowed_actions(self);
1775
1776     if (reconfig)
1777         /* force reconfigure to make sure decorations are updated */
1778         client_reconfigure(self, TRUE);
1779 }
1780
1781 static void client_change_allowed_actions(ObClient *self)
1782 {
1783     gulong actions[12];
1784     gint num = 0;
1785
1786     /* desktop windows are kept on all desktops */
1787     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1788         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CHANGE_DESKTOP);
1789
1790     if (self->functions & OB_CLIENT_FUNC_SHADE)
1791         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_SHADE);
1792     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1793         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CLOSE);
1794     if (self->functions & OB_CLIENT_FUNC_MOVE)
1795         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MOVE);
1796     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1797         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MINIMIZE);
1798     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1799         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_RESIZE);
1800     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1801         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_FULLSCREEN);
1802     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1803         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_HORZ);
1804         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_VERT);
1805     }
1806     if (self->functions & OB_CLIENT_FUNC_ABOVE)
1807         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_ABOVE);
1808     if (self->functions & OB_CLIENT_FUNC_BELOW)
1809         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_BELOW);
1810     if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1811         actions[num++] = OBT_PROP_ATOM(OB_WM_ACTION_UNDECORATE);
1812
1813     OBT_PROP_SETA32(self->window, NET_WM_ALLOWED_ACTIONS, ATOM, actions, num);
1814
1815     /* make sure the window isn't breaking any rules now
1816
1817        don't check ICONIFY here.  just cuz a window can't iconify doesnt mean
1818        it can't be iconified with its parent
1819     */
1820
1821     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1822         if (self->frame) client_shade(self, FALSE);
1823         else self->shaded = FALSE;
1824     }
1825     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1826         if (self->frame) client_fullscreen(self, FALSE);
1827         else self->fullscreen = FALSE;
1828     }
1829     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1830                                                          self->max_vert)) {
1831         if (self->frame) client_maximize(self, FALSE, 0);
1832         else {
1833             self->max_vert = self->max_horz = FALSE;
1834             render_plugin->frame_trigger (self->frame, OB_TRIGGER_UNMAX_VERT);
1835             render_plugin->frame_trigger (self->frame, OB_TRIGGER_UNMAX_HORZ);
1836         }
1837
1838     }
1839 }
1840
1841 void client_update_wmhints(ObClient *self)
1842 {
1843     XWMHints *hints;
1844
1845     /* assume a window takes input if it doesnt specify */
1846     self->can_focus = TRUE;
1847
1848     if ((hints = XGetWMHints(obt_display, self->window)) != NULL) {
1849         gboolean ur;
1850
1851         if (hints->flags & InputHint)
1852             self->can_focus = hints->input;
1853
1854         /* only do this when first managing the window *AND* when we aren't
1855            starting up! */
1856         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1857             if (hints->flags & StateHint)
1858                 self->iconic = hints->initial_state == IconicState;
1859
1860         ur = self->urgent;
1861         self->urgent = (hints->flags & XUrgencyHint);
1862         if (self->urgent && !ur)
1863             client_hilite(self, TRUE);
1864         else if (!self->urgent && ur && self->demands_attention)
1865             client_hilite(self, FALSE);
1866
1867         if (!(hints->flags & WindowGroupHint))
1868             hints->window_group = None;
1869
1870         /* did the group state change? */
1871         if (hints->window_group !=
1872             (self->group ? self->group->leader : None))
1873         {
1874             ObGroup *oldgroup = self->group;
1875
1876             /* remove from the old group if there was one */
1877             if (self->group != NULL) {
1878                 group_remove(self->group, self);
1879                 self->group = NULL;
1880             }
1881
1882             /* add ourself to the group if we have one */
1883             if (hints->window_group != None) {
1884                 self->group = group_add(hints->window_group, self);
1885             }
1886
1887             /* Put ourselves into the new group's transient tree, and remove
1888                ourselves from the old group's */
1889             client_update_transient_tree(self, oldgroup, self->group,
1890                                          self->transient_for_group,
1891                                          self->transient_for_group,
1892                                          client_direct_parent(self),
1893                                          client_direct_parent(self));
1894
1895             /* Lastly, being in a group, or not, can change if the window is
1896                transient for anything.
1897
1898                The logic for this is:
1899                self->transient = TRUE always if the window wants to be
1900                transient for something, even if transient_for was NULL because
1901                it wasn't in a group before.
1902
1903                If parents was NULL and oldgroup was NULL we can assume
1904                that when we add the new group, it will become transient for
1905                something.
1906
1907                If transient_for_group is TRUE, then it must have already
1908                had a group. If it is getting a new group, the above call to
1909                client_update_transient_tree has already taken care of
1910                everything ! If it is losing all group status then it will
1911                no longer be transient for anything and that needs to be
1912                updated.
1913             */
1914             if (self->transient &&
1915                 ((self->parents == NULL && oldgroup == NULL) ||
1916                  (self->transient_for_group && !self->group)))
1917                 client_update_transient_for(self);
1918         }
1919
1920         /* the WM_HINTS can contain an icon */
1921         if (hints->flags & IconPixmapHint)
1922             client_update_icons(self);
1923
1924         XFree(hints);
1925     }
1926 }
1927
1928 void client_update_title(ObClient *self)
1929 {
1930     gchar *data = NULL;
1931     gchar *visible = NULL;
1932
1933     g_free(self->title);
1934     g_free(self->original_title);
1935
1936     /* try netwm */
1937     if (!OBT_PROP_GETS(self->window, NET_WM_NAME, utf8, &data)) {
1938         /* try old x stuff */
1939         if (!(OBT_PROP_GETS(self->window, WM_NAME, locale, &data)
1940               || OBT_PROP_GETS(self->window, WM_NAME, utf8, &data))) {
1941             if (self->transient) {
1942     /*
1943     GNOME alert windows are not given titles:
1944     http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1945     */
1946                 data = g_strdup("");
1947             } else
1948                 data = g_strdup("Unnamed Window");
1949         }
1950     }
1951     self->original_title = g_strdup(data);
1952
1953     if (self->client_machine) {
1954         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1955         g_free(data);
1956     } else
1957         visible = data;
1958
1959     if (self->not_responding) {
1960         data = visible;
1961         if (self->kill_level > 0)
1962             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1963         else
1964             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
1965         g_free(data);
1966     }
1967
1968     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_NAME, utf8, visible);
1969     self->title = visible;
1970
1971     if (self->frame) {
1972         /* update title render */
1973         render_plugin->frame_update_title (self->frame, self->title);
1974         render_plugin->frame_update_skin (self->frame);
1975     }
1976
1977     /* update the icon title */
1978     data = NULL;
1979     g_free(self->icon_title);
1980
1981     /* try netwm */
1982     if (!OBT_PROP_GETS(self->window, NET_WM_ICON_NAME, utf8, &data))
1983         /* try old x stuff */
1984         if (!(OBT_PROP_GETS(self->window, WM_ICON_NAME, locale, &data) ||
1985               OBT_PROP_GETS(self->window, WM_ICON_NAME, utf8, &data)))
1986             data = g_strdup(self->title);
1987
1988     if (self->client_machine) {
1989         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1990         g_free(data);
1991     } else
1992         visible = data;
1993
1994     if (self->not_responding) {
1995         data = visible;
1996         if (self->kill_level > 0)
1997             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1998         else
1999             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
2000         g_free(data);
2001     }
2002
2003     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_ICON_NAME, utf8, visible);
2004     self->icon_title = visible;
2005 }
2006
2007 void client_update_strut(ObClient *self)
2008 {
2009     guint num;
2010     guint32 *data;
2011     gboolean got = FALSE;
2012     StrutPartial strut;
2013
2014     if (OBT_PROP_GETA32(self->window, NET_WM_STRUT_PARTIAL, CARDINAL,
2015                         &data, &num))
2016     {
2017         if (num == 12) {
2018             got = TRUE;
2019             STRUT_PARTIAL_SET(strut,
2020                               data[0], data[2], data[1], data[3],
2021                               data[4], data[5], data[8], data[9],
2022                               data[6], data[7], data[10], data[11]);
2023         }
2024         g_free(data);
2025     }
2026
2027     if (!got &&
2028         OBT_PROP_GETA32(self->window, NET_WM_STRUT, CARDINAL, &data, &num)) {
2029         if (num == 4) {
2030             Rect *a;
2031
2032             got = TRUE;
2033
2034             /* use the screen's width/height */
2035             a = screen_physical_area_all_monitors();
2036
2037             STRUT_PARTIAL_SET(strut,
2038                               data[0], data[2], data[1], data[3],
2039                               a->y, a->y + a->height - 1,
2040                               a->x, a->x + a->width - 1,
2041                               a->y, a->y + a->height - 1,
2042                               a->x, a->x + a->width - 1);
2043             g_free(a);
2044         }
2045         g_free(data);
2046     }
2047
2048     if (!got)
2049         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2050                           0, 0, 0, 0, 0, 0, 0, 0);
2051
2052     if (!STRUT_EQUAL(strut, self->strut)) {
2053         self->strut = strut;
2054
2055         /* updating here is pointless while we're being mapped cuz we're not in
2056            the client list yet */
2057         if (self->frame)
2058             screen_update_areas();
2059     }
2060 }
2061
2062 /* Avoid storing icons above this size if possible */
2063 #define AVOID_ABOVE 64
2064
2065 void client_update_icons(ObClient *self)
2066 {
2067     guint num;
2068     guint32 *data;
2069     guint w, h, i, j;
2070     guint num_seen;  /* number of icons present */
2071     guint num_small_seen;  /* number of icons small enough present */
2072     guint smallest, smallest_area;
2073
2074     for (i = 0; i < self->nicons; ++i)
2075         g_free(self->icons[i].data);
2076     if (self->nicons > 0)
2077         g_free(self->icons);
2078     self->nicons = 0;
2079
2080     if (OBT_PROP_GETA32(self->window, NET_WM_ICON, CARDINAL, &data, &num)) {
2081         /* figure out how many valid icons are in here */
2082         i = 0;
2083         num_seen = num_small_seen = 0;
2084         smallest = smallest_area = 0;
2085         if (num > 2)
2086             while (i < num) {
2087                 w = data[i++];
2088                 h = data[i++];
2089                 i += w * h;
2090                 /* watch for it being too small for the specified size, or for
2091                    zero sized icons. */
2092                 if (i > num || w == 0 || h == 0) break;
2093
2094                 if (!smallest_area || w*h < smallest_area) {
2095                     smallest = num_seen;
2096                     smallest_area = w*h;
2097                 }
2098                 ++num_seen;
2099                 if (w <= AVOID_ABOVE && h <= AVOID_ABOVE)
2100                     ++num_small_seen;
2101             }
2102         if (num_small_seen > 0)
2103             self->nicons = num_small_seen;
2104         else if (num_seen)
2105             self->nicons = 1;
2106
2107         self->icons = g_new(ObClientIcon, self->nicons);
2108
2109         /* store the icons */
2110         i = 0;
2111         for (j = 0; j < self->nicons;) {
2112             guint x, y, t;
2113
2114             w = self->icons[j].width = data[i++];
2115             h = self->icons[j].height = data[i++];
2116
2117             /* if there are some icons smaller than the threshold, we're
2118                skipping all the ones above */
2119             if (num_small_seen > 0) {
2120                 if (w > AVOID_ABOVE || h > AVOID_ABOVE) {
2121                     i += w*h;
2122                     continue;
2123                 }
2124             }
2125             /* if there were no icons smaller than the threshold, then we are
2126                only taking the smallest available one we saw */
2127             else if (j != smallest) {
2128                 i += w*h;
2129                 continue;
2130             }
2131
2132             self->icons[j].data = g_new(RrPixel32, w * h);
2133             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
2134                 if (x >= w) {
2135                     x = 0;
2136                     ++y;
2137                 }
2138                 self->icons[j].data[t] =
2139                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2140                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
2141                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
2142                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
2143             }
2144             g_assert(i <= num);
2145
2146             ++j;
2147         }
2148
2149         g_free(data);
2150     } else {
2151         XWMHints *hints;
2152
2153         if ((hints = XGetWMHints(obt_display, self->window))) {
2154             if (hints->flags & IconPixmapHint) {
2155                 self->nicons = 1;
2156                 self->icons = g_new(ObClientIcon, self->nicons);
2157                 obt_display_ignore_errors(TRUE);
2158                 if (!RrPixmapToRGBA(ob_rr_inst,
2159                                     hints->icon_pixmap,
2160                                     (hints->flags & IconMaskHint ?
2161                                      hints->icon_mask : None),
2162                                     &self->icons[0].width,
2163                                     &self->icons[0].height,
2164                                     &self->icons[0].data))
2165                 {
2166                     g_free(self->icons);
2167                     self->nicons = 0;
2168                 }
2169                 obt_display_ignore_errors(FALSE);
2170             }
2171             XFree(hints);
2172         }
2173     }
2174
2175     /* set the default icon onto the window
2176        in theory, this could be a race, but if a window doesn't set an icon
2177        or removes it entirely, it's not very likely it is going to set one
2178        right away afterwards
2179
2180        if it has parents, then one of them will have an icon already
2181     */
2182     if (self->nicons == 0 && !self->parents) {
2183         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2184         gulong *data;
2185
2186         data = g_new(gulong, 48*48+2);
2187         data[0] = data[1] =  48;
2188         for (i = 0; i < 48*48; ++i)
2189             data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2190                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2191                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2192                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2193         OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, data, 48*48+2);
2194         g_free(data);
2195     } else if (self->frame)
2196         render_plugin->frame_update_skin (self->frame);
2197         /* don't draw the icon empty if we're just setting one now anyways,
2198            we'll get the property change any second */
2199         //frame_adjust_icon(self->frame);
2200 }
2201
2202 void client_update_icon_geometry(ObClient *self)
2203 {
2204     guint num;
2205     guint32 *data;
2206
2207     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2208
2209     if (OBT_PROP_GETA32(self->window, NET_WM_ICON_GEOMETRY, CARDINAL,
2210                         &data, &num))
2211     {
2212         if (num == 4)
2213             /* don't let them set it with an area < 0 */
2214             RECT_SET(self->icon_geometry, data[0], data[1],
2215                      MAX(data[2],0), MAX(data[3],0));
2216         g_free(data);
2217     }
2218 }
2219
2220 static void client_get_session_ids(ObClient *self)
2221 {
2222     guint32 leader;
2223     gboolean got;
2224     gchar *s;
2225     gchar **ss;
2226
2227     if (!OBT_PROP_GET32(self->window, WM_CLIENT_LEADER, WINDOW, &leader))
2228         leader = None;
2229
2230     /* get the SM_CLIENT_ID */
2231     got = FALSE;
2232     if (leader)
2233         got = OBT_PROP_GETS(leader, SM_CLIENT_ID, locale, &self->sm_client_id);
2234     if (!got)
2235         OBT_PROP_GETS(self->window, SM_CLIENT_ID, locale, &self->sm_client_id);
2236
2237     /* get the WM_CLASS (name and class). make them "" if they are not
2238        provided */
2239     got = FALSE;
2240     if (leader)
2241         got = OBT_PROP_GETSS(leader, WM_CLASS, locale, &ss);
2242     if (!got)
2243         got = OBT_PROP_GETSS(self->window, WM_CLASS, locale, &ss);
2244
2245     if (got) {
2246         if (ss[0]) {
2247             self->name = g_strdup(ss[0]);
2248             if (ss[1])
2249                 self->class = g_strdup(ss[1]);
2250         }
2251         g_strfreev(ss);
2252     }
2253
2254     if (self->name == NULL) self->name = g_strdup("");
2255     if (self->class == NULL) self->class = g_strdup("");
2256
2257     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2258     got = FALSE;
2259     if (leader)
2260         got = OBT_PROP_GETS(leader, WM_WINDOW_ROLE, locale, &s);
2261     if (!got)
2262         got = OBT_PROP_GETS(self->window, WM_WINDOW_ROLE, locale, &s);
2263
2264     if (got)
2265         self->role = s;
2266     else
2267         self->role = g_strdup("");
2268
2269     /* get the WM_COMMAND */
2270     got = FALSE;
2271
2272     if (leader)
2273         got = OBT_PROP_GETSS(leader, WM_COMMAND, locale, &ss);
2274     if (!got)
2275         got = OBT_PROP_GETSS(self->window, WM_COMMAND, locale, &ss);
2276
2277     if (got) {
2278         /* merge/mash them all together */
2279         gchar *merge = NULL;
2280         gint i;
2281
2282         for (i = 0; ss[i]; ++i) {
2283             gchar *tmp = merge;
2284             if (merge)
2285                 merge = g_strconcat(merge, ss[i], NULL);
2286             else
2287                 merge = g_strconcat(ss[i], NULL);
2288             g_free(tmp);
2289         }
2290         g_strfreev(ss);
2291
2292         self->wm_command = merge;
2293     }
2294
2295     /* get the WM_CLIENT_MACHINE */
2296     got = FALSE;
2297     if (leader)
2298         got = OBT_PROP_GETS(leader, WM_CLIENT_MACHINE, locale, &s);
2299     if (!got)
2300         got = OBT_PROP_GETS(self->window, WM_CLIENT_MACHINE, locale, &s);
2301
2302     if (got) {
2303         gchar localhost[128];
2304         guint32 pid;
2305
2306         gethostname(localhost, 127);
2307         localhost[127] = '\0';
2308         if (strcmp(localhost, s) != 0)
2309             self->client_machine = s;
2310         else
2311             g_free(s);
2312
2313         /* see if it has the PID set too (the PID requires that the
2314            WM_CLIENT_MACHINE be set) */
2315         if (OBT_PROP_GET32(self->window, NET_WM_PID, CARDINAL, &pid))
2316             self->pid = pid;
2317     }
2318 }
2319
2320 static void client_change_wm_state(ObClient *self)
2321 {
2322     gulong state[2];
2323     glong old;
2324
2325     old = self->wmstate;
2326
2327     if (self->shaded || self->iconic ||
2328         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2329     {
2330         self->wmstate = IconicState;
2331     } else
2332         self->wmstate = NormalState;
2333
2334     if (old != self->wmstate) {
2335         OBT_PROP_MSG(ob_screen, self->window, KDE_WM_CHANGE_STATE,
2336                      self->wmstate, 1, 0, 0, 0);
2337
2338         state[0] = self->wmstate;
2339         state[1] = None;
2340         OBT_PROP_SETA32(self->window, WM_STATE, WM_STATE, state, 2);
2341     }
2342 }
2343
2344 static void client_change_state(ObClient *self)
2345 {
2346     gulong netstate[12];
2347     guint num;
2348
2349     num = 0;
2350     if (self->modal)
2351         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MODAL);
2352     if (self->shaded)
2353         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SHADED);
2354     if (self->iconic)
2355         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_HIDDEN);
2356     if (self->skip_taskbar)
2357         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR);
2358     if (self->skip_pager)
2359         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER);
2360     if (self->fullscreen)
2361         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN);
2362     if (self->max_vert)
2363         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT);
2364     if (self->max_horz)
2365         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ);
2366     if (self->above)
2367         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_ABOVE);
2368     if (self->below)
2369         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_BELOW);
2370     if (self->demands_attention)
2371         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION);
2372     if (self->undecorated)
2373         netstate[num++] = OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED);
2374     OBT_PROP_SETA32(self->window, NET_WM_STATE, ATOM, netstate, num);
2375
2376     if (self->frame)
2377         render_plugin->frame_update_layout (self->frame, FALSE, FALSE);
2378 }
2379
2380 ObClient *client_search_focus_tree(ObClient *self)
2381 {
2382     GSList *it;
2383     ObClient *ret;
2384
2385     for (it = self->transients; it; it = g_slist_next(it)) {
2386         if (client_focused(it->data)) return it->data;
2387         if ((ret = client_search_focus_tree(it->data))) return ret;
2388     }
2389     return NULL;
2390 }
2391
2392 ObClient *client_search_focus_tree_full(ObClient *self)
2393 {
2394     if (self->parents) {
2395         GSList *it;
2396
2397         for (it = self->parents; it; it = g_slist_next(it)) {
2398             ObClient *c = it->data;
2399             if ((c = client_search_focus_tree_full(it->data))) return c;
2400         }
2401
2402         return NULL;
2403     }
2404     else {
2405         /* this function checks the whole tree, the client_search_focus_tree
2406            does not, so we need to check this window */
2407         if (client_focused(self))
2408             return self;
2409         return client_search_focus_tree(self);
2410     }
2411 }
2412
2413 ObClient *client_search_focus_group_full(ObClient *self)
2414 {
2415     GSList *it;
2416
2417     if (self->group) {
2418         for (it = self->group->members; it; it = g_slist_next(it)) {
2419             ObClient *c = it->data;
2420
2421             if (client_focused(c)) return c;
2422             if ((c = client_search_focus_tree(it->data))) return c;
2423         }
2424     } else
2425         if (client_focused(self)) return self;
2426     return NULL;
2427 }
2428
2429 gboolean client_has_parent(ObClient *self)
2430 {
2431     return self->parents != NULL;
2432 }
2433
2434 static ObStackingLayer calc_layer(ObClient *self)
2435 {
2436     ObStackingLayer l;
2437     Rect *monitor;
2438
2439     monitor = screen_physical_area_monitor(client_monitor(self));
2440
2441     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2442         l = OB_STACKING_LAYER_DESKTOP;
2443     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2444         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2445         else l = OB_STACKING_LAYER_ABOVE;
2446     }
2447     else if ((self->fullscreen ||
2448               /* No decorations and fills the monitor = oldskool fullscreen.
2449                  But not for maximized windows.
2450               */
2451               (self->decorations == 0 &&
2452                !(self->max_horz && self->max_vert) &&
2453                RECT_EQUAL(self->area, *monitor))) &&
2454              /* you are fullscreen while you or your children are focused.. */
2455              (client_focused(self) || client_search_focus_tree(self) ||
2456               /* you can be fullscreen if you're on another desktop */
2457               (self->desktop != screen_desktop &&
2458                self->desktop != DESKTOP_ALL) ||
2459               /* and you can also be fullscreen if the focused client is on
2460                  another monitor, or nothing else is focused */
2461               (!focus_client ||
2462                client_monitor(focus_client) != client_monitor(self))))
2463         l = OB_STACKING_LAYER_FULLSCREEN;
2464     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2465     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2466     else l = OB_STACKING_LAYER_NORMAL;
2467
2468     g_free(monitor);
2469
2470     return l;
2471 }
2472
2473 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2474                                         ObStackingLayer min)
2475 {
2476     ObStackingLayer old, own;
2477     GSList *it;
2478
2479     old = self->layer;
2480     own = calc_layer(self);
2481     self->layer = MAX(own, min);
2482
2483     if (self->layer != old) {
2484         stacking_remove(CLIENT_AS_WINDOW(self));
2485         stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2486     }
2487
2488     /* we've been restacked */
2489     self->visited = TRUE;
2490
2491     for (it = self->transients; it; it = g_slist_next(it))
2492         client_calc_layer_recursive(it->data, orig,
2493                                     self->layer);
2494 }
2495
2496 static void client_calc_layer_internal(ObClient *self)
2497 {
2498     GSList *sit;
2499
2500     /* transients take on the layer of their parents */
2501     sit = client_search_all_top_parents(self);
2502
2503     for (; sit; sit = g_slist_next(sit))
2504         client_calc_layer_recursive(sit->data, self, 0);
2505 }
2506
2507 void client_calc_layer(ObClient *self)
2508 {
2509     GList *it;
2510
2511     /* skip over stuff above fullscreen layer */
2512     for (it = stacking_list; it; it = g_list_next(it))
2513         if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
2514
2515     /* find the windows in the fullscreen layer, and mark them not-visited */
2516     for (; it; it = g_list_next(it)) {
2517         if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
2518         else if (WINDOW_IS_CLIENT(it->data))
2519             WINDOW_AS_CLIENT(it->data)->visited = FALSE;
2520     }
2521
2522     client_calc_layer_internal(self);
2523
2524     /* skip over stuff above fullscreen layer */
2525     for (it = stacking_list; it; it = g_list_next(it))
2526         if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
2527
2528     /* now recalc any windows in the fullscreen layer which have not
2529        had their layer recalced already */
2530     for (; it; it = g_list_next(it)) {
2531         if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
2532         else if (WINDOW_IS_CLIENT(it->data) &&
2533                  !WINDOW_AS_CLIENT(it->data)->visited)
2534             client_calc_layer_internal(it->data);
2535     }
2536 }
2537
2538 gboolean client_should_show(ObClient *self)
2539 {
2540     if (self->iconic)
2541         return FALSE;
2542     if (client_normal(self) && screen_showing_desktop)
2543         return FALSE;
2544     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2545         return TRUE;
2546
2547     return FALSE;
2548 }
2549
2550 gboolean client_show(ObClient *self)
2551 {
2552     gboolean show = FALSE;
2553
2554     if (client_should_show(self)) {
2555         /* replay pending pointer event before showing the window, in case it
2556            should be going to something under the window */
2557         mouse_replay_pointer();
2558
2559         render_plugin->frame_show(self->frame);
2560         show = TRUE;
2561
2562         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2563            it needs to be in IconicState. This includes when it is on another
2564            desktop!
2565         */
2566         client_change_wm_state(self);
2567     }
2568     return show;
2569 }
2570
2571 gboolean client_hide(ObClient *self)
2572 {
2573     gboolean hide = FALSE;
2574
2575     if (!client_should_show(self)) {
2576         if (self == focus_client) {
2577             /* if there is a grab going on, then we need to cancel it. if we
2578                move focus during the grab, applications will get
2579                NotifyWhileGrabbed events and ignore them !
2580
2581                actions should not rely on being able to move focus during an
2582                interactive grab.
2583             */
2584             event_cancel_all_key_grabs();
2585         }
2586
2587         /* We don't need to ignore enter events here.
2588            The window can hide/iconify in 3 different ways:
2589            1 - through an x message. in this case we ignore all enter events
2590                caused by responding to the x message (unless underMouse)
2591            2 - by a keyboard action. in this case we ignore all enter events
2592                caused by the action
2593            3 - by a mouse action. in this case they are doing stuff with the
2594                mouse and focus _should_ move.
2595
2596            Also in action_end, we simulate an enter event that can't be ignored
2597            so trying to ignore them is futile in case 3 anyways
2598         */
2599
2600         /* replay pending pointer event before hiding the window, in case it
2601            should be going to the window */
2602         mouse_replay_pointer();
2603
2604         self->ignore_unmaps += render_plugin->frame_hide(self->frame);
2605         hide = TRUE;
2606
2607         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2608            it needs to be in IconicState. This includes when it is on another
2609            desktop!
2610         */
2611         client_change_wm_state(self);
2612     }
2613     return hide;
2614 }
2615
2616 void client_showhide(ObClient *self)
2617 {
2618     if (!client_show(self))
2619         client_hide(self);
2620 }
2621
2622 gboolean client_normal(ObClient *self) {
2623     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2624               self->type == OB_CLIENT_TYPE_DOCK ||
2625               self->type == OB_CLIENT_TYPE_SPLASH);
2626 }
2627
2628 gboolean client_helper(ObClient *self)
2629 {
2630     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2631             self->type == OB_CLIENT_TYPE_MENU ||
2632             self->type == OB_CLIENT_TYPE_TOOLBAR);
2633 }
2634
2635 gboolean client_mouse_focusable(ObClient *self)
2636 {
2637     return !(self->type == OB_CLIENT_TYPE_MENU ||
2638              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2639              self->type == OB_CLIENT_TYPE_SPLASH ||
2640              self->type == OB_CLIENT_TYPE_DOCK);
2641 }
2642
2643 gboolean client_enter_focusable(ObClient *self)
2644 {
2645     /* you can focus desktops but it shouldn't on enter */
2646     return (client_mouse_focusable(self) &&
2647             self->type != OB_CLIENT_TYPE_DESKTOP);
2648 }
2649
2650
2651 static void client_apply_startup_state(ObClient *self,
2652                                        gint x, gint y, gint w, gint h)
2653 {
2654     /* save the states that we are going to apply */
2655     gboolean iconic = self->iconic;
2656     gboolean fullscreen = self->fullscreen;
2657     gboolean undecorated = self->undecorated;
2658     gboolean shaded = self->shaded;
2659     gboolean demands_attention = self->demands_attention;
2660     gboolean max_horz = self->max_horz;
2661     gboolean max_vert = self->max_vert;
2662     Rect oldarea;
2663     gint l;
2664
2665     /* turn them all off in the client, so they won't affect the window
2666        being placed */
2667     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2668         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2669
2670     /* move the client to its placed position, or it it's already there,
2671        generate a ConfigureNotify telling the client where it is.
2672
2673        do this after adjusting the frame. otherwise it gets all weird and
2674        clients don't work right
2675
2676        do this before applying the states so they have the correct
2677        pre-max/pre-fullscreen values
2678     */
2679     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2680     ob_debug("placed window 0x%x at %d, %d with size %d x %d",
2681              self->window, x, y, w, h);
2682     /* save the area, and make it where it should be for the premax stuff */
2683     oldarea = self->area;
2684     RECT_SET(self->area, x, y, w, h);
2685     render_plugin->frame_set_client_area (self->frame, self->area);
2686
2687     /* apply the states. these are in a carefully crafted order.. */
2688
2689     if (iconic)
2690         client_iconify(self, TRUE, FALSE, TRUE);
2691     if (fullscreen)
2692         client_fullscreen(self, TRUE);
2693     if (undecorated)
2694         client_set_undecorated(self, TRUE);
2695     if (shaded)
2696         client_shade(self, TRUE);
2697     if (demands_attention)
2698         client_hilite(self, TRUE);
2699
2700     if (max_vert && max_horz)
2701         client_maximize(self, TRUE, 0);
2702     else if (max_vert)
2703         client_maximize(self, TRUE, 2);
2704     else if (max_horz)
2705         client_maximize(self, TRUE, 1);
2706
2707     /* if the window hasn't been configured yet, then do so now, in fact the
2708        x,y,w,h may _not_ be the same as the area rect, which can end up
2709        meaning that the client isn't properly moved/resized by the fullscreen
2710        function
2711        pho can cause this because it maps at size of the screen but not 0,0
2712        so openbox moves it on screen to 0,0 (thus x,y=0,0 and area.x,y don't).
2713        then fullscreen'ing makes it go to 0,0 which it thinks it already is at
2714        cuz thats where the pre-fullscreen will be. however the actual area is
2715        not, so this needs to be called even if we have fullscreened/maxed
2716     */
2717     self->area = oldarea;
2718     render_plugin->frame_set_client_area (self->frame, self->area);
2719     render_plugin->frame_set_decorations (self->frame, self->decorations);
2720     render_plugin->frame_update_layout (self->frame, FALSE, FALSE);
2721     client_configure(self, x, y, w, h, FALSE, TRUE, FALSE);
2722
2723     /* set the desktop hint, to make sure that it always exists */
2724     OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, self->desktop);
2725
2726     /* nothing to do for the other states:
2727        skip_taskbar
2728        skip_pager
2729        modal
2730        above
2731        below
2732     */
2733 }
2734
2735 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2736 {
2737     /* these should be the current values. this is for when you're not moving,
2738        just resizing */
2739     g_assert(*x == self->area.x);
2740     g_assert(oldw == self->area.width);
2741
2742     /* horizontal */
2743     switch (self->gravity) {
2744     default:
2745     case NorthWestGravity:
2746     case WestGravity:
2747     case SouthWestGravity:
2748     case StaticGravity:
2749     case ForgetGravity:
2750         break;
2751     case NorthGravity:
2752     case CenterGravity:
2753     case SouthGravity:
2754         *x -= (neww - oldw) / 2;
2755         break;
2756     case NorthEastGravity:
2757     case EastGravity:
2758     case SouthEastGravity:
2759         *x -= neww - oldw;
2760         break;
2761     }
2762 }
2763
2764 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2765 {
2766     /* these should be the current values. this is for when you're not moving,
2767        just resizing */
2768     g_assert(*y == self->area.y);
2769     g_assert(oldh == self->area.height);
2770
2771     /* vertical */
2772     switch (self->gravity) {
2773     default:
2774     case NorthWestGravity:
2775     case NorthGravity:
2776     case NorthEastGravity:
2777     case StaticGravity:
2778     case ForgetGravity:
2779         break;
2780     case WestGravity:
2781     case CenterGravity:
2782     case EastGravity:
2783         *y -= (newh - oldh) / 2;
2784         break;
2785     case SouthWestGravity:
2786     case SouthGravity:
2787     case SouthEastGravity:
2788         *y -= newh - oldh;
2789         break;
2790     }
2791 }
2792
2793 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2794                           gint *logicalw, gint *logicalh,
2795                           gboolean user)
2796 {
2797     Rect desired = {*x, *y, *w, *h};
2798     frame_rect_to_frame(self, &desired);
2799
2800     /* make the frame recalculate its dimentions n shit without changing
2801        anything visible for real, this way the constraints below can work with
2802        the updated frame dimensions. */
2803     render_plugin->frame_set_decorations (self->frame, self->decorations);
2804     render_plugin->frame_update_layout (self->frame, FALSE, TRUE);
2805
2806     /* gets the frame's position */
2807     frame_client_gravity(self, x, y);
2808
2809     /* these positions are frame positions, not client positions */
2810
2811     /* set the size and position if fullscreen */
2812     if (self->fullscreen) {
2813         Rect *a;
2814         guint i;
2815
2816         i = screen_find_monitor(&desired);
2817         a = screen_physical_area_monitor(i);
2818
2819         *x = a->x;
2820         *y = a->y;
2821         *w = a->width;
2822         *h = a->height;
2823
2824         user = FALSE; /* ignore if the client can't be moved/resized when it
2825                          is fullscreening */
2826
2827         g_free(a);
2828     } else if (self->max_horz || self->max_vert) {
2829         Rect *a;
2830         guint i;
2831
2832         /* use all possible struts when maximizing to the full screen */
2833         i = screen_find_monitor(&desired);
2834         a = screen_area(self->desktop, i,
2835                         (self->max_horz && self->max_vert ? NULL : &desired));
2836
2837         Strut size = render_plugin->frame_get_size(self->frame);
2838         /* set the size and position if maximized */
2839         if (self->max_horz) {
2840             *x = a->x;
2841             *w = a->width - size.left - size.right;
2842         }
2843         if (self->max_vert) {
2844             *y = a->y;
2845             *h = a->height - size.top - size.bottom;
2846         }
2847
2848         user = FALSE; /* ignore if the client can't be moved/resized when it
2849                          is maximizing */
2850
2851         g_free(a);
2852     }
2853
2854     /* gets the client's position */
2855     frame_frame_gravity(self, x, y);
2856
2857     /* work within the prefered sizes given by the window */
2858     if (!(*w == self->area.width && *h == self->area.height)) {
2859         gint basew, baseh, minw, minh;
2860         gint incw, inch;
2861         gfloat minratio, maxratio;
2862
2863         incw = self->fullscreen || self->max_horz ? 1 : self->size_inc.width;
2864         inch = self->fullscreen || self->max_vert ? 1 : self->size_inc.height;
2865         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2866             0 : self->min_ratio;
2867         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2868             0 : self->max_ratio;
2869
2870         /* base size is substituted with min size if not specified */
2871         if (self->base_size.width || self->base_size.height) {
2872             basew = self->base_size.width;
2873             baseh = self->base_size.height;
2874         } else {
2875             basew = self->min_size.width;
2876             baseh = self->min_size.height;
2877         }
2878         /* min size is substituted with base size if not specified */
2879         if (self->min_size.width || self->min_size.height) {
2880             minw = self->min_size.width;
2881             minh = self->min_size.height;
2882         } else {
2883             minw = self->base_size.width;
2884             minh = self->base_size.height;
2885         }
2886
2887         /* if this is a user-requested resize, then check against min/max
2888            sizes */
2889
2890         /* smaller than min size or bigger than max size? */
2891         if (*w > self->max_size.width) *w = self->max_size.width;
2892         if (*w < minw) *w = minw;
2893         if (*h > self->max_size.height) *h = self->max_size.height;
2894         if (*h < minh) *h = minh;
2895
2896         *w -= basew;
2897         *h -= baseh;
2898
2899         /* keep to the increments */
2900         *w /= incw;
2901         *h /= inch;
2902
2903         /* you cannot resize to nothing */
2904         if (basew + *w < 1) *w = 1 - basew;
2905         if (baseh + *h < 1) *h = 1 - baseh;
2906
2907         /* save the logical size */
2908         *logicalw = incw > 1 ? *w : *w + basew;
2909         *logicalh = inch > 1 ? *h : *h + baseh;
2910
2911         *w *= incw;
2912         *h *= inch;
2913
2914         *w += basew;
2915         *h += baseh;
2916
2917         /* adjust the height to match the width for the aspect ratios.
2918            for this, min size is not substituted for base size ever. */
2919         *w -= self->base_size.width;
2920         *h -= self->base_size.height;
2921
2922         if (minratio)
2923             if (*h * minratio > *w) {
2924                 *h = (gint)(*w / minratio);
2925
2926                 /* you cannot resize to nothing */
2927                 if (*h < 1) {
2928                     *h = 1;
2929                     *w = (gint)(*h * minratio);
2930                 }
2931             }
2932         if (maxratio)
2933             if (*h * maxratio < *w) {
2934                 *h = (gint)(*w / maxratio);
2935
2936                 /* you cannot resize to nothing */
2937                 if (*h < 1) {
2938                     *h = 1;
2939                     *w = (gint)(*h * minratio);
2940                 }
2941             }
2942
2943         *w += self->base_size.width;
2944         *h += self->base_size.height;
2945     }
2946
2947     /* these override the above states! if you cant move you can't move! */
2948     if (user) {
2949         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2950             *x = self->area.x;
2951             *y = self->area.y;
2952         }
2953         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2954             *w = self->area.width;
2955             *h = self->area.height;
2956         }
2957     }
2958
2959     g_assert(*w > 0);
2960     g_assert(*h > 0);
2961 }
2962
2963
2964 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2965                       gboolean user, gboolean final, gboolean force_reply)
2966 {
2967     Rect oldframe;
2968     gint oldw, oldh;
2969     gboolean send_resize_client;
2970     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2971     gboolean fmoved, fresized;
2972     guint fdecor = render_plugin->frame_get_decorations(self->frame);
2973     gboolean fhorz = render_plugin->frame_is_max_horz(self->frame);
2974     gboolean fvert = render_plugin->frame_is_max_vert(self->frame);
2975     gint logicalw, logicalh;
2976
2977     Strut size = render_plugin->frame_get_size(self->frame);
2978     Rect area = render_plugin->frame_get_window_area(self->frame);
2979
2980     /* find the new x, y, width, and height (and logical size) */
2981     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2982
2983     /* set the logical size if things changed */
2984     if (!(w == self->area.width && h == self->area.height))
2985         SIZE_SET(self->logical_size, logicalw, logicalh);
2986
2987     /* figure out if we moved or resized or what */
2988     moved = (x != self->area.x || y != self->area.y);
2989     resized = (w != self->area.width || h != self->area.height);
2990
2991     oldw = self->area.width;
2992     oldh = self->area.height;
2993     oldframe = render_plugin->frame_get_window_area(self->frame);
2994     RECT_SET(self->area, x, y, w, h);
2995
2996     render_plugin->frame_set_client_area (self->frame, self->area);
2997
2998     /* for app-requested resizes, always resize if 'resized' is true.
2999        for user-requested ones, only resize if final is true, or when
3000        resizing in redraw mode */
3001     send_resize_client = ((!user && resized) ||
3002                           (user && (final ||
3003                                     (resized && config_resize_redraw))));
3004
3005     /* if the client is enlarging, then resize the client before the frame */
3006     if (send_resize_client && (w > oldw || h > oldh)) {
3007         render_plugin->frame_set_decorations (self->frame, self->decorations);
3008         render_plugin->frame_update_layout (self->frame, FALSE, FALSE);
3009     }
3010
3011     /* find the frame's dimensions and move/resize it */
3012     fmoved = moved;
3013     fresized = resized;
3014
3015     /* if decorations changed, then readjust everything for the frame */
3016     if (self->decorations != fdecor ||
3017         self->max_horz != fhorz || self->max_vert != fvert)
3018     {
3019         fmoved = fresized = TRUE;
3020     }
3021
3022     /* adjust the frame */
3023     if (fmoved || fresized) {
3024         gulong ignore_start;
3025         if (!user)
3026             ignore_start = event_start_ignore_all_enters();
3027
3028         /* replay pending pointer event before move the window, in case it
3029            would change what window gets the event */
3030         mouse_replay_pointer();
3031
3032         render_plugin->frame_set_decorations (self->frame, self->decorations);
3033         render_plugin->frame_update_layout (self->frame, TRUE, FALSE);
3034
3035         if (!user)
3036             event_end_ignore_all_enters(ignore_start);
3037     }
3038
3039     if (!user || final) {
3040         gint oldrx = self->root_pos.x;
3041         gint oldry = self->root_pos.y;
3042         /* we have reset the client to 0 border width, so don't include
3043            it in these coords */
3044         POINT_SET(self->root_pos,
3045                   area.x + size.left -
3046                   self->border_width,
3047                   area.y + size.top -
3048                   self->border_width);
3049         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
3050             rootmoved = TRUE;
3051     }
3052
3053     /* This is kinda tricky and should not be changed.. let me explain!
3054
3055        When user = FALSE, then the request is coming from the application
3056        itself, and we are more strict about when to send a synthetic
3057        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3058        in this case (if force_reply is true)
3059
3060        When user = TRUE, then the request is coming from "us", like when we
3061        maximize a window or something.  In this case we are more lenient.  We
3062        used to follow the same rules as above, but _Java_ Swing can't handle
3063        this. So just to appease Swing, when user = TRUE, we always send
3064        a synthetic ConfigureNotify to give the window its root coordinates.
3065     */
3066     if ((!user && !resized && (rootmoved || force_reply)) ||
3067         (user && final && rootmoved))
3068     {
3069         XEvent event;
3070
3071         event.type = ConfigureNotify;
3072         event.xconfigure.display = obt_display;
3073         event.xconfigure.event = self->window;
3074         event.xconfigure.window = self->window;
3075
3076         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3077                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3078
3079         /* root window real coords */
3080         event.xconfigure.x = self->root_pos.x;
3081         event.xconfigure.y = self->root_pos.y;
3082         event.xconfigure.width = w;
3083         event.xconfigure.height = h;
3084         event.xconfigure.border_width = self->border_width;
3085         event.xconfigure.above = None;
3086         event.xconfigure.override_redirect = FALSE;
3087         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3088                    FALSE, StructureNotifyMask, &event);
3089     }
3090
3091     /* if the client is shrinking, then resize the frame before the client.
3092
3093        both of these resize sections may run, because the top one only resizes
3094        in the direction that is growing
3095      */
3096     if (send_resize_client && (w <= oldw || h <= oldh)) {
3097       render_plugin->frame_set_decorations (self->frame, self->decorations);
3098       render_plugin->frame_update_layout (self->frame, FALSE, FALSE);
3099     }
3100
3101     XFlush(obt_display);
3102
3103     /* if it moved between monitors, then this can affect the stacking
3104        layer of this window or others - for fullscreen windows */
3105     Rect current_frame = render_plugin->frame_get_window_area(self->frame);
3106     if (screen_find_monitor(&current_frame) !=
3107         screen_find_monitor(&oldframe))
3108     {
3109         client_calc_layer(self);
3110     }
3111 }
3112
3113 void client_fullscreen(ObClient *self, gboolean fs)
3114 {
3115     gint x, y, w, h;
3116
3117     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3118         self->fullscreen == fs) return;                   /* already done */
3119
3120     self->fullscreen = fs;
3121     client_change_state(self); /* change the state hints on the client */
3122
3123     if (fs) {
3124         self->pre_fullscreen_area = self->area;
3125         /* if the window is maximized, its area isn't all that meaningful.
3126            save it's premax area instead. */
3127         if (self->max_horz) {
3128             self->pre_fullscreen_area.x = self->pre_max_area.x;
3129             self->pre_fullscreen_area.width = self->pre_max_area.width;
3130         }
3131         if (self->max_vert) {
3132             self->pre_fullscreen_area.y = self->pre_max_area.y;
3133             self->pre_fullscreen_area.height = self->pre_max_area.height;
3134         }
3135
3136         /* these will help configure_full figure out where to fullscreen
3137            the window */
3138         x = self->area.x;
3139         y = self->area.y;
3140         w = self->area.width;
3141         h = self->area.height;
3142     } else {
3143         g_assert(self->pre_fullscreen_area.width > 0 &&
3144                  self->pre_fullscreen_area.height > 0);
3145
3146         x = self->pre_fullscreen_area.x;
3147         y = self->pre_fullscreen_area.y;
3148         w = self->pre_fullscreen_area.width;
3149         h = self->pre_fullscreen_area.height;
3150         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3151     }
3152
3153     ob_debug("Window %s going fullscreen (%d)",
3154              self->title, self->fullscreen);
3155
3156     client_setup_decor_and_functions(self, FALSE);
3157     client_move_resize(self, x, y, w, h);
3158
3159     /* and adjust our layer/stacking. do this after resizing the window,
3160        and applying decorations, because windows which fill the screen are
3161        considered "fullscreen" and it affects their layer */
3162     client_calc_layer(self);
3163
3164     if (fs) {
3165         /* try focus us when we go into fullscreen mode */
3166         client_focus(self);
3167     }
3168 }
3169
3170 static void client_iconify_recursive(ObClient *self,
3171                                      gboolean iconic, gboolean curdesk,
3172                                      gboolean hide_animation)
3173 {
3174     GSList *it;
3175     gboolean changed = FALSE;
3176
3177
3178     if (self->iconic != iconic) {
3179         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3180                  self->window);
3181
3182         if (iconic) {
3183             /* don't let non-normal windows iconify along with their parents
3184                or whatever */
3185             if (client_normal(self)) {
3186                 self->iconic = iconic;
3187
3188                 /* update the focus lists.. iconic windows go to the bottom of
3189                    the list */
3190                 focus_order_to_bottom(self);
3191
3192                 changed = TRUE;
3193             }
3194         } else {
3195             self->iconic = iconic;
3196
3197             if (curdesk && self->desktop != screen_desktop &&
3198                 self->desktop != DESKTOP_ALL)
3199                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3200
3201             /* this puts it after the current focused window */
3202             focus_order_remove(self);
3203             focus_order_add_new(self);
3204
3205             changed = TRUE;
3206         }
3207     }
3208
3209     if (changed) {
3210         client_change_state(self);
3211         if (config_animate_iconify && !hide_animation)
3212         render_plugin->frame_begin_iconify_animation(self->frame, iconic);
3213         /* do this after starting the animation so it doesn't flash */
3214         client_showhide(self);
3215     }
3216
3217     /* iconify all direct transients, and deiconify all transients
3218        (non-direct too) */
3219     for (it = self->transients; it; it = g_slist_next(it))
3220         if (it->data != self)
3221             if (client_is_direct_child(self, it->data) || !iconic)
3222                 client_iconify_recursive(it->data, iconic, curdesk,
3223                                          hide_animation);
3224 }
3225
3226 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3227                     gboolean hide_animation)
3228 {
3229     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3230         /* move up the transient chain as far as possible first */
3231         self = client_search_top_direct_parent(self);
3232         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3233     }
3234 }
3235
3236 void client_maximize(ObClient *self, gboolean max, gint dir)
3237 {
3238     gint x, y, w, h;
3239
3240     g_assert(dir == 0 || dir == 1 || dir == 2);
3241     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3242
3243     /* check if already done */
3244     if (max) {
3245         if (dir == 0 && self->max_horz && self->max_vert) return;
3246         if (dir == 1 && self->max_horz) return;
3247         if (dir == 2 && self->max_vert) return;
3248     } else {
3249         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3250         if (dir == 1 && !self->max_horz) return;
3251         if (dir == 2 && !self->max_vert) return;
3252     }
3253
3254     /* these will help configure_full figure out which screen to fill with
3255        the window */
3256     x = self->area.x;
3257     y = self->area.y;
3258     w = self->area.width;
3259     h = self->area.height;
3260
3261     if (max) {
3262         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3263             RECT_SET(self->pre_max_area,
3264                      self->area.x, self->pre_max_area.y,
3265                      self->area.width, self->pre_max_area.height);
3266         }
3267         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3268             RECT_SET(self->pre_max_area,
3269                      self->pre_max_area.x, self->area.y,
3270                      self->pre_max_area.width, self->area.height);
3271         }
3272     } else {
3273         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3274             g_assert(self->pre_max_area.width > 0);
3275
3276             x = self->pre_max_area.x;
3277             w = self->pre_max_area.width;
3278
3279             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3280                      0, self->pre_max_area.height);
3281         }
3282         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3283             g_assert(self->pre_max_area.height > 0);
3284
3285             y = self->pre_max_area.y;
3286             h = self->pre_max_area.height;
3287
3288             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3289                      self->pre_max_area.width, 0);
3290         }
3291     }
3292
3293     if (dir == 0 || dir == 1) { 
3294         self->max_horz = max; /* horz */
3295         render_plugin->frame_trigger(self->frame, max? OB_TRIGGER_MAX_HORZ : OB_TRIGGER_UNMAX_HORZ);
3296     }
3297
3298     if (dir == 0 || dir == 2){
3299         self->max_vert = max; /* vert */
3300         render_plugin->frame_trigger(self->frame, max? OB_TRIGGER_MAX_VERT : OB_TRIGGER_UNMAX_VERT);
3301     }
3302
3303     client_change_state(self); /* change the state hints on the client */
3304
3305     client_setup_decor_and_functions(self, FALSE);
3306     client_move_resize(self, x, y, w, h);
3307 }
3308
3309 void client_shade(ObClient *self, gboolean shade)
3310 {
3311     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3312          shade) ||                         /* can't shade */
3313         self->shaded == shade) return;     /* already done */
3314
3315     self->shaded = shade;
3316     client_change_state(self);
3317     client_change_wm_state(self); /* the window is being hidden/shown */
3318     /* resize the frame to just the titlebar */
3319     render_plugin->frame_set_is_shaded (self->frame, self->shaded);
3320     render_plugin->frame_update_layout(self->frame, FALSE, FALSE);
3321 }
3322
3323 static void client_ping_event(ObClient *self, gboolean dead)
3324 {
3325     self->not_responding = dead;
3326     client_update_title(self);
3327
3328     if (!dead) {
3329         /* it came back to life ! */
3330
3331         if (self->kill_prompt) {
3332             prompt_unref(self->kill_prompt);
3333             self->kill_prompt = NULL;
3334         }
3335
3336         self->kill_level = 0;
3337     }
3338 }
3339
3340 void client_close(ObClient *self)
3341 {
3342     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3343
3344     if (self->prompt) {
3345         prompt_cancel(self->prompt);
3346         return;
3347     }
3348
3349     /* in the case that the client provides no means to requesting that it
3350        close, we just kill it */
3351     if (!self->delete_window)
3352         /* don't use client_kill(), we should only kill based on PID in
3353            response to a lack of PING replies */
3354         XKillClient(obt_display, self->window);
3355     else {
3356         /* request the client to close with WM_DELETE_WINDOW */
3357         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3358                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_curtime,
3359                         0, 0, 0, NoEventMask);
3360
3361         if (self->not_responding)
3362             client_prompt_kill(self);
3363     }
3364 }
3365
3366 #define OB_KILL_RESULT_NO 0
3367 #define OB_KILL_RESULT_YES 1
3368
3369 static void client_kill_requested(ObPrompt *p, gint result, gpointer data)
3370 {
3371     ObClient *self = data;
3372
3373     if (result == OB_KILL_RESULT_YES)
3374         client_kill(self);
3375
3376     prompt_unref(self->kill_prompt);
3377     self->kill_prompt = NULL;
3378 }
3379
3380 static void client_prompt_kill(ObClient *self)
3381 {
3382     /* check if we're already prompting */
3383     if (!self->kill_prompt) {
3384         ObPromptAnswer answers[] = {
3385             { _("No"), OB_KILL_RESULT_NO },
3386             { _("Yes"), OB_KILL_RESULT_YES }
3387         };
3388         gchar *m;
3389         const gchar *sig;
3390
3391         if (self->kill_level == 0)
3392             sig = "terminate";
3393         else
3394             sig = "kill";
3395
3396         m = g_strdup_printf
3397             (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"), self->original_title, sig);
3398
3399         self->kill_prompt = prompt_new(m, answers,
3400                                        sizeof(answers)/sizeof(answers[0]),
3401                                        OB_KILL_RESULT_NO, /* default = no */
3402                                        OB_KILL_RESULT_NO, /* cancel = no */
3403                                        client_kill_requested, self);
3404         g_free(m);
3405     }
3406
3407     prompt_show(self->kill_prompt, self);
3408 }
3409
3410 void client_kill(ObClient *self)
3411 {
3412     /* don't kill our own windows */
3413     if (self->prompt) return;
3414
3415     if (!self->client_machine && self->pid) {
3416         /* running on the local host */
3417         if (self->kill_level == 0) {
3418             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3419                      self->window, self->pid);
3420             kill(self->pid, SIGTERM);
3421             ++self->kill_level;
3422
3423             /* show that we're trying to kill it */
3424             client_update_title(self);
3425         }
3426         else {
3427             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3428                      self->window, self->pid);
3429             kill(self->pid, SIGKILL); /* kill -9 */
3430         }
3431     }
3432     else {
3433         /* running on a remote host */
3434         XKillClient(obt_display, self->window);
3435     }
3436 }
3437
3438 void client_hilite(ObClient *self, gboolean hilite)
3439 {
3440     if (self->demands_attention == hilite)
3441         return; /* no change */
3442
3443     /* don't allow focused windows to hilite */
3444     self->demands_attention = hilite && !client_focused(self);
3445     if (self->frame != NULL) { /* if we're mapping, just set the state */
3446         if (self->demands_attention)
3447         render_plugin->frame_flash_start(self->frame);
3448         else
3449         render_plugin->frame_flash_stop(self->frame);
3450         client_change_state(self);
3451     }
3452 }
3453
3454 static void client_set_desktop_recursive(ObClient *self,
3455                                          guint target,
3456                                          gboolean donthide,
3457                                          gboolean dontraise)
3458 {
3459     guint old;
3460     GSList *it;
3461
3462     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3463
3464         ob_debug("Setting desktop %u", target+1);
3465
3466         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3467
3468         old = self->desktop;
3469         self->desktop = target;
3470         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3471         /* the frame can display the current desktop state */
3472         render_plugin->frame_set_decorations (self->frame, self->decorations);
3473         render_plugin->frame_update_layout(self->frame, FALSE, FALSE);
3474         /* 'move' the window to the new desktop */
3475         if (!donthide)
3476             client_hide(self);
3477         client_show(self);
3478         /* raise if it was not already on the desktop */
3479         if (old != DESKTOP_ALL && !dontraise)
3480             stacking_raise(CLIENT_AS_WINDOW(self));
3481         if (STRUT_EXISTS(self->strut))
3482             screen_update_areas();
3483         else
3484             /* the new desktop's geometry may be different, so we may need to
3485                resize, for example if we are maximized */
3486             client_reconfigure(self, FALSE);
3487     }
3488
3489     /* move all transients */
3490     for (it = self->transients; it; it = g_slist_next(it))
3491         if (it->data != self)
3492             if (client_is_direct_child(self, it->data))
3493                 client_set_desktop_recursive(it->data, target,
3494                                              donthide, dontraise);
3495 }
3496
3497 void client_set_desktop(ObClient *self, guint target,
3498                         gboolean donthide, gboolean dontraise)
3499 {
3500     self = client_search_top_direct_parent(self);
3501     client_set_desktop_recursive(self, target, donthide, dontraise);
3502 }
3503
3504 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3505 {
3506     while (child != parent && (child = client_direct_parent(child)));
3507     return child == parent;
3508 }
3509
3510 ObClient *client_search_modal_child(ObClient *self)
3511 {
3512     GSList *it;
3513     ObClient *ret;
3514
3515     for (it = self->transients; it; it = g_slist_next(it)) {
3516         ObClient *c = it->data;
3517         if ((ret = client_search_modal_child(c))) return ret;
3518         if (c->modal) return c;
3519     }
3520     return NULL;
3521 }
3522
3523 gboolean client_validate(ObClient *self)
3524 {
3525     XEvent e;
3526
3527     XSync(obt_display, FALSE); /* get all events on the server */
3528
3529     if (XCheckTypedWindowEvent(obt_display, self->window, DestroyNotify, &e) ||
3530         XCheckTypedWindowEvent(obt_display, self->window, UnmapNotify, &e))
3531     {
3532         XPutBackEvent(obt_display, &e);
3533         return FALSE;
3534     }
3535
3536     return TRUE;
3537 }
3538
3539 void client_set_wm_state(ObClient *self, glong state)
3540 {
3541     if (state == self->wmstate) return; /* no change */
3542
3543     switch (state) {
3544     case IconicState:
3545         client_iconify(self, TRUE, TRUE, FALSE);
3546         break;
3547     case NormalState:
3548         client_iconify(self, FALSE, TRUE, FALSE);
3549         break;
3550     }
3551 }
3552
3553 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3554 {
3555     gboolean shaded = self->shaded;
3556     gboolean fullscreen = self->fullscreen;
3557     gboolean undecorated = self->undecorated;
3558     gboolean max_horz = self->max_horz;
3559     gboolean max_vert = self->max_vert;
3560     gboolean modal = self->modal;
3561     gboolean iconic = self->iconic;
3562     gboolean demands_attention = self->demands_attention;
3563     gboolean above = self->above;
3564     gboolean below = self->below;
3565     gint i;
3566
3567     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3568           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3569           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3570         /* an invalid action was passed to the client message, ignore it */
3571         return;
3572
3573     for (i = 0; i < 2; ++i) {
3574         Atom state = i == 0 ? data1 : data2;
3575
3576         if (!state) continue;
3577
3578         /* if toggling, then pick whether we're adding or removing */
3579         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3580             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3581                 action = modal ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3582                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3583             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3584                 action = self->max_vert ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3585                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3586             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3587                 action = self->max_horz ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3588                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3589             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3590                 action = shaded ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3591                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3592             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3593                 action = self->skip_taskbar ?
3594                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3595                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3596             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3597                 action = self->skip_pager ?
3598                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3599                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3600             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3601                 action = self->iconic ?
3602                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3603                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3604             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3605                 action = fullscreen ?
3606                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3607                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3608             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3609                 action = self->above ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3610                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3611             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3612                 action = self->below ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3613                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3614             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3615                 action = self->demands_attention ?
3616                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3617                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3618             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3619                 action = undecorated ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3620                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3621         }
3622
3623         if (action == OBT_PROP_ATOM(NET_WM_STATE_ADD)) {
3624             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3625                 modal = TRUE;
3626             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3627                 max_vert = TRUE;
3628             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3629                 max_horz = TRUE;
3630             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3631                 shaded = TRUE;
3632             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3633                 self->skip_taskbar = TRUE;
3634             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3635                 self->skip_pager = TRUE;
3636             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3637                 iconic = TRUE;
3638             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3639                 fullscreen = TRUE;
3640             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3641                 above = TRUE;
3642                 below = FALSE;
3643             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3644                 above = FALSE;
3645                 below = TRUE;
3646             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3647                 demands_attention = TRUE;
3648             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3649                 undecorated = TRUE;
3650             }
3651
3652         } else { /* action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) */
3653             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3654                 modal = FALSE;
3655             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3656                 max_vert = FALSE;
3657             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3658                 max_horz = FALSE;
3659             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3660                 shaded = FALSE;
3661             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3662                 self->skip_taskbar = FALSE;
3663             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3664                 self->skip_pager = FALSE;
3665             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3666                 iconic = FALSE;
3667             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3668                 fullscreen = FALSE;
3669             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3670                 above = FALSE;
3671             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3672                 below = FALSE;
3673             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3674                 demands_attention = FALSE;
3675             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3676                 undecorated = FALSE;
3677             }
3678         }
3679     }
3680
3681     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3682         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3683             /* toggling both */
3684             if (max_horz == max_vert) { /* both going the same way */
3685                 client_maximize(self, max_horz, 0);
3686             } else {
3687                 client_maximize(self, max_horz, 1);
3688                 client_maximize(self, max_vert, 2);
3689             }
3690         } else {
3691             /* toggling one */
3692             if (max_horz != self->max_horz)
3693                 client_maximize(self, max_horz, 1);
3694             else
3695                 client_maximize(self, max_vert, 2);
3696         }
3697     }
3698     /* change fullscreen state before shading, as it will affect if the window
3699        can shade or not */
3700     if (fullscreen != self->fullscreen)
3701         client_fullscreen(self, fullscreen);
3702     if (shaded != self->shaded)
3703         client_shade(self, shaded);
3704     if (undecorated != self->undecorated)
3705         client_set_undecorated(self, undecorated);
3706     if (above != self->above || below != self->below) {
3707         self->above = above;
3708         self->below = below;
3709         client_calc_layer(self);
3710     }
3711
3712     if (modal != self->modal) {
3713         self->modal = modal;
3714         /* when a window changes modality, then its stacking order with its
3715            transients needs to change */
3716         stacking_raise(CLIENT_AS_WINDOW(self));
3717
3718         /* it also may get focused. if something is focused that shouldn't
3719            be focused anymore, then move the focus */
3720         if (focus_client && client_focus_target(focus_client) != focus_client)
3721             client_focus(focus_client);
3722     }
3723
3724     if (iconic != self->iconic)
3725         client_iconify(self, iconic, FALSE, FALSE);
3726
3727     if (demands_attention != self->demands_attention)
3728         client_hilite(self, demands_attention);
3729
3730     client_change_state(self); /* change the hint to reflect these changes */
3731 }
3732
3733 ObClient *client_focus_target(ObClient *self)
3734 {
3735     ObClient *child = NULL;
3736
3737     child = client_search_modal_child(self);
3738     if (child) return child;
3739     return self;
3740 }
3741
3742 gboolean client_can_focus(ObClient *self)
3743 {
3744     /* choose the correct target */
3745     self = client_focus_target(self);
3746
3747     if (!render_plugin->frame_is_visible(self->frame))
3748         return FALSE;
3749
3750     if (!(self->can_focus || self->focus_notify))
3751         return FALSE;
3752
3753     return TRUE;
3754 }
3755
3756 gboolean client_focus(ObClient *self)
3757 {
3758     /* we might not focus this window, so if we have modal children which would
3759        be focused instead, bring them to this desktop */
3760     client_bring_modal_windows(self);
3761
3762     /* choose the correct target */
3763     self = client_focus_target(self);
3764
3765     if (!client_can_focus(self)) {
3766         ob_debug_type(OB_DEBUG_FOCUS,
3767                       "Client %s can't be focused", self->title);
3768         return FALSE;
3769     }
3770
3771     ob_debug_type(OB_DEBUG_FOCUS,
3772                   "Focusing client \"%s\" (0x%x) at time %u",
3773                   self->title, self->window, event_curtime);
3774
3775     /* if using focus_delay, stop the timer now so that focus doesn't
3776        go moving on us */
3777     event_halt_focus_delay();
3778
3779     /* if there is a grab going on, then we need to cancel it. if we move
3780        focus during the grab, applications will get NotifyWhileGrabbed events
3781        and ignore them !
3782
3783        actions should not rely on being able to move focus during an
3784        interactive grab.
3785     */
3786     event_cancel_all_key_grabs();
3787
3788     obt_display_ignore_errors(TRUE);
3789
3790     if (self->can_focus) {
3791         /* This can cause a BadMatch error with CurrentTime, or if an app
3792            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3793         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
3794                        event_curtime);
3795     }
3796
3797     if (self->focus_notify) {
3798         XEvent ce;
3799         ce.xclient.type = ClientMessage;
3800         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
3801         ce.xclient.display = obt_display;
3802         ce.xclient.window = self->window;
3803         ce.xclient.format = 32;
3804         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
3805         ce.xclient.data.l[1] = event_curtime;
3806         ce.xclient.data.l[2] = 0l;
3807         ce.xclient.data.l[3] = 0l;
3808         ce.xclient.data.l[4] = 0l;
3809         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
3810     }
3811
3812     obt_display_ignore_errors(FALSE);
3813
3814     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
3815                   obt_display_error_occured);
3816     return !obt_display_error_occured;
3817 }
3818
3819 static void client_present(ObClient *self, gboolean here, gboolean raise,
3820                            gboolean unshade)
3821 {
3822     if (client_normal(self) && screen_showing_desktop)
3823         screen_show_desktop(FALSE, self);
3824     if (self->iconic)
3825         client_iconify(self, FALSE, here, FALSE);
3826     if (self->desktop != DESKTOP_ALL &&
3827         self->desktop != screen_desktop)
3828     {
3829         if (here)
3830             client_set_desktop(self, screen_desktop, FALSE, TRUE);
3831         else
3832             screen_set_desktop(self->desktop, FALSE);
3833     } else if (!render_plugin->frame_is_visible(self->frame))
3834         /* if its not visible for other reasons, then don't mess
3835            with it */
3836         return;
3837     if (self->shaded && unshade)
3838         client_shade(self, FALSE);
3839     if (raise)
3840         stacking_raise(CLIENT_AS_WINDOW(self));
3841
3842     client_focus(self);
3843 }
3844
3845 void client_activate(ObClient *self, gboolean here, gboolean raise,
3846                      gboolean unshade, gboolean user)
3847 {
3848     client_present(self, here, raise, unshade);
3849 }
3850
3851 static void client_bring_windows_recursive(ObClient *self,
3852                                            guint desktop,
3853                                            gboolean helpers,
3854                                            gboolean modals,
3855                                            gboolean iconic)
3856 {
3857     GSList *it;
3858
3859     for (it = self->transients; it; it = g_slist_next(it))
3860         client_bring_windows_recursive(it->data, desktop,
3861                                        helpers, modals, iconic);
3862
3863     if (((helpers && client_helper(self)) ||
3864          (modals && self->modal)) &&
3865         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3866          (iconic && self->iconic)))
3867     {
3868         if (iconic && self->iconic)
3869             client_iconify(self, FALSE, TRUE, FALSE);
3870         else
3871             client_set_desktop(self, desktop, FALSE, FALSE);
3872     }
3873 }
3874
3875 void client_bring_helper_windows(ObClient *self)
3876 {
3877     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3878 }
3879
3880 void client_bring_modal_windows(ObClient *self)
3881 {
3882     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3883 }
3884
3885 gboolean client_focused(ObClient *self)
3886 {
3887     return self == focus_client;
3888 }
3889
3890 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3891 {
3892     guint i;
3893     gulong min_diff, min_i;
3894
3895     if (!self->nicons) {
3896         ObClientIcon *parent = NULL;
3897         GSList *it;
3898
3899         for (it = self->parents; it; it = g_slist_next(it)) {
3900             ObClient *c = it->data;
3901             if ((parent = client_icon_recursive(c, w, h)))
3902                 break;
3903         }
3904
3905         return parent;
3906     }
3907
3908     /* some kind of crappy approximation to find the icon closest in size to
3909        what we requested, but icons are generally all the same ratio as
3910        eachother so it's good enough. */
3911
3912     min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3913     min_i = 0;
3914
3915     for (i = 1; i < self->nicons; ++i) {
3916         gulong diff;
3917
3918         diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3919         if (diff < min_diff) {
3920             min_diff = diff;
3921             min_i = i;
3922         }
3923     }
3924     return &self->icons[min_i];
3925 }
3926
3927 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3928 {
3929     ObClientIcon *ret;
3930     static ObClientIcon deficon;
3931
3932     if (!(ret = client_icon_recursive(self, w, h))) {
3933         deficon.width = deficon.height = 48;
3934         deficon.data = ob_rr_theme->def_win_icon;
3935         ret = &deficon;
3936     }
3937     return ret;
3938 }
3939
3940 void client_set_layer(ObClient *self, gint layer)
3941 {
3942     if (layer < 0) {
3943         self->below = TRUE;
3944         self->above = FALSE;
3945     } else if (layer == 0) {
3946         self->below = self->above = FALSE;
3947     } else {
3948         self->below = FALSE;
3949         self->above = TRUE;
3950     }
3951     client_calc_layer(self);
3952     client_change_state(self); /* reflect this in the state hints */
3953 }
3954
3955 void client_set_undecorated(ObClient *self, gboolean undecorated)
3956 {
3957     if (self->undecorated != undecorated &&
3958         /* don't let it undecorate if the function is missing, but let
3959            it redecorate */
3960         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3961     {
3962         self->undecorated = undecorated;
3963         client_setup_decor_and_functions(self, TRUE);
3964         client_change_state(self); /* reflect this in the state hints */
3965     }
3966 }
3967
3968 guint client_monitor(ObClient *self)
3969 {
3970     Rect area = render_plugin->frame_get_window_area(self->frame);
3971     return screen_find_monitor(&area);
3972 }
3973
3974 ObClient *client_direct_parent(ObClient *self)
3975 {
3976     if (!self->parents) return NULL;
3977     if (self->transient_for_group) return NULL;
3978     return self->parents->data;
3979 }
3980
3981 ObClient *client_search_top_direct_parent(ObClient *self)
3982 {
3983     ObClient *p;
3984     while ((p = client_direct_parent(self))) self = p;
3985     return self;
3986 }
3987
3988 static GSList *client_search_all_top_parents_internal(ObClient *self,
3989                                                       gboolean bylayer,
3990                                                       ObStackingLayer layer)
3991 {
3992     GSList *ret;
3993     ObClient *p;
3994
3995     /* move up the direct transient chain as far as possible */
3996     while ((p = client_direct_parent(self)) &&
3997            (!bylayer || p->layer == layer))
3998         self = p;
3999
4000     if (!self->parents)
4001         ret = g_slist_prepend(NULL, self);
4002     else
4003         ret = g_slist_copy(self->parents);
4004
4005     return ret;
4006 }
4007
4008 GSList *client_search_all_top_parents(ObClient *self)
4009 {
4010     return client_search_all_top_parents_internal(self, FALSE, 0);
4011 }
4012
4013 GSList *client_search_all_top_parents_layer(ObClient *self)
4014 {
4015     return client_search_all_top_parents_internal(self, TRUE, self->layer);
4016 }
4017
4018 ObClient *client_search_focus_parent(ObClient *self)
4019 {
4020     GSList *it;
4021
4022     for (it = self->parents; it; it = g_slist_next(it))
4023         if (client_focused(it->data)) return it->data;
4024
4025     return NULL;
4026 }
4027
4028 ObClient *client_search_parent(ObClient *self, ObClient *search)
4029 {
4030     GSList *it;
4031
4032     for (it = self->parents; it; it = g_slist_next(it))
4033         if (it->data == search) return search;
4034
4035     return NULL;
4036 }
4037
4038 ObClient *client_search_transient(ObClient *self, ObClient *search)
4039 {
4040     GSList *sit;
4041
4042     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4043         if (sit->data == search)
4044             return search;
4045         if (client_search_transient(sit->data, search))
4046             return search;
4047     }
4048     return NULL;
4049 }
4050
4051 static void detect_edge(Rect area, ObDirection dir,
4052                         gint my_head, gint my_size,
4053                         gint my_edge_start, gint my_edge_size,
4054                         gint *dest, gboolean *near_edge)
4055 {
4056     gint edge_start, edge_size, head, tail;
4057     gboolean skip_head = FALSE, skip_tail = FALSE;
4058
4059     switch (dir) {
4060         case OB_DIRECTION_NORTH:
4061         case OB_DIRECTION_SOUTH:
4062             edge_start = area.x;
4063             edge_size = area.width;
4064             break;
4065         case OB_DIRECTION_EAST:
4066         case OB_DIRECTION_WEST:
4067             edge_start = area.y;
4068             edge_size = area.height;
4069             break;
4070         default:
4071             g_assert_not_reached();
4072     }
4073
4074     /* do we collide with this window? */
4075     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4076                 edge_start, edge_size))
4077         return;
4078
4079     switch (dir) {
4080         case OB_DIRECTION_NORTH:
4081             head = RECT_BOTTOM(area);
4082             tail = RECT_TOP(area);
4083             break;
4084         case OB_DIRECTION_SOUTH:
4085             head = RECT_TOP(area);
4086             tail = RECT_BOTTOM(area);
4087             break;
4088         case OB_DIRECTION_WEST:
4089             head = RECT_RIGHT(area);
4090             tail = RECT_LEFT(area);
4091             break;
4092         case OB_DIRECTION_EAST:
4093             head = RECT_LEFT(area);
4094             tail = RECT_RIGHT(area);
4095             break;
4096         default:
4097             g_assert_not_reached();
4098     }
4099     switch (dir) {
4100         case OB_DIRECTION_NORTH:
4101         case OB_DIRECTION_WEST:
4102             /* check if our window is past the head of this window */
4103             if (my_head <= head + 1)
4104                 skip_head = TRUE;
4105             /* check if our window's tail is past the tail of this window */
4106             if (my_head + my_size - 1 <= tail)
4107                 skip_tail = TRUE;
4108             /* check if the head of this window is closer than the previously
4109                chosen edge (take into account that the previously chosen
4110                edge might have been a tail, not a head) */
4111             if (head + (*near_edge ? 0 : my_size) < *dest)
4112                 skip_head = TRUE;
4113             /* check if the tail of this window is closer than the previously
4114                chosen edge (take into account that the previously chosen
4115                edge might have been a head, not a tail) */
4116             if (tail - (!*near_edge ? 0 : my_size) < *dest)
4117                 skip_tail = TRUE;
4118             break;
4119         case OB_DIRECTION_SOUTH:
4120         case OB_DIRECTION_EAST:
4121             /* check if our window is past the head of this window */
4122             if (my_head >= head - 1)
4123                 skip_head = TRUE;
4124             /* check if our window's tail is past the tail of this window */
4125             if (my_head - my_size + 1 >= tail)
4126                 skip_tail = TRUE;
4127             /* check if the head of this window is closer than the previously
4128                chosen edge (take into account that the previously chosen
4129                edge might have been a tail, not a head) */
4130             if (head - (*near_edge ? 0 : my_size) > *dest)
4131                 skip_head = TRUE;
4132             /* check if the tail of this window is closer than the previously
4133                chosen edge (take into account that the previously chosen
4134                edge might have been a head, not a tail) */
4135             if (tail + (!*near_edge ? 0 : my_size) > *dest)
4136                 skip_tail = TRUE;
4137             break;
4138         default:
4139             g_assert_not_reached();
4140     }
4141
4142     ob_debug("my head %d size %d", my_head, my_size);
4143     ob_debug("head %d tail %d deest %d", head, tail, *dest);
4144     if (!skip_head) {
4145         ob_debug("using near edge %d", head);
4146         *dest = head;
4147         *near_edge = TRUE;
4148     }
4149     else if (!skip_tail) {
4150         ob_debug("using far edge %d", tail);
4151         *dest = tail;
4152         *near_edge = FALSE;
4153     }
4154 }
4155
4156 void client_find_edge_directional(ObClient *self, ObDirection dir,
4157                                   gint my_head, gint my_size,
4158                                   gint my_edge_start, gint my_edge_size,
4159                                   gint *dest, gboolean *near_edge)
4160 {
4161     GList *it;
4162     Rect *a, *mon;
4163     Rect dock_area;
4164     gint edge;
4165
4166     Strut size = render_plugin->frame_get_size(self->frame);
4167     Rect area = render_plugin->frame_get_window_area(self->frame);
4168
4169     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4170                     &area);
4171     mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR,
4172                       &area);
4173
4174     switch (dir) {
4175     case OB_DIRECTION_NORTH:
4176         if (my_head >= RECT_TOP(*mon) + 1)
4177             edge = RECT_TOP(*mon) - 1;
4178         else
4179             edge = RECT_TOP(*a) - 1;
4180         break;
4181     case OB_DIRECTION_SOUTH:
4182         if (my_head <= RECT_BOTTOM(*mon) - 1)
4183             edge = RECT_BOTTOM(*mon) + 1;
4184         else
4185             edge = RECT_BOTTOM(*a) + 1;
4186         break;
4187     case OB_DIRECTION_EAST:
4188         if (my_head <= RECT_RIGHT(*mon) - 1)
4189             edge = RECT_RIGHT(*mon) + 1;
4190         else
4191             edge = RECT_RIGHT(*a) + 1;
4192         break;
4193     case OB_DIRECTION_WEST:
4194         if (my_head >= RECT_LEFT(*mon) + 1)
4195             edge = RECT_LEFT(*mon) - 1;
4196         else
4197             edge = RECT_LEFT(*a) - 1;
4198         break;
4199     default:
4200         g_assert_not_reached();
4201     }
4202     /* default to the far edge, then narrow it down */
4203     *dest = edge;
4204     *near_edge = TRUE;
4205
4206     for (it = client_list; it; it = g_list_next(it)) {
4207         ObClient *cur = it->data;
4208
4209         /* skip windows to not bump into */
4210         if (cur == self)
4211             continue;
4212         if (cur->iconic)
4213             continue;
4214         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4215             cur->desktop != screen_desktop)
4216             continue;
4217
4218         ob_debug("trying window %s", cur->title);
4219
4220         detect_edge(render_plugin->frame_get_window_area(cur->frame), dir, my_head, my_size, my_edge_start,
4221                     my_edge_size, dest, near_edge);
4222     }
4223     dock_get_area(&dock_area);
4224     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4225                 my_edge_size, dest, near_edge);
4226     g_free(a);
4227     g_free(mon);
4228 }
4229
4230 void client_find_move_directional(ObClient *self, ObDirection dir,
4231                                   gint *x, gint *y)
4232 {
4233     gint head, size;
4234     gint e, e_start, e_size;
4235     gboolean near;
4236
4237
4238     Rect area = render_plugin->frame_get_window_area(self->frame);
4239
4240     switch (dir) {
4241     case OB_DIRECTION_EAST:
4242         head = RECT_RIGHT(area);
4243         size = area.width;
4244         e_start = RECT_TOP(area);
4245         e_size = area.height;
4246         break;
4247     case OB_DIRECTION_WEST:
4248         head = RECT_LEFT(area);
4249         size = area.width;
4250         e_start = RECT_TOP(area);
4251         e_size = area.height;
4252         break;
4253     case OB_DIRECTION_NORTH:
4254         head = RECT_TOP(area);
4255         size = area.height;
4256         e_start = RECT_LEFT(area);
4257         e_size = area.width;
4258         break;
4259     case OB_DIRECTION_SOUTH:
4260         head = RECT_BOTTOM(area);
4261         size = area.height;
4262         e_start = RECT_LEFT(area);
4263         e_size = area.width;
4264         break;
4265     default:
4266         g_assert_not_reached();
4267     }
4268
4269     client_find_edge_directional(self, dir, head, size,
4270                                  e_start, e_size, &e, &near);
4271     *x = area.x;
4272     *y = area.y;
4273     switch (dir) {
4274     case OB_DIRECTION_EAST:
4275         if (near) e -= area.width;
4276         else      e++;
4277         *x = e;
4278         break;
4279     case OB_DIRECTION_WEST:
4280         if (near) e++;
4281         else      e -= area.width;
4282         *x = e;
4283         break;
4284     case OB_DIRECTION_NORTH:
4285         if (near) e++;
4286         else      e -= area.height;
4287         *y = e;
4288         break;
4289     case OB_DIRECTION_SOUTH:
4290         if (near) e -= area.height;
4291         else      e++;
4292         *y = e;
4293         break;
4294     default:
4295         g_assert_not_reached();
4296     }
4297     frame_frame_gravity(self, x, y);
4298 }
4299
4300 void client_find_resize_directional(ObClient *self, ObDirection side,
4301                                     gboolean grow,
4302                                     gint *x, gint *y, gint *w, gint *h)
4303 {
4304     gint head;
4305     gint e, e_start, e_size, delta;
4306     gboolean near;
4307     ObDirection dir;
4308
4309     Rect area = render_plugin->frame_get_window_area(self->frame);
4310     Strut size = render_plugin->frame_get_size(self->frame);
4311
4312     switch (side) {
4313     case OB_DIRECTION_EAST:
4314         head = RECT_RIGHT(area) +
4315             (self->size_inc.width - 1) * (grow ? 1 : -1);
4316         e_start = RECT_TOP(area);
4317         e_size = area.height;
4318         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4319         break;
4320     case OB_DIRECTION_WEST:
4321         head = RECT_LEFT(area) -
4322             (self->size_inc.width - 1) * (grow ? 1 : -1);
4323         e_start = RECT_TOP(area);
4324         e_size = area.height;
4325         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4326         break;
4327     case OB_DIRECTION_NORTH:
4328         head = RECT_TOP(area) -
4329             (self->size_inc.height - 1) * (grow ? 1 : -1);
4330         e_start = RECT_LEFT(area);
4331         e_size = area.width;
4332         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4333         break;
4334     case OB_DIRECTION_SOUTH:
4335         head = RECT_BOTTOM(area) +
4336             (self->size_inc.height - 1) * (grow ? 1 : -1);
4337         e_start = RECT_LEFT(area);
4338         e_size = area.width;
4339         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4340         break;
4341     default:
4342         g_assert_not_reached();
4343     }
4344
4345     ob_debug("head %d dir %d", head, dir);
4346     client_find_edge_directional(self, dir, head, 1,
4347                                  e_start, e_size, &e, &near);
4348     ob_debug("edge %d", e);
4349     *x = area.x;
4350     *y = area.y;
4351     *w = area.width;
4352     *h = area.height;
4353     switch (side) {
4354     case OB_DIRECTION_EAST:
4355         if (grow == near) --e;
4356         delta = e - RECT_RIGHT(area);
4357         *w += delta;
4358         break;
4359     case OB_DIRECTION_WEST:
4360         if (grow == near) ++e;
4361         delta = RECT_LEFT(area) - e;
4362         *x -= delta;
4363         *w += delta;
4364         break;
4365     case OB_DIRECTION_NORTH:
4366         if (grow == near) ++e;
4367         delta = RECT_TOP(area) - e;
4368         *y -= delta;
4369         *h += delta;
4370         break;
4371     case OB_DIRECTION_SOUTH:
4372         if (grow == near) --e;
4373         delta = e - RECT_BOTTOM(area);
4374         *h += delta;
4375         break;
4376     default:
4377         g_assert_not_reached();
4378     }
4379     frame_frame_gravity(self, x, y);
4380     *w -= size.left + size.right;
4381     *h -= size.top + size.bottom;
4382 }
4383
4384 ObClient* client_under_pointer(void)
4385 {
4386     gint x, y;
4387     GList *it;
4388     ObClient *ret = NULL;
4389
4390     if (screen_pointer_pos(&x, &y)) {
4391         for (it = stacking_list; it; it = g_list_next(it)) {
4392             if (WINDOW_IS_CLIENT(it->data)) {
4393                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4394                 if (render_plugin->frame_is_visible(c->frame) &&
4395                     /* check the desktop, this is done during desktop
4396                        switching and windows are shown/hidden status is not
4397                        reliable */
4398                     (c->desktop == screen_desktop ||
4399                      c->desktop == DESKTOP_ALL) &&
4400                     /* ignore all animating windows */
4401                     !(render_plugin->frame_iconify_animating(c->frame)) &&
4402                     RECT_CONTAINS(render_plugin->frame_get_window_area(c->frame), x, y))
4403                 {
4404                     ret = c;
4405                     break;
4406                 }
4407             }
4408         }
4409     }
4410     return ret;
4411 }
4412
4413 gboolean client_has_group_siblings(ObClient *self)
4414 {
4415     return self->group && self->group->members->next;
4416 }