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