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