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