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