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