]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
remove debug prints
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.c for the Openbox window manager
4    Copyright (c) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "client.h"
20 #include "debug.h"
21 #include "startupnotify.h"
22 #include "dock.h"
23 #include "xerror.h"
24 #include "screen.h"
25 #include "moveresize.h"
26 #include "place.h"
27 #include "prop.h"
28 #include "extensions.h"
29 #include "frame.h"
30 #include "session.h"
31 #include "event.h"
32 #include "grab.h"
33 #include "focus.h"
34 #include "stacking.h"
35 #include "openbox.h"
36 #include "group.h"
37 #include "config.h"
38 #include "menuframe.h"
39 #include "keyboard.h"
40 #include "mouse.h"
41 #include "render/render.h"
42
43 #include <glib.h>
44 #include <X11/Xutil.h>
45
46 /*! The event mask to grab on client windows */
47 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
48                           StructureNotifyMask)
49
50 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
51                                 ButtonMotionMask)
52
53 typedef struct
54 {
55     ObClientDestructor func;
56     gpointer data;
57 } Destructor;
58
59 GList      *client_list        = NULL;
60 GSList     *client_destructors = NULL;
61
62 static void client_get_all(ObClient *self);
63 static void client_toggle_border(ObClient *self, gboolean show);
64 static void client_get_startup_id(ObClient *self);
65 static void client_get_area(ObClient *self);
66 static void client_get_desktop(ObClient *self);
67 static void client_get_state(ObClient *self);
68 static void client_get_shaped(ObClient *self);
69 static void client_get_mwm_hints(ObClient *self);
70 static void client_get_gravity(ObClient *self);
71 static void client_showhide(ObClient *self);
72 static void client_change_allowed_actions(ObClient *self);
73 static void client_change_state(ObClient *self);
74 static void client_apply_startup_state(ObClient *self);
75 static void client_restore_session_state(ObClient *self);
76 static void client_restore_session_stacking(ObClient *self);
77 static void client_urgent_notify(ObClient *self);
78
79 void client_startup(gboolean reconfig)
80 {
81     if (reconfig) return;
82
83     client_set_list();
84 }
85
86 void client_shutdown(gboolean reconfig)
87 {
88 }
89
90 void client_add_destructor(ObClientDestructor func, gpointer data)
91 {
92     Destructor *d = g_new(Destructor, 1);
93     d->func = func;
94     d->data = data;
95     client_destructors = g_slist_prepend(client_destructors, d);
96 }
97
98 void client_remove_destructor(ObClientDestructor func)
99 {
100     GSList *it;
101
102     for (it = client_destructors; it; it = g_slist_next(it)) {
103         Destructor *d = it->data;
104         if (d->func == func) {
105             g_free(d);
106             client_destructors = g_slist_delete_link(client_destructors, it);
107             break;
108         }
109     }
110 }
111
112 void client_set_list()
113 {
114     Window *windows, *win_it;
115     GList *it;
116     guint size = g_list_length(client_list);
117
118     /* create an array of the window ids */
119     if (size > 0) {
120         windows = g_new(Window, size);
121         win_it = windows;
122         for (it = client_list; it != NULL; it = it->next, ++win_it)
123             *win_it = ((ObClient*)it->data)->window;
124     } else
125         windows = NULL;
126
127     PROP_SETA32(RootWindow(ob_display, ob_screen),
128                 net_client_list, window, (guint32*)windows, size);
129
130     if (windows)
131         g_free(windows);
132
133     stacking_set_list();
134 }
135
136 /*
137 void client_foreach_transient(ObClient *self, ObClientForeachFunc func, void *data)
138 {
139     GSList *it;
140
141     for (it = self->transients; it; it = it->next) {
142         if (!func(it->data, data)) return;
143         client_foreach_transient(it->data, func, data);
144     }
145 }
146
147 void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, void *data)
148 {
149     if (self->transient_for) {
150         if (self->transient_for != OB_TRAN_GROUP) {
151             if (!func(self->transient_for, data)) return;
152             client_foreach_ancestor(self->transient_for, func, data);
153         } else {
154             GSList *it;
155
156             for (it = self->group->members; it; it = it->next)
157                 if (it->data != self &&
158                     !((ObClient*)it->data)->transient_for) {
159                     if (!func(it->data, data)) return;
160                     client_foreach_ancestor(it->data, func, data);
161                 }
162         }
163     }
164 }
165 */
166
167 void client_manage_all()
168 {
169     unsigned int i, j, nchild;
170     Window w, *children;
171     XWMHints *wmhints;
172     XWindowAttributes attrib;
173
174     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
175                &w, &w, &children, &nchild);
176
177     /* remove all icon windows from the list */
178     for (i = 0; i < nchild; i++) {
179         if (children[i] == None) continue;
180         wmhints = XGetWMHints(ob_display, children[i]);
181         if (wmhints) {
182             if ((wmhints->flags & IconWindowHint) &&
183                 (wmhints->icon_window != children[i]))
184                 for (j = 0; j < nchild; j++)
185                     if (children[j] == wmhints->icon_window) {
186                         children[j] = None;
187                         break;
188                     }
189             XFree(wmhints);
190         }
191     }
192
193     for (i = 0; i < nchild; ++i) {
194         if (children[i] == None)
195             continue;
196         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
197             if (attrib.override_redirect) continue;
198
199             if (attrib.map_state != IsUnmapped)
200                 client_manage(children[i]);
201         }
202     }
203     XFree(children);
204 }
205
206 void client_manage(Window window)
207 {
208     ObClient *self;
209     XEvent e;
210     XWindowAttributes attrib;
211     XSetWindowAttributes attrib_set;
212     XWMHints *wmhint;
213     gboolean activate = FALSE;
214
215     grab_server(TRUE);
216
217     /* check if it has already been unmapped by the time we started mapping
218        the grab does a sync so we don't have to here */
219     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
220         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
221         XPutBackEvent(ob_display, &e);
222
223         grab_server(FALSE);
224         return; /* don't manage it */
225     }
226
227     /* make sure it isn't an override-redirect window */
228     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
229         attrib.override_redirect) {
230         grab_server(FALSE);
231         return; /* don't manage it */
232     }
233   
234     /* is the window a docking app */
235     if ((wmhint = XGetWMHints(ob_display, window))) {
236         if ((wmhint->flags & StateHint) &&
237             wmhint->initial_state == WithdrawnState) {
238             dock_add(window, wmhint);
239             grab_server(FALSE);
240             XFree(wmhint);
241             return;
242         }
243         XFree(wmhint);
244     }
245
246     ob_debug("Managing window: %lx\n", window);
247
248     /* choose the events we want to receive on the CLIENT window */
249     attrib_set.event_mask = CLIENT_EVENTMASK;
250     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
251     XChangeWindowAttributes(ob_display, window,
252                             CWEventMask|CWDontPropagate, &attrib_set);
253
254
255     /* create the ObClient struct, and populate it from the hints on the
256        window */
257     self = g_new0(ObClient, 1);
258     self->obwin.type = Window_Client;
259     self->window = window;
260
261     /* non-zero defaults */
262     self->title_count = 1;
263     self->wmstate = NormalState;
264     self->layer = -1;
265     self->desktop = screen_num_desktops; /* always an invalid value */
266
267     client_get_all(self);
268     client_restore_session_state(self);
269
270     sn_app_started(self->class);
271
272     client_change_state(self);
273
274     /* remove the client's border (and adjust re gravity) */
275     client_toggle_border(self, FALSE);
276      
277     /* specify that if we exit, the window should not be destroyed and should
278        be reparented back to root automatically */
279     XChangeSaveSet(ob_display, window, SetModeInsert);
280
281     /* create the decoration frame for the client window */
282     self->frame = frame_new();
283
284     frame_grab_client(self->frame, self);
285
286     grab_server(FALSE);
287
288     client_apply_startup_state(self);
289
290     /* update the focus lists */
291     focus_order_add_new(self);
292
293     stacking_add(CLIENT_AS_WINDOW(self));
294     client_restore_session_stacking(self);
295
296     /* focus the new window? */
297     if (ob_state() != OB_STATE_STARTING &&
298         (config_focus_new || client_search_focus_parent(self)) &&
299         /* note the check against Type_Normal/Dialog, not client_normal(self),
300            which would also include other types. in this case we want more
301            strict rules for focus */
302         (self->type == OB_CLIENT_TYPE_NORMAL ||
303          self->type == OB_CLIENT_TYPE_DIALOG))
304     {        
305         activate = TRUE;
306 #if 0
307         if (self->desktop != screen_desktop) {
308             /* activate the window */
309             activate = TRUE;
310         } else {
311             gboolean group_foc = FALSE;
312
313             if (self->group) {
314                 GSList *it;
315
316                 for (it = self->group->members; it; it = it->next)
317                 {
318                     if (client_focused(it->data))
319                     {
320                         group_foc = TRUE;
321                         break;
322                     }
323                 }
324             }
325             if ((group_foc ||
326                  (!self->transient_for && (!self->group ||
327                                            !self->group->members->next))) ||
328                 client_search_focus_tree_full(self) ||
329                 !focus_client ||
330                 !client_normal(focus_client))
331             {
332                 /* activate the window */
333                 activate = TRUE;
334             }
335         }
336 #endif
337     }
338
339     if (ob_state() == OB_STATE_RUNNING) {
340         int x = self->area.x, ox = x;
341         int y = self->area.y, oy = y;
342
343         place_client(self, &x, &y);
344
345         /* make sure the window is visible */
346         client_find_onscreen(self, &x, &y,
347                              self->frame->area.width,
348                              self->frame->area.height,
349                              client_normal(self));
350
351         if (x != ox || y != oy)
352             client_move(self, x, y);
353     }
354
355     client_showhide(self);
356
357     /* use client_focus instead of client_activate cuz client_activate does
358        stuff like switch desktops etc and I'm not interested in all that when
359        a window maps since its not based on an action from the user like
360        clicking a window to activate is. so keep the new window out of the way
361        but do focus it. */
362     if (activate) client_focus(self);
363
364     /* client_activate does this but we aret using it so we have to do it
365        here as well */
366     if (screen_showing_desktop)
367         screen_show_desktop(FALSE);
368
369     /* add to client list/map */
370     client_list = g_list_append(client_list, self);
371     g_hash_table_insert(window_map, &self->window, self);
372
373     /* this has to happen after we're in the client_list */
374     screen_update_areas();
375
376     /* update the list hints */
377     client_set_list();
378
379     keyboard_grab_for_client(self, TRUE);
380     mouse_grab_for_client(self, TRUE);
381
382     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
383 }
384
385 void client_unmanage_all()
386 {
387     while (client_list != NULL)
388         client_unmanage(client_list->data);
389 }
390
391 void client_unmanage(ObClient *self)
392 {
393     guint j;
394     GSList *it;
395
396     ob_debug("Unmanaging window: %lx (%s)\n", self->window, self->class);
397
398     g_assert(self != NULL);
399
400     keyboard_grab_for_client(self, FALSE);
401     mouse_grab_for_client(self, FALSE);
402
403     /* remove the window from our save set */
404     XChangeSaveSet(ob_display, self->window, SetModeDelete);
405
406     /* we dont want events no more */
407     XSelectInput(ob_display, self->window, NoEventMask);
408
409     frame_hide(self->frame);
410
411     client_list = g_list_remove(client_list, self);
412     stacking_remove(self);
413     g_hash_table_remove(window_map, &self->window);
414
415     /* update the focus lists */
416     focus_order_remove(self);
417
418     /* once the client is out of the list, update the struts to remove it's
419        influence */
420     screen_update_areas();
421
422     for (it = client_destructors; it; it = g_slist_next(it)) {
423         Destructor *d = it->data;
424         d->func(self, d->data);
425     }
426         
427     if (focus_client == self) {
428         XEvent e;
429
430         /* focus the last focused window on the desktop, and ignore enter
431            events from the unmap so it doesnt mess with the focus */
432         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
433         /* remove these flags so we don't end up getting focused in the
434            fallback! */
435         self->can_focus = FALSE;
436         self->focus_notify = FALSE;
437         self->modal = FALSE;
438         client_unfocus(self);
439     }
440
441     /* tell our parent(s) that we're gone */
442     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
443         GSList *it;
444
445         for (it = self->group->members; it; it = it->next)
446             if (it->data != self)
447                 ((ObClient*)it->data)->transients =
448                     g_slist_remove(((ObClient*)it->data)->transients, self);
449     } else if (self->transient_for) {        /* transient of window */
450         self->transient_for->transients =
451             g_slist_remove(self->transient_for->transients, self);
452     }
453
454     /* tell our transients that we're gone */
455     for (it = self->transients; it != NULL; it = it->next) {
456         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
457             ((ObClient*)it->data)->transient_for = NULL;
458             client_calc_layer(it->data);
459         }
460     }
461
462     /* remove from its group */
463     if (self->group) {
464         group_remove(self->group, self);
465         self->group = NULL;
466     }
467
468     /* give the client its border back */
469     client_toggle_border(self, TRUE);
470
471     /* reparent the window out of the frame, and free the frame */
472     frame_release_client(self->frame, self);
473     self->frame = NULL;
474      
475     if (ob_state() != OB_STATE_EXITING) {
476         /* these values should not be persisted across a window
477            unmapping/mapping */
478         PROP_ERASE(self->window, net_wm_desktop);
479         PROP_ERASE(self->window, net_wm_state);
480         PROP_ERASE(self->window, wm_state);
481     } else {
482         /* if we're left in an iconic state, the client wont be mapped. this is
483            bad, since we will no longer be managing the window on restart */
484         if (self->iconic)
485             XMapWindow(ob_display, self->window);
486     }
487
488
489     ob_debug("Unmanaged window 0x%lx\n", self->window);
490
491     /* free all data allocated in the client struct */
492     g_slist_free(self->transients);
493     for (j = 0; j < self->nicons; ++j)
494         g_free(self->icons[j].data);
495     if (self->nicons > 0)
496         g_free(self->icons);
497     g_free(self->title);
498     g_free(self->icon_title);
499     g_free(self->name);
500     g_free(self->class);
501     g_free(self->role);
502     g_free(self->sm_client_id);
503     g_free(self);
504      
505     /* update the list hints */
506     client_set_list();
507 }
508
509 static void client_urgent_notify(ObClient *self)
510 {
511     if (self->urgent)
512         frame_flash_start(self->frame);
513     else
514         frame_flash_stop(self->frame);
515 }
516
517 static void client_restore_session_state(ObClient *self)
518 {
519     GList *it;
520
521     if (!(it = session_state_find(self)))
522         return;
523
524     self->session = it->data;
525
526     RECT_SET(self->area, self->session->x, self->session->y,
527              self->session->w, self->session->h);
528     self->positioned = TRUE;
529     XResizeWindow(ob_display, self->window,
530                   self->session->w, self->session->h);
531
532     self->desktop = (self->session->desktop == DESKTOP_ALL ?
533                      self->session->desktop :
534                      MIN(screen_num_desktops - 1, self->session->desktop));
535     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
536
537     self->shaded = self->session->shaded;
538     self->iconic = self->session->iconic;
539     self->skip_pager = self->session->skip_pager;
540     self->skip_taskbar = self->session->skip_taskbar;
541     self->fullscreen = self->session->fullscreen;
542     self->above = self->session->above;
543     self->below = self->session->below;
544     self->max_horz = self->session->max_horz;
545     self->max_vert = self->session->max_vert;
546 }
547
548 static void client_restore_session_stacking(ObClient *self)
549 {
550     GList *it;
551
552     if (!self->session) return;
553
554     it = g_list_find(session_saved_state, self->session);
555     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
556         GList *cit;
557
558         for (cit = client_list; cit; cit = g_list_next(cit))
559             if (session_state_cmp(it->data, cit->data))
560                 break;
561         if (cit) {
562             client_calc_layer(self);
563             stacking_below(CLIENT_AS_WINDOW(self),
564                            CLIENT_AS_WINDOW(cit->data));
565             break;
566         }
567     }
568 }
569
570 void client_move_onscreen(ObClient *self, gboolean rude)
571 {
572     int x = self->area.x;
573     int y = self->area.y;
574     if (client_find_onscreen(self, &x, &y,
575                              self->frame->area.width,
576                              self->frame->area.height, rude)) {
577         client_move(self, x, y);
578     }
579 }
580
581 gboolean client_find_onscreen(ObClient *self, int *x, int *y, int w, int h,
582                               gboolean rude)
583 {
584     Rect *a;
585     int ox = *x, oy = *y;
586
587     frame_client_gravity(self->frame, x, y); /* get where the frame
588                                                 would be */
589
590     /* XXX watch for xinerama dead areas */
591
592     a = screen_area(self->desktop);
593     if (client_normal(self)) {
594         if (!self->strut.right && *x >= a->x + a->width - 1)
595             *x = a->x + a->width - self->frame->area.width;
596         if (!self->strut.bottom && *y >= a->y + a->height - 1)
597             *y = a->y + a->height - self->frame->area.height;
598         if (!self->strut.left && *x + self->frame->area.width - 1 < a->x)
599             *x = a->x;
600         if (!self->strut.top && *y + self->frame->area.height - 1 < a->y)
601             *y = a->y;
602     }
603
604     if (rude) {
605         /* this is my MOZILLA BITCHSLAP. oh ya it fucking feels good.
606            Java can suck it too. */
607
608         /* dont let windows map/move into the strut unless they
609            are bigger than the available area */
610         if (w <= a->width) {
611             if (!self->strut.left && *x < a->x) *x = a->x;
612             if (!self->strut.right && *x + w > a->x + a->width)
613                 *x = a->x + a->width - w;
614         }
615         if (h <= a->height) {
616             if (!self->strut.top && *y < a->y) *y = a->y;
617             if (!self->strut.bottom && *y + h > a->y + a->height)
618                 *y = a->y + a->height - h;
619         }
620     }
621
622     frame_frame_gravity(self->frame, x, y); /* get where the client
623                                                should be */
624
625     return ox != *x || oy != *y;
626 }
627
628 static void client_toggle_border(ObClient *self, gboolean show)
629 {
630     /* adjust our idea of where the client is, based on its border. When the
631        border is removed, the client should now be considered to be in a
632        different position.
633        when re-adding the border to the client, the same operation needs to be
634        reversed. */
635     int oldx = self->area.x, oldy = self->area.y;
636     int x = oldx, y = oldy;
637     switch(self->gravity) {
638     default:
639     case NorthWestGravity:
640     case WestGravity:
641     case SouthWestGravity:
642         break;
643     case NorthEastGravity:
644     case EastGravity:
645     case SouthEastGravity:
646         if (show) x -= self->border_width * 2;
647         else      x += self->border_width * 2;
648         break;
649     case NorthGravity:
650     case SouthGravity:
651     case CenterGravity:
652     case ForgetGravity:
653     case StaticGravity:
654         if (show) x -= self->border_width;
655         else      x += self->border_width;
656         break;
657     }
658     switch(self->gravity) {
659     default:
660     case NorthWestGravity:
661     case NorthGravity:
662     case NorthEastGravity:
663         break;
664     case SouthWestGravity:
665     case SouthGravity:
666     case SouthEastGravity:
667         if (show) y -= self->border_width * 2;
668         else      y += self->border_width * 2;
669         break;
670     case WestGravity:
671     case EastGravity:
672     case CenterGravity:
673     case ForgetGravity:
674     case StaticGravity:
675         if (show) y -= self->border_width;
676         else      y += self->border_width;
677         break;
678     }
679     self->area.x = x;
680     self->area.y = y;
681
682     if (show) {
683         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
684
685         /* move the client so it is back it the right spot _with_ its
686            border! */
687         if (x != oldx || y != oldy)
688             XMoveWindow(ob_display, self->window, x, y);
689     } else
690         XSetWindowBorderWidth(ob_display, self->window, 0);
691 }
692
693
694 static void client_get_all(ObClient *self)
695 {
696     client_get_area(self);
697     client_update_transient_for(self);
698     client_update_wmhints(self);
699     client_get_startup_id(self);
700     client_get_desktop(self);
701     client_get_state(self);
702     client_get_shaped(self);
703
704     client_get_mwm_hints(self);
705     client_get_type(self);/* this can change the mwmhints for special cases */
706
707     client_update_protocols(self);
708
709     client_get_gravity(self); /* get the attribute gravity */
710     client_update_normal_hints(self); /* this may override the attribute
711                                          gravity */
712
713     /* got the type, the mwmhints, the protocols, and the normal hints
714        (min/max sizes), so we're ready to set up the decorations/functions */
715     client_setup_decor_and_functions(self);
716   
717     client_update_title(self);
718     client_update_class(self);
719     client_update_sm_client_id(self);
720     client_update_strut(self);
721     client_update_icons(self);
722 }
723
724 static void client_get_startup_id(ObClient *self)
725 {
726     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
727         if (self->group)
728             PROP_GETS(self->group->leader,
729                       net_startup_id, utf8, &self->startup_id);
730 }
731
732 static void client_get_area(ObClient *self)
733 {
734     XWindowAttributes wattrib;
735     Status ret;
736   
737     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
738     g_assert(ret != BadWindow);
739
740     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
741     self->border_width = wattrib.border_width;
742 }
743
744 static void client_get_desktop(ObClient *self)
745 {
746     guint32 d = screen_num_desktops; /* an always-invalid value */
747
748     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
749         if (d >= screen_num_desktops && d != DESKTOP_ALL)
750             self->desktop = screen_num_desktops - 1;
751         else
752             self->desktop = d;
753     } else {
754         gboolean trdesk = FALSE;
755
756        if (self->transient_for) {
757            if (self->transient_for != OB_TRAN_GROUP) {
758                 self->desktop = self->transient_for->desktop;
759                 trdesk = TRUE;
760             } else {
761                 GSList *it;
762
763                 for (it = self->group->members; it; it = it->next)
764                     if (it->data != self &&
765                         !((ObClient*)it->data)->transient_for) {
766                         self->desktop = ((ObClient*)it->data)->desktop;
767                         trdesk = TRUE;
768                         break;
769                     }
770             }
771        }
772        if (!trdesk) {
773            /* try get from the startup-notification protocol */
774            if (sn_get_desktop(self->startup_id, &self->desktop)) {
775                if (self->desktop >= screen_num_desktops &&
776                    self->desktop != DESKTOP_ALL)
777                    self->desktop = screen_num_desktops - 1;
778            } else
779                /* defaults to the current desktop */
780                self->desktop = screen_desktop;
781        }
782     }
783     if (self->desktop != d) {
784         /* set the desktop hint, to make sure that it always exists */
785         PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
786     }
787 }
788
789 static void client_get_state(ObClient *self)
790 {
791     guint32 *state;
792     guint num;
793   
794     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
795         gulong i;
796         for (i = 0; i < num; ++i) {
797             if (state[i] == prop_atoms.net_wm_state_modal)
798                 self->modal = TRUE;
799             else if (state[i] == prop_atoms.net_wm_state_shaded)
800                 self->shaded = TRUE;
801             else if (state[i] == prop_atoms.net_wm_state_hidden)
802                 self->iconic = TRUE;
803             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
804                 self->skip_taskbar = TRUE;
805             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
806                 self->skip_pager = TRUE;
807             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
808                 self->fullscreen = TRUE;
809             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
810                 self->max_vert = TRUE;
811             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
812                 self->max_horz = TRUE;
813             else if (state[i] == prop_atoms.net_wm_state_above)
814                 self->above = TRUE;
815             else if (state[i] == prop_atoms.net_wm_state_below)
816                 self->below = TRUE;
817             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
818                 self->undecorated = TRUE;
819         }
820
821         g_free(state);
822     }
823 }
824
825 static void client_get_shaped(ObClient *self)
826 {
827     self->shaped = FALSE;
828 #ifdef   SHAPE
829     if (extensions_shape) {
830         int foo;
831         guint ufoo;
832         int s;
833
834         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
835
836         XShapeQueryExtents(ob_display, self->window, &s, &foo,
837                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
838                            &ufoo);
839         self->shaped = (s != 0);
840     }
841 #endif
842 }
843
844 void client_update_transient_for(ObClient *self)
845 {
846     Window t = None;
847     ObClient *target = NULL;
848
849     if (XGetTransientForHint(ob_display, self->window, &t)) {
850         self->transient = TRUE;
851         if (t != self->window) { /* cant be transient to itself! */
852             target = g_hash_table_lookup(window_map, &t);
853             /* if this happens then we need to check for it*/
854             g_assert(target != self);
855             if (target && !WINDOW_IS_CLIENT(target)) {
856                 /* this can happen when a dialog is a child of
857                    a dockapp, for example */
858                 target = NULL;
859             }
860             
861             if (!target && self->group) {
862                 /* not transient to a client, see if it is transient for a
863                    group */
864                 if (t == self->group->leader ||
865                     t == None ||
866                     t == RootWindow(ob_display, ob_screen))
867                 {
868                     /* window is a transient for its group! */
869                     target = OB_TRAN_GROUP;
870                 }
871             }
872         }
873     } else
874         self->transient = FALSE;
875
876     /* if anything has changed... */
877     if (target != self->transient_for) {
878         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
879             GSList *it;
880
881             /* remove from old parents */
882             for (it = self->group->members; it; it = g_slist_next(it)) {
883                 ObClient *c = it->data;
884                 if (c != self && !c->transient_for)
885                     c->transients = g_slist_remove(c->transients, self);
886             }
887         } else if (self->transient_for != NULL) { /* transient of window */
888             /* remove from old parent */
889             self->transient_for->transients =
890                 g_slist_remove(self->transient_for->transients, self);
891         }
892         self->transient_for = target;
893         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
894             GSList *it;
895
896             /* add to new parents */
897             for (it = self->group->members; it; it = g_slist_next(it)) {
898                 ObClient *c = it->data;
899                 if (c != self && !c->transient_for)
900                     c->transients = g_slist_append(c->transients, self);
901             }
902
903             /* remove all transients which are in the group, that causes
904                circlular pointer hell of doom */
905             for (it = self->group->members; it; it = g_slist_next(it)) {
906                 GSList *sit, *next;
907                 for (sit = self->transients; sit; sit = next) {
908                     next = g_slist_next(sit);
909                     if (sit->data == it->data)
910                         self->transients =
911                             g_slist_delete_link(self->transients, sit);
912                 }
913             }
914         } else if (self->transient_for != NULL) { /* transient of window */
915             /* add to new parent */
916             self->transient_for->transients =
917                 g_slist_append(self->transient_for->transients, self);
918         }
919     }
920 }
921
922 static void client_get_mwm_hints(ObClient *self)
923 {
924     guint num;
925     guint32 *hints;
926
927     self->mwmhints.flags = 0; /* default to none */
928
929     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
930                     &hints, &num)) {
931         if (num >= OB_MWM_ELEMENTS) {
932             self->mwmhints.flags = hints[0];
933             self->mwmhints.functions = hints[1];
934             self->mwmhints.decorations = hints[2];
935         }
936         g_free(hints);
937     }
938 }
939
940 void client_get_type(ObClient *self)
941 {
942     guint num, i;
943     guint32 *val;
944
945     self->type = -1;
946   
947     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
948         /* use the first value that we know about in the array */
949         for (i = 0; i < num; ++i) {
950             if (val[i] == prop_atoms.net_wm_window_type_desktop)
951                 self->type = OB_CLIENT_TYPE_DESKTOP;
952             else if (val[i] == prop_atoms.net_wm_window_type_dock)
953                 self->type = OB_CLIENT_TYPE_DOCK;
954             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
955                 self->type = OB_CLIENT_TYPE_TOOLBAR;
956             else if (val[i] == prop_atoms.net_wm_window_type_menu)
957                 self->type = OB_CLIENT_TYPE_MENU;
958             else if (val[i] == prop_atoms.net_wm_window_type_utility)
959                 self->type = OB_CLIENT_TYPE_UTILITY;
960             else if (val[i] == prop_atoms.net_wm_window_type_splash)
961                 self->type = OB_CLIENT_TYPE_SPLASH;
962             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
963                 self->type = OB_CLIENT_TYPE_DIALOG;
964             else if (val[i] == prop_atoms.net_wm_window_type_normal)
965                 self->type = OB_CLIENT_TYPE_NORMAL;
966             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
967                 /* prevent this window from getting any decor or
968                    functionality */
969                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
970                                          OB_MWM_FLAG_DECORATIONS);
971                 self->mwmhints.decorations = 0;
972                 self->mwmhints.functions = 0;
973             }
974             if (self->type != (ObClientType) -1)
975                 break; /* grab the first legit type */
976         }
977         g_free(val);
978     }
979     
980     if (self->type == (ObClientType) -1) {
981         /*the window type hint was not set, which means we either classify
982           ourself as a normal window or a dialog, depending on if we are a
983           transient. */
984         if (self->transient)
985             self->type = OB_CLIENT_TYPE_DIALOG;
986         else
987             self->type = OB_CLIENT_TYPE_NORMAL;
988     }
989 }
990
991 void client_update_protocols(ObClient *self)
992 {
993     guint32 *proto;
994     guint num_return, i;
995
996     self->focus_notify = FALSE;
997     self->delete_window = FALSE;
998
999     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1000         for (i = 0; i < num_return; ++i) {
1001             if (proto[i] == prop_atoms.wm_delete_window) {
1002                 /* this means we can request the window to close */
1003                 self->delete_window = TRUE;
1004             } else if (proto[i] == prop_atoms.wm_take_focus)
1005                 /* if this protocol is requested, then the window will be
1006                    notified whenever we want it to receive focus */
1007                 self->focus_notify = TRUE;
1008         }
1009         g_free(proto);
1010     }
1011 }
1012
1013 static void client_get_gravity(ObClient *self)
1014 {
1015     XWindowAttributes wattrib;
1016     Status ret;
1017
1018     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1019     g_assert(ret != BadWindow);
1020     self->gravity = wattrib.win_gravity;
1021 }
1022
1023 void client_update_normal_hints(ObClient *self)
1024 {
1025     XSizeHints size;
1026     long ret;
1027     int oldgravity = self->gravity;
1028
1029     /* defaults */
1030     self->min_ratio = 0.0f;
1031     self->max_ratio = 0.0f;
1032     SIZE_SET(self->size_inc, 1, 1);
1033     SIZE_SET(self->base_size, 0, 0);
1034     SIZE_SET(self->min_size, 0, 0);
1035     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1036
1037     /* get the hints from the window */
1038     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1039         self->positioned = !!(size.flags & (PPosition|USPosition));
1040
1041         if (size.flags & PWinGravity) {
1042             self->gravity = size.win_gravity;
1043       
1044             /* if the client has a frame, i.e. has already been mapped and
1045                is changing its gravity */
1046             if (self->frame && self->gravity != oldgravity) {
1047                 /* move our idea of the client's position based on its new
1048                    gravity */
1049                 self->area.x = self->frame->area.x;
1050                 self->area.y = self->frame->area.y;
1051                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1052             }
1053         }
1054
1055         if (size.flags & PAspect) {
1056             if (size.min_aspect.y)
1057                 self->min_ratio = (float)size.min_aspect.x / size.min_aspect.y;
1058             if (size.max_aspect.y)
1059                 self->max_ratio = (float)size.max_aspect.x / size.max_aspect.y;
1060         }
1061
1062         if (size.flags & PMinSize)
1063             SIZE_SET(self->min_size, size.min_width, size.min_height);
1064     
1065         if (size.flags & PMaxSize)
1066             SIZE_SET(self->max_size, size.max_width, size.max_height);
1067     
1068         if (size.flags & PBaseSize)
1069             SIZE_SET(self->base_size, size.base_width, size.base_height);
1070     
1071         if (size.flags & PResizeInc)
1072             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1073     }
1074 }
1075
1076 void client_setup_decor_and_functions(ObClient *self)
1077 {
1078     /* start with everything (cept fullscreen) */
1079     self->decorations =
1080         (OB_FRAME_DECOR_TITLEBAR |
1081          (ob_rr_theme->show_handle ? OB_FRAME_DECOR_HANDLE : 0) |
1082          OB_FRAME_DECOR_GRIPS |
1083          OB_FRAME_DECOR_BORDER |
1084          OB_FRAME_DECOR_ICON |
1085          OB_FRAME_DECOR_ALLDESKTOPS |
1086          OB_FRAME_DECOR_ICONIFY |
1087          OB_FRAME_DECOR_MAXIMIZE |
1088          OB_FRAME_DECOR_SHADE);
1089     self->functions =
1090         (OB_CLIENT_FUNC_RESIZE |
1091          OB_CLIENT_FUNC_MOVE |
1092          OB_CLIENT_FUNC_ICONIFY |
1093          OB_CLIENT_FUNC_MAXIMIZE |
1094          OB_CLIENT_FUNC_SHADE);
1095     if (self->delete_window) {
1096         self->functions |= OB_CLIENT_FUNC_CLOSE;
1097         self->decorations |= OB_FRAME_DECOR_CLOSE;
1098     }
1099
1100     if (!(self->min_size.width < self->max_size.width ||
1101           self->min_size.height < self->max_size.height))
1102         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1103
1104     switch (self->type) {
1105     case OB_CLIENT_TYPE_NORMAL:
1106         /* normal windows retain all of the possible decorations and
1107            functionality, and are the only windows that you can fullscreen */
1108         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1109         break;
1110
1111     case OB_CLIENT_TYPE_DIALOG:
1112     case OB_CLIENT_TYPE_UTILITY:
1113         /* these windows cannot be maximized */
1114         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1115         break;
1116
1117     case OB_CLIENT_TYPE_MENU:
1118     case OB_CLIENT_TYPE_TOOLBAR:
1119         /* these windows get less functionality */
1120         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1121         break;
1122
1123     case OB_CLIENT_TYPE_DESKTOP:
1124     case OB_CLIENT_TYPE_DOCK:
1125     case OB_CLIENT_TYPE_SPLASH:
1126         /* none of these windows are manipulated by the window manager */
1127         self->decorations = 0;
1128         self->functions = 0;
1129         break;
1130     }
1131
1132     /* Mwm Hints are applied subtractively to what has already been chosen for
1133        decor and functionality */
1134     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1135         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1136             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1137                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1138                 /* if the mwm hints request no handle or title, then all
1139                    decorations are disabled */
1140                 self->decorations = 0;
1141         }
1142     }
1143
1144     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1145         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1146             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1147                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1148             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1149                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1150             /* dont let mwm hints kill any buttons
1151             if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1152                 self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1153             if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1154                 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1155             */
1156             /* dont let mwm hints kill the close button
1157                if (! (self->mwmhints.functions & MwmFunc_Close))
1158                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1159         }
1160     }
1161
1162     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1163         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1164     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1165         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1166     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1167         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1168
1169     /* can't maximize without moving/resizing */
1170     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1171           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1172           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1173         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1174         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1175     }
1176
1177     /* kill the handle on fully maxed windows */
1178     if (self->max_vert && self->max_horz)
1179         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1180
1181     /* finally, the user can have requested no decorations, which overrides
1182        everything */
1183     if (self->undecorated)
1184         self->decorations = OB_FRAME_DECOR_BORDER;
1185
1186     /* if we don't have a titlebar, then we cannot shade! */
1187     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1188         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1189
1190     /* now we need to check against rules for the client's current state */
1191     if (self->fullscreen) {
1192         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1193                             OB_CLIENT_FUNC_FULLSCREEN |
1194                             OB_CLIENT_FUNC_ICONIFY);
1195         self->decorations = 0;
1196     }
1197
1198     client_change_allowed_actions(self);
1199
1200     if (self->frame) {
1201         /* adjust the client's decorations, etc. */
1202         client_reconfigure(self);
1203     } else {
1204         /* this makes sure that these windows appear on all desktops */
1205         if (self->type == OB_CLIENT_TYPE_DESKTOP &&
1206             self->desktop != DESKTOP_ALL)
1207         {
1208             self->desktop = DESKTOP_ALL;
1209         }
1210     }
1211 }
1212
1213 static void client_change_allowed_actions(ObClient *self)
1214 {
1215     guint32 actions[9];
1216     int num = 0;
1217
1218     /* desktop windows are kept on all desktops */
1219     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1220         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1221
1222     if (self->functions & OB_CLIENT_FUNC_SHADE)
1223         actions[num++] = prop_atoms.net_wm_action_shade;
1224     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1225         actions[num++] = prop_atoms.net_wm_action_close;
1226     if (self->functions & OB_CLIENT_FUNC_MOVE)
1227         actions[num++] = prop_atoms.net_wm_action_move;
1228     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1229         actions[num++] = prop_atoms.net_wm_action_minimize;
1230     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1231         actions[num++] = prop_atoms.net_wm_action_resize;
1232     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1233         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1234     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1235         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1236         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1237     }
1238
1239     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1240
1241     /* make sure the window isn't breaking any rules now */
1242
1243     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1244         if (self->frame) client_shade(self, FALSE);
1245         else self->shaded = FALSE;
1246     }
1247     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1248         if (self->frame) client_iconify(self, FALSE, TRUE);
1249         else self->iconic = FALSE;
1250     }
1251     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1252         if (self->frame) client_fullscreen(self, FALSE, TRUE);
1253         else self->fullscreen = FALSE;
1254     }
1255     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1256                                                          self->max_vert)) {
1257         if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1258         else self->max_vert = self->max_horz = FALSE;
1259     }
1260 }
1261
1262 void client_reconfigure(ObClient *self)
1263 {
1264     /* by making this pass FALSE for user, we avoid the emacs event storm where
1265        every configurenotify causes an update in its normal hints, i think this
1266        is generally what we want anyways... */
1267     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1268                      self->area.width, self->area.height, FALSE, TRUE);
1269 }
1270
1271 void client_update_wmhints(ObClient *self)
1272 {
1273     XWMHints *hints;
1274     gboolean ur = FALSE;
1275     GSList *it;
1276
1277     /* assume a window takes input if it doesnt specify */
1278     self->can_focus = TRUE;
1279   
1280     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1281         if (hints->flags & InputHint)
1282             self->can_focus = hints->input;
1283
1284         /* only do this when first managing the window *AND* when we aren't
1285            starting up! */
1286         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1287             if (hints->flags & StateHint)
1288                 self->iconic = hints->initial_state == IconicState;
1289
1290         if (hints->flags & XUrgencyHint)
1291             ur = TRUE;
1292
1293         if (!(hints->flags & WindowGroupHint))
1294             hints->window_group = None;
1295
1296         /* did the group state change? */
1297         if (hints->window_group !=
1298             (self->group ? self->group->leader : None)) {
1299             /* remove from the old group if there was one */
1300             if (self->group != NULL) {
1301                 /* remove transients of the group */
1302                 for (it = self->group->members; it; it = it->next)
1303                     self->transients = g_slist_remove(self->transients,
1304                                                       it->data);
1305
1306                 /* remove myself from parents in the group */
1307                 if (self->transient_for == OB_TRAN_GROUP) {
1308                     for (it = self->group->members; it; it = it->next) {
1309                         ObClient *c = it->data;
1310
1311                         if (c != self && !c->transient_for)
1312                             c->transients = g_slist_remove(c->transients,
1313                                                            self);
1314                     }
1315                 }
1316
1317                 group_remove(self->group, self);
1318                 self->group = NULL;
1319             }
1320             if (hints->window_group != None) {
1321                 self->group = group_add(hints->window_group, self);
1322
1323                 /* i can only have transients from the group if i am not
1324                    transient myself */
1325                 if (!self->transient_for) {
1326                     /* add other transients of the group that are already
1327                        set up */
1328                     for (it = self->group->members; it; it = it->next) {
1329                         ObClient *c = it->data;
1330                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1331                             self->transients =
1332                                 g_slist_append(self->transients, c);
1333                     }
1334                 }
1335             }
1336
1337             /* because the self->transient flag wont change from this call,
1338                we don't need to update the window's type and such, only its
1339                transient_for, and the transients lists of other windows in
1340                the group may be affected */
1341             client_update_transient_for(self);
1342         }
1343
1344         /* the WM_HINTS can contain an icon */
1345         client_update_icons(self);
1346
1347         XFree(hints);
1348     }
1349
1350     if (ur != self->urgent) {
1351         self->urgent = ur;
1352         /* fire the urgent callback if we're mapped, otherwise, wait until
1353            after we're mapped */
1354         if (self->frame)
1355             client_urgent_notify(self);
1356     }
1357 }
1358
1359 void client_update_title(ObClient *self)
1360 {
1361     GList *it;
1362     guint32 nums;
1363     guint i;
1364     gchar *data = NULL;
1365     gboolean read_title;
1366     gchar *old_title;
1367
1368     old_title = self->title;
1369      
1370     /* try netwm */
1371     if (!PROP_GETS(self->window, net_wm_name, utf8, &data))
1372         /* try old x stuff */
1373         if (!PROP_GETS(self->window, wm_name, locale, &data))
1374             data = g_strdup("Unnamed Window");
1375
1376     /* did the title change? then reset the title_count */
1377     if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1378         self->title_count = 1;
1379
1380     /* look for duplicates and append a number */
1381     nums = 0;
1382     for (it = client_list; it; it = it->next)
1383         if (it->data != self) {
1384             ObClient *c = it->data;
1385             if (0 == strncmp(c->title, data, strlen(data)))
1386                 nums |= 1 << c->title_count;
1387         }
1388     /* find first free number */
1389     for (i = 1; i <= 32; ++i)
1390         if (!(nums & (1 << i))) {
1391             if (self->title_count == 1 || i == 1)
1392                 self->title_count = i;
1393             break;
1394         }
1395     /* dont display the number for the first window */
1396     if (self->title_count > 1) {
1397         char *ndata;
1398         ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1399         g_free(data);
1400         data = ndata;
1401     }
1402
1403     PROP_SETS(self->window, net_wm_visible_name, data);
1404
1405     self->title = data;
1406
1407     if (self->frame)
1408         frame_adjust_title(self->frame);
1409
1410     g_free(old_title);
1411
1412     /* update the icon title */
1413     data = NULL;
1414     g_free(self->icon_title);
1415
1416     read_title = TRUE;
1417     /* try netwm */
1418     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1419         /* try old x stuff */
1420         if (!PROP_GETS(self->window, wm_icon_name, locale, &data)) {
1421             data = g_strdup(self->title);
1422             read_title = FALSE;
1423         }
1424
1425     /* append the title count, dont display the number for the first window */
1426     if (read_title && self->title_count > 1) {
1427         char *vdata, *ndata;
1428         ndata = g_strdup_printf(" - [%u]", self->title_count);
1429         vdata = g_strconcat(data, ndata, NULL);
1430         g_free(ndata);
1431         g_free(data);
1432         data = vdata;
1433     }
1434
1435     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1436
1437     self->icon_title = data;
1438 }
1439
1440 void client_update_class(ObClient *self)
1441 {
1442     char **data;
1443     char *s;
1444
1445     if (self->name) g_free(self->name);
1446     if (self->class) g_free(self->class);
1447     if (self->role) g_free(self->role);
1448
1449     self->name = self->class = self->role = NULL;
1450
1451     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1452         if (data[0]) {
1453             self->name = g_strdup(data[0]);
1454             if (data[1])
1455                 self->class = g_strdup(data[1]);
1456         }
1457         g_strfreev(data);     
1458     }
1459
1460     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1461         self->role = s;
1462
1463     if (self->name == NULL) self->name = g_strdup("");
1464     if (self->class == NULL) self->class = g_strdup("");
1465     if (self->role == NULL) self->role = g_strdup("");
1466 }
1467
1468 void client_update_strut(ObClient *self)
1469 {
1470     guint num;
1471     guint32 *data;
1472     gboolean got = FALSE;
1473     StrutPartial strut;
1474
1475     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1476                     &data, &num)) {
1477         if (num == 12) {
1478             got = TRUE;
1479             STRUT_PARTIAL_SET(strut,
1480                               data[0], data[2], data[1], data[3],
1481                               data[4], data[5], data[8], data[9],
1482                               data[6], data[7], data[10], data[11]);
1483         }
1484         g_free(data);
1485     }
1486
1487     if (!got &&
1488         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1489         if (num == 4) {
1490             got = TRUE;
1491             STRUT_PARTIAL_SET(strut,
1492                               data[0], data[2], data[1], data[3],
1493                               0, 0, 0, 0, 0, 0, 0, 0);
1494         }
1495         g_free(data);
1496     }
1497
1498     if (!got)
1499         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1500                           0, 0, 0, 0, 0, 0, 0, 0);
1501
1502     if (!STRUT_EQUAL(strut, self->strut)) {
1503         self->strut = strut;
1504
1505         /* updating here is pointless while we're being mapped cuz we're not in
1506            the client list yet */
1507         if (self->frame)
1508             screen_update_areas();
1509     }
1510 }
1511
1512 void client_update_icons(ObClient *self)
1513 {
1514     guint num;
1515     guint32 *data;
1516     guint w, h, i, j;
1517
1518     for (i = 0; i < self->nicons; ++i)
1519         g_free(self->icons[i].data);
1520     if (self->nicons > 0)
1521         g_free(self->icons);
1522     self->nicons = 0;
1523
1524     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1525         /* figure out how many valid icons are in here */
1526         i = 0;
1527         while (num - i > 2) {
1528             w = data[i++];
1529             h = data[i++];
1530             i += w * h;
1531             if (i > num || w*h == 0) break;
1532             ++self->nicons;
1533         }
1534
1535         self->icons = g_new(ObClientIcon, self->nicons);
1536     
1537         /* store the icons */
1538         i = 0;
1539         for (j = 0; j < self->nicons; ++j) {
1540             guint x, y, t;
1541
1542             w = self->icons[j].width = data[i++];
1543             h = self->icons[j].height = data[i++];
1544
1545             if (w*h == 0) continue;
1546
1547             self->icons[j].data = g_new(RrPixel32, w * h);
1548             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1549                 if (x >= w) {
1550                     x = 0;
1551                     ++y;
1552                 }
1553                 self->icons[j].data[t] =
1554                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1555                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1556                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1557                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1558             }
1559             g_assert(i <= num);
1560         }
1561
1562         g_free(data);
1563     } else if (PROP_GETA32(self->window, kwm_win_icon,
1564                            kwm_win_icon, &data, &num)) {
1565         if (num == 2) {
1566             self->nicons++;
1567             self->icons = g_new(ObClientIcon, self->nicons);
1568             xerror_set_ignore(TRUE);
1569             if (!RrPixmapToRGBA(ob_rr_inst,
1570                                 data[0], data[1],
1571                                 &self->icons[self->nicons-1].width,
1572                                 &self->icons[self->nicons-1].height,
1573                                 &self->icons[self->nicons-1].data)) {
1574                 g_free(&self->icons[self->nicons-1]);
1575                 self->nicons--;
1576             }
1577             xerror_set_ignore(FALSE);
1578         }
1579         g_free(data);
1580     } else {
1581         XWMHints *hints;
1582
1583         if ((hints = XGetWMHints(ob_display, self->window))) {
1584             if (hints->flags & IconPixmapHint) {
1585                 self->nicons++;
1586                 self->icons = g_new(ObClientIcon, self->nicons);
1587                 xerror_set_ignore(TRUE);
1588                 if (!RrPixmapToRGBA(ob_rr_inst,
1589                                     hints->icon_pixmap,
1590                                     (hints->flags & IconMaskHint ?
1591                                      hints->icon_mask : None),
1592                                     &self->icons[self->nicons-1].width,
1593                                     &self->icons[self->nicons-1].height,
1594                                     &self->icons[self->nicons-1].data)){
1595                     g_free(&self->icons[self->nicons-1]);
1596                     self->nicons--;
1597                 }
1598                 xerror_set_ignore(FALSE);
1599             }
1600             XFree(hints);
1601         }
1602     }
1603
1604     if (self->frame)
1605         frame_adjust_icon(self->frame);
1606 }
1607
1608 static void client_change_state(ObClient *self)
1609 {
1610     guint32 state[2];
1611     guint32 netstate[11];
1612     guint num;
1613
1614     state[0] = self->wmstate;
1615     state[1] = None;
1616     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1617
1618     num = 0;
1619     if (self->modal)
1620         netstate[num++] = prop_atoms.net_wm_state_modal;
1621     if (self->shaded)
1622         netstate[num++] = prop_atoms.net_wm_state_shaded;
1623     if (self->iconic)
1624         netstate[num++] = prop_atoms.net_wm_state_hidden;
1625     if (self->skip_taskbar)
1626         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1627     if (self->skip_pager)
1628         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1629     if (self->fullscreen)
1630         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1631     if (self->max_vert)
1632         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1633     if (self->max_horz)
1634         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1635     if (self->above)
1636         netstate[num++] = prop_atoms.net_wm_state_above;
1637     if (self->below)
1638         netstate[num++] = prop_atoms.net_wm_state_below;
1639     if (self->undecorated)
1640         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1641     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1642
1643     client_calc_layer(self);
1644
1645     if (self->frame)
1646         frame_adjust_state(self->frame);
1647 }
1648
1649 ObClient *client_search_focus_tree(ObClient *self)
1650 {
1651     GSList *it;
1652     ObClient *ret;
1653
1654     for (it = self->transients; it != NULL; it = it->next) {
1655         if (client_focused(it->data)) return it->data;
1656         if ((ret = client_search_focus_tree(it->data))) return ret;
1657     }
1658     return NULL;
1659 }
1660
1661 ObClient *client_search_focus_tree_full(ObClient *self)
1662 {
1663     if (self->transient_for) {
1664         if (self->transient_for != OB_TRAN_GROUP) {
1665             return client_search_focus_tree_full(self->transient_for);
1666         } else {
1667             GSList *it;
1668             gboolean recursed = FALSE;
1669         
1670             for (it = self->group->members; it; it = it->next)
1671                 if (!((ObClient*)it->data)->transient_for) {
1672                     ObClient *c;
1673                     if ((c = client_search_focus_tree_full(it->data)))
1674                         return c;
1675                     recursed = TRUE;
1676                 }
1677             if (recursed)
1678               return NULL;
1679         }
1680     }
1681
1682     /* this function checks the whole tree, the client_search_focus_tree~
1683        does not, so we need to check this window */
1684     if (client_focused(self))
1685       return self;
1686     return client_search_focus_tree(self);
1687 }
1688
1689 static ObStackingLayer calc_layer(ObClient *self)
1690 {
1691     ObStackingLayer l;
1692
1693     if (self->fullscreen &&
1694         (client_focused(self) || client_search_focus_tree(self)))
1695         l = OB_STACKING_LAYER_FULLSCREEN;
1696     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1697         l = OB_STACKING_LAYER_DESKTOP;
1698     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1699         if (!self->below) l = OB_STACKING_LAYER_TOP;
1700         else l = OB_STACKING_LAYER_NORMAL;
1701     }
1702     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1703     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1704     else l = OB_STACKING_LAYER_NORMAL;
1705
1706     return l;
1707 }
1708
1709 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1710                                         ObStackingLayer l, gboolean raised)
1711 {
1712     ObStackingLayer old, own;
1713     GSList *it;
1714
1715     old = self->layer;
1716     own = calc_layer(self);
1717     self->layer = l > own ? l : own;
1718
1719     for (it = self->transients; it; it = it->next)
1720         client_calc_layer_recursive(it->data, orig,
1721                                     l, raised ? raised : l != old);
1722
1723     if (!raised && l != old)
1724         if (orig->frame) { /* only restack if the original window is managed */
1725             /* XXX add_non_intrusive ever? */
1726             stacking_remove(CLIENT_AS_WINDOW(self));
1727             stacking_add(CLIENT_AS_WINDOW(self));
1728         }
1729 }
1730
1731 void client_calc_layer(ObClient *self)
1732 {
1733     ObStackingLayer l;
1734     ObClient *orig;
1735
1736     orig = self;
1737
1738     /* transients take on the layer of their parents */
1739     self = client_search_top_transient(self);
1740
1741     l = calc_layer(self);
1742
1743     client_calc_layer_recursive(self, orig, l, FALSE);
1744 }
1745
1746 gboolean client_should_show(ObClient *self)
1747 {
1748     if (self->iconic) return FALSE;
1749     else if (!(self->desktop == screen_desktop ||
1750                self->desktop == DESKTOP_ALL)) return FALSE;
1751     else if (client_normal(self) && screen_showing_desktop) return FALSE;
1752     
1753     return TRUE;
1754 }
1755
1756 static void client_showhide(ObClient *self)
1757 {
1758
1759     if (client_should_show(self))
1760         frame_show(self->frame);
1761     else
1762         frame_hide(self->frame);
1763 }
1764
1765 gboolean client_normal(ObClient *self) {
1766     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1767               self->type == OB_CLIENT_TYPE_DOCK ||
1768               self->type == OB_CLIENT_TYPE_SPLASH);
1769 }
1770
1771 static void client_apply_startup_state(ObClient *self)
1772 {
1773     /* these are in a carefully crafted order.. */
1774
1775     if (self->iconic) {
1776         self->iconic = FALSE;
1777         client_iconify(self, TRUE, FALSE);
1778     }
1779     if (self->fullscreen) {
1780         self->fullscreen = FALSE;
1781         client_fullscreen(self, TRUE, FALSE);
1782     }
1783     if (self->undecorated) {
1784         self->undecorated = FALSE;
1785         client_set_undecorated(self, TRUE);
1786     }
1787     if (self->shaded) {
1788         self->shaded = FALSE;
1789         client_shade(self, TRUE);
1790     }
1791     if (self->urgent)
1792         client_urgent_notify(self);
1793   
1794     if (self->max_vert && self->max_horz) {
1795         self->max_vert = self->max_horz = FALSE;
1796         client_maximize(self, TRUE, 0, FALSE);
1797     } else if (self->max_vert) {
1798         self->max_vert = FALSE;
1799         client_maximize(self, TRUE, 2, FALSE);
1800     } else if (self->max_horz) {
1801         self->max_horz = FALSE;
1802         client_maximize(self, TRUE, 1, FALSE);
1803     }
1804
1805     /* nothing to do for the other states:
1806        skip_taskbar
1807        skip_pager
1808        modal
1809        above
1810        below
1811     */
1812 }
1813
1814 void client_configure_full(ObClient *self, ObCorner anchor,
1815                            int x, int y, int w, int h,
1816                            gboolean user, gboolean final,
1817                            gboolean force_reply)
1818 {
1819     gint oldw, oldh;
1820     gboolean send_resize_client;
1821     gboolean moved = FALSE, resized = FALSE;
1822     guint fdecor = self->frame->decorations;
1823     gboolean fhorz = self->frame->max_horz;
1824
1825     /* make the frame recalculate its dimentions n shit without changing
1826        anything visible for real, this way the constraints below can work with
1827        the updated frame dimensions. */
1828     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1829
1830     /* gets the frame's position */
1831     frame_client_gravity(self->frame, &x, &y);
1832
1833     /* these positions are frame positions, not client positions */
1834
1835     /* set the size and position if fullscreen */
1836     if (self->fullscreen) {
1837 #ifdef VIDMODE
1838         int dot;
1839         XF86VidModeModeLine mode;
1840 #endif
1841         Rect *a;
1842         guint i;
1843
1844         i = client_monitor(self);
1845         a = screen_physical_area_monitor(i);
1846
1847 #ifdef VIDMODE
1848         if (i == 0 && /* primary head */
1849             extensions_vidmode &&
1850             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1851             /* get the mode last so the mode.privsize isnt freed incorrectly */
1852             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1853             x += a->x;
1854             y += a->y;
1855             w = mode.hdisplay;
1856             h = mode.vdisplay;
1857             if (mode.privsize) XFree(mode.private);
1858         } else
1859 #endif
1860         {
1861             x = a->x;
1862             y = a->y;
1863             w = a->width;
1864             h = a->height;
1865         }
1866
1867         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1868     } else {
1869         Rect *a;
1870
1871         a = screen_area_monitor(self->desktop, client_monitor(self));
1872
1873         /* set the size and position if maximized */
1874         if (self->max_horz) {
1875             x = a->x;
1876             w = a->width - self->frame->size.left - self->frame->size.right;
1877         }
1878         if (self->max_vert) {
1879             y = a->y;
1880             h = a->height - self->frame->size.top - self->frame->size.bottom;
1881         }
1882     }
1883
1884     /* gets the client's position */
1885     frame_frame_gravity(self->frame, &x, &y);
1886
1887     /* these override the above states! if you cant move you can't move! */
1888     if (user) {
1889         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
1890             x = self->area.x;
1891             y = self->area.y;
1892         }
1893         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
1894             w = self->area.width;
1895             h = self->area.height;
1896         }
1897     }
1898
1899     if (!(w == self->area.width && h == self->area.height)) {
1900         int basew, baseh, minw, minh;
1901
1902         /* base size is substituted with min size if not specified */
1903         if (self->base_size.width || self->base_size.height) {
1904             basew = self->base_size.width;
1905             baseh = self->base_size.height;
1906         } else {
1907             basew = self->min_size.width;
1908             baseh = self->min_size.height;
1909         }
1910         /* min size is substituted with base size if not specified */
1911         if (self->min_size.width || self->min_size.height) {
1912             minw = self->min_size.width;
1913             minh = self->min_size.height;
1914         } else {
1915             minw = self->base_size.width;
1916             minh = self->base_size.height;
1917         }
1918
1919         /* if this is a user-requested resize, then check against min/max
1920            sizes */
1921
1922         /* smaller than min size or bigger than max size? */
1923         if (w > self->max_size.width) w = self->max_size.width;
1924         if (w < minw) w = minw;
1925         if (h > self->max_size.height) h = self->max_size.height;
1926         if (h < minh) h = minh;
1927
1928         w -= basew;
1929         h -= baseh;
1930
1931         /* keep to the increments */
1932         w /= self->size_inc.width;
1933         h /= self->size_inc.height;
1934
1935         /* you cannot resize to nothing */
1936         if (basew + w < 1) w = 1 - basew;
1937         if (baseh + h < 1) h = 1 - baseh;
1938   
1939         /* store the logical size */
1940         SIZE_SET(self->logical_size,
1941                  self->size_inc.width > 1 ? w : w + basew,
1942                  self->size_inc.height > 1 ? h : h + baseh);
1943
1944         w *= self->size_inc.width;
1945         h *= self->size_inc.height;
1946
1947         w += basew;
1948         h += baseh;
1949
1950         /* adjust the height to match the width for the aspect ratios.
1951            for this, min size is not substituted for base size ever. */
1952         w -= self->base_size.width;
1953         h -= self->base_size.height;
1954
1955         if (self->min_ratio)
1956             if (h * self->min_ratio > w) h = (int)(w / self->min_ratio);
1957         if (self->max_ratio)
1958             if (h * self->max_ratio < w) h = (int)(w / self->max_ratio);
1959
1960         w += self->base_size.width;
1961         h += self->base_size.height;
1962     }
1963
1964     switch (anchor) {
1965     case OB_CORNER_TOPLEFT:
1966         break;
1967     case OB_CORNER_TOPRIGHT:
1968         x -= w - self->area.width;
1969         break;
1970     case OB_CORNER_BOTTOMLEFT:
1971         y -= h - self->area.height;
1972         break;
1973     case OB_CORNER_BOTTOMRIGHT:
1974         x -= w - self->area.width;
1975         y -= h - self->area.height;
1976         break;
1977     }
1978
1979     moved = x != self->area.x || y != self->area.y;
1980     resized = w != self->area.width || h != self->area.height;
1981
1982     oldw = self->area.width;
1983     oldh = self->area.height;
1984     RECT_SET(self->area, x, y, w, h);
1985
1986     /* for app-requested resizes, always resize if 'resized' is true.
1987        for user-requested ones, only resize if final is true, or when
1988        resizing in redraw mode */
1989     send_resize_client = ((!user && resized) ||
1990                           (user && (final ||
1991                                     (resized && config_redraw_resize))));
1992
1993     /* if the client is enlarging, the resize the client before the frame */
1994     if (send_resize_client && user && (w > oldw || h > oldh))
1995         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
1996
1997     /* move/resize the frame to match the request */
1998     if (self->frame) {
1999         if (self->decorations != fdecor || self->max_horz != fhorz)
2000             moved = resized = TRUE;
2001
2002         if (moved || resized)
2003             frame_adjust_area(self->frame, moved, resized, FALSE);
2004
2005         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2006         {
2007             XEvent event;
2008             event.type = ConfigureNotify;
2009             event.xconfigure.display = ob_display;
2010             event.xconfigure.event = self->window;
2011             event.xconfigure.window = self->window;
2012
2013             /* root window real coords */
2014             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2015                 self->border_width;
2016             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2017                 self->border_width;
2018             event.xconfigure.width = w;
2019             event.xconfigure.height = h;
2020             event.xconfigure.border_width = 0;
2021             event.xconfigure.above = self->frame->plate;
2022             event.xconfigure.override_redirect = FALSE;
2023             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2024                        FALSE, StructureNotifyMask, &event);
2025         }
2026     }
2027
2028     /* if the client is shrinking, then resize the frame before the client */
2029     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2030         XResizeWindow(ob_display, self->window, w, h);
2031
2032     XFlush(ob_display);
2033 }
2034
2035 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2036 {
2037     int x, y, w, h;
2038
2039     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2040         self->fullscreen == fs) return;                   /* already done */
2041
2042     self->fullscreen = fs;
2043     client_change_state(self); /* change the state hints on the client,
2044                                   and adjust out layer/stacking */
2045
2046     if (fs) {
2047         if (savearea)
2048             self->pre_fullscreen_area = self->area;
2049
2050         /* these are not actually used cuz client_configure will set them
2051            as appropriate when the window is fullscreened */
2052         x = y = w = h = 0;
2053     } else {
2054         Rect *a;
2055
2056         if (self->pre_fullscreen_area.width > 0 &&
2057             self->pre_fullscreen_area.height > 0)
2058         {
2059             x = self->pre_fullscreen_area.x;
2060             y = self->pre_fullscreen_area.y;
2061             w = self->pre_fullscreen_area.width;
2062             h = self->pre_fullscreen_area.height;
2063             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2064         } else {
2065             /* pick some fallbacks... */
2066             a = screen_area_monitor(self->desktop, 0);
2067             x = a->x + a->width / 4;
2068             y = a->y + a->height / 4;
2069             w = a->width / 2;
2070             h = a->height / 2;
2071         }
2072     }
2073
2074     client_setup_decor_and_functions(self);
2075
2076     client_move_resize(self, x, y, w, h);
2077
2078     /* try focus us when we go into fullscreen mode */
2079     client_focus(self);
2080 }
2081
2082 static void client_iconify_recursive(ObClient *self,
2083                                      gboolean iconic, gboolean curdesk)
2084 {
2085     GSList *it;
2086     gboolean changed = FALSE;
2087
2088
2089     if (self->iconic != iconic) {
2090         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2091                  self->window);
2092
2093         self->iconic = iconic;
2094
2095         if (iconic) {
2096             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2097                 long old;
2098
2099                 old = self->wmstate;
2100                 self->wmstate = IconicState;
2101                 if (old != self->wmstate)
2102                     PROP_MSG(self->window, kde_wm_change_state,
2103                              self->wmstate, 1, 0, 0);
2104
2105                 self->ignore_unmaps++;
2106                 /* we unmap the client itself so that we can get MapRequest
2107                    events, and because the ICCCM tells us to! */
2108                 XUnmapWindow(ob_display, self->window);
2109
2110                 /* update the focus lists.. iconic windows go to the bottom of
2111                    the list, put the new iconic window at the 'top of the
2112                    bottom'. */
2113                 focus_order_to_top(self);
2114
2115                 changed = TRUE;
2116             }
2117         } else {
2118             long old;
2119
2120             if (curdesk)
2121                 client_set_desktop(self, screen_desktop, FALSE);
2122
2123             old = self->wmstate;
2124             self->wmstate = self->shaded ? IconicState : NormalState;
2125             if (old != self->wmstate)
2126                 PROP_MSG(self->window, kde_wm_change_state,
2127                          self->wmstate, 1, 0, 0);
2128
2129             XMapWindow(ob_display, self->window);
2130
2131             /* this puts it after the current focused window */
2132             focus_order_remove(self);
2133             focus_order_add_new(self);
2134
2135             /* this is here cuz with the VIDMODE extension, the viewport can
2136                change while a fullscreen window is iconic, and when it
2137                uniconifies, it would be nice if it did so to the new position
2138                of the viewport */
2139             client_reconfigure(self);
2140
2141             changed = TRUE;
2142         }
2143     }
2144
2145     if (changed) {
2146         client_change_state(self);
2147         client_showhide(self);
2148         screen_update_areas();
2149     }
2150
2151     /* iconify all transients */
2152     for (it = self->transients; it != NULL; it = it->next)
2153         if (it->data != self) client_iconify_recursive(it->data,
2154                                                        iconic, curdesk);
2155 }
2156
2157 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2158 {
2159     /* move up the transient chain as far as possible first */
2160     self = client_search_top_transient(self);
2161
2162     client_iconify_recursive(client_search_top_transient(self),
2163                              iconic, curdesk);
2164 }
2165
2166 void client_maximize(ObClient *self, gboolean max, int dir, gboolean savearea)
2167 {
2168     int x, y, w, h;
2169      
2170     g_assert(dir == 0 || dir == 1 || dir == 2);
2171     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2172
2173     /* check if already done */
2174     if (max) {
2175         if (dir == 0 && self->max_horz && self->max_vert) return;
2176         if (dir == 1 && self->max_horz) return;
2177         if (dir == 2 && self->max_vert) return;
2178     } else {
2179         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2180         if (dir == 1 && !self->max_horz) return;
2181         if (dir == 2 && !self->max_vert) return;
2182     }
2183
2184     /* we just tell it to configure in the same place and client_configure
2185        worries about filling the screen with the window */
2186     x = self->area.x;
2187     y = self->area.y;
2188     w = self->area.width;
2189     h = self->area.height;
2190
2191     if (max) {
2192         if (savearea)
2193             self->pre_max_area = self->area;
2194     } else {
2195         Rect *a;
2196
2197         a = screen_area_monitor(self->desktop, 0);
2198         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2199             if (self->pre_max_area.width > 0) {
2200                 x = self->pre_max_area.x;
2201                 w = self->pre_max_area.width;
2202
2203                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2204                          0, self->pre_max_area.height);
2205             } else {
2206                 /* pick some fallbacks... */
2207                 x = a->x + a->width / 4;
2208                 w = a->width / 2;
2209             }
2210         }
2211         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2212             if (self->pre_max_area.height > 0) {
2213                 y = self->pre_max_area.y;
2214                 h = self->pre_max_area.height;
2215
2216                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2217                          self->pre_max_area.width, 0);
2218             } else {
2219                 /* pick some fallbacks... */
2220                 y = a->y + a->height / 4;
2221                 h = a->height / 2;
2222             }
2223         }
2224     }
2225
2226     if (dir == 0 || dir == 1) /* horz */
2227         self->max_horz = max;
2228     if (dir == 0 || dir == 2) /* vert */
2229         self->max_vert = max;
2230
2231     client_change_state(self); /* change the state hints on the client */
2232
2233     client_setup_decor_and_functions(self);
2234
2235     client_move_resize(self, x, y, w, h);
2236 }
2237
2238 void client_shade(ObClient *self, gboolean shade)
2239 {
2240     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2241          shade) ||                         /* can't shade */
2242         self->shaded == shade) return;     /* already done */
2243
2244     /* when we're iconic, don't change the wmstate */
2245     if (!self->iconic) {
2246         long old;
2247
2248         old = self->wmstate;
2249         self->wmstate = shade ? IconicState : NormalState;
2250         if (old != self->wmstate)
2251             PROP_MSG(self->window, kde_wm_change_state,
2252                      self->wmstate, 1, 0, 0);
2253     }
2254
2255     self->shaded = shade;
2256     client_change_state(self);
2257     /* resize the frame to just the titlebar */
2258     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2259 }
2260
2261 void client_close(ObClient *self)
2262 {
2263     XEvent ce;
2264
2265     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2266
2267     /*
2268       XXX: itd be cool to do timeouts and shit here for killing the client's
2269       process off
2270       like... if the window is around after 5 seconds, then the close button
2271       turns a nice red, and if this function is called again, the client is
2272       explicitly killed.
2273     */
2274
2275     ce.xclient.type = ClientMessage;
2276     ce.xclient.message_type =  prop_atoms.wm_protocols;
2277     ce.xclient.display = ob_display;
2278     ce.xclient.window = self->window;
2279     ce.xclient.format = 32;
2280     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2281     ce.xclient.data.l[1] = event_lasttime;
2282     ce.xclient.data.l[2] = 0l;
2283     ce.xclient.data.l[3] = 0l;
2284     ce.xclient.data.l[4] = 0l;
2285     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2286 }
2287
2288 void client_kill(ObClient *self)
2289 {
2290     XKillClient(ob_display, self->window);
2291 }
2292
2293 void client_set_desktop_recursive(ObClient *self,
2294                                   guint target, gboolean donthide)
2295 {
2296     guint old;
2297     GSList *it;
2298
2299     if (target != self->desktop) {
2300
2301         ob_debug("Setting desktop %u\n", target+1);
2302
2303         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2304
2305         /* remove from the old desktop(s) */
2306         focus_order_remove(self);
2307
2308         old = self->desktop;
2309         self->desktop = target;
2310         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2311         /* the frame can display the current desktop state */
2312         frame_adjust_state(self->frame);
2313         /* 'move' the window to the new desktop */
2314         if (!donthide)
2315             client_showhide(self);
2316         /* raise if it was not already on the desktop */
2317         if (old != DESKTOP_ALL)
2318             client_raise(self);
2319         screen_update_areas();
2320
2321         /* add to the new desktop(s) */
2322         if (config_focus_new)
2323             focus_order_to_top(self);
2324         else
2325             focus_order_to_bottom(self);
2326     }
2327
2328     /* move all transients */
2329     for (it = self->transients; it != NULL; it = it->next)
2330         if (it->data != self) client_set_desktop_recursive(it->data,
2331                                                            target, donthide);
2332 }
2333
2334 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2335 {
2336     client_set_desktop_recursive(client_search_top_transient(self),
2337                                  target, donthide);
2338 }
2339
2340 ObClient *client_search_modal_child(ObClient *self)
2341 {
2342     GSList *it;
2343     ObClient *ret;
2344   
2345     for (it = self->transients; it != NULL; it = it->next) {
2346         ObClient *c = it->data;
2347         if ((ret = client_search_modal_child(c))) return ret;
2348         if (c->modal) return c;
2349     }
2350     return NULL;
2351 }
2352
2353 gboolean client_validate(ObClient *self)
2354 {
2355     XEvent e; 
2356
2357     XSync(ob_display, FALSE); /* get all events on the server */
2358
2359     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2360         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2361         XPutBackEvent(ob_display, &e);
2362         return FALSE;
2363     }
2364
2365     return TRUE;
2366 }
2367
2368 void client_set_wm_state(ObClient *self, long state)
2369 {
2370     if (state == self->wmstate) return; /* no change */
2371   
2372     switch (state) {
2373     case IconicState:
2374         client_iconify(self, TRUE, TRUE);
2375         break;
2376     case NormalState:
2377         client_iconify(self, FALSE, TRUE);
2378         break;
2379     }
2380 }
2381
2382 void client_set_state(ObClient *self, Atom action, long data1, long data2)
2383 {
2384     gboolean shaded = self->shaded;
2385     gboolean fullscreen = self->fullscreen;
2386     gboolean undecorated = self->undecorated;
2387     gboolean max_horz = self->max_horz;
2388     gboolean max_vert = self->max_vert;
2389     int i;
2390
2391     if (!(action == prop_atoms.net_wm_state_add ||
2392           action == prop_atoms.net_wm_state_remove ||
2393           action == prop_atoms.net_wm_state_toggle))
2394         /* an invalid action was passed to the client message, ignore it */
2395         return; 
2396
2397     for (i = 0; i < 2; ++i) {
2398         Atom state = i == 0 ? data1 : data2;
2399     
2400         if (!state) continue;
2401
2402         /* if toggling, then pick whether we're adding or removing */
2403         if (action == prop_atoms.net_wm_state_toggle) {
2404             if (state == prop_atoms.net_wm_state_modal)
2405                 action = self->modal ? prop_atoms.net_wm_state_remove :
2406                     prop_atoms.net_wm_state_add;
2407             else if (state == prop_atoms.net_wm_state_maximized_vert)
2408                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2409                     prop_atoms.net_wm_state_add;
2410             else if (state == prop_atoms.net_wm_state_maximized_horz)
2411                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2412                     prop_atoms.net_wm_state_add;
2413             else if (state == prop_atoms.net_wm_state_shaded)
2414                 action = shaded ? prop_atoms.net_wm_state_remove :
2415                     prop_atoms.net_wm_state_add;
2416             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2417                 action = self->skip_taskbar ?
2418                     prop_atoms.net_wm_state_remove :
2419                     prop_atoms.net_wm_state_add;
2420             else if (state == prop_atoms.net_wm_state_skip_pager)
2421                 action = self->skip_pager ?
2422                     prop_atoms.net_wm_state_remove :
2423                     prop_atoms.net_wm_state_add;
2424             else if (state == prop_atoms.net_wm_state_fullscreen)
2425                 action = fullscreen ?
2426                     prop_atoms.net_wm_state_remove :
2427                     prop_atoms.net_wm_state_add;
2428             else if (state == prop_atoms.net_wm_state_above)
2429                 action = self->above ? prop_atoms.net_wm_state_remove :
2430                     prop_atoms.net_wm_state_add;
2431             else if (state == prop_atoms.net_wm_state_below)
2432                 action = self->below ? prop_atoms.net_wm_state_remove :
2433                     prop_atoms.net_wm_state_add;
2434             else if (state == prop_atoms.ob_wm_state_undecorated)
2435                 action = undecorated ? prop_atoms.net_wm_state_remove :
2436                     prop_atoms.net_wm_state_add;
2437         }
2438     
2439         if (action == prop_atoms.net_wm_state_add) {
2440             if (state == prop_atoms.net_wm_state_modal) {
2441                 /* XXX raise here or something? */
2442                 self->modal = TRUE;
2443             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2444                 max_vert = TRUE;
2445             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2446                 max_horz = TRUE;
2447             } else if (state == prop_atoms.net_wm_state_shaded) {
2448                 shaded = TRUE;
2449             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2450                 self->skip_taskbar = TRUE;
2451             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2452                 self->skip_pager = TRUE;
2453             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2454                 fullscreen = TRUE;
2455             } else if (state == prop_atoms.net_wm_state_above) {
2456                 self->above = TRUE;
2457             } else if (state == prop_atoms.net_wm_state_below) {
2458                 self->below = TRUE;
2459             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2460                 undecorated = TRUE;
2461             }
2462
2463         } else { /* action == prop_atoms.net_wm_state_remove */
2464             if (state == prop_atoms.net_wm_state_modal) {
2465                 self->modal = FALSE;
2466             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2467                 max_vert = FALSE;
2468             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2469                 max_horz = FALSE;
2470             } else if (state == prop_atoms.net_wm_state_shaded) {
2471                 shaded = FALSE;
2472             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2473                 self->skip_taskbar = FALSE;
2474             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2475                 self->skip_pager = FALSE;
2476             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2477                 fullscreen = FALSE;
2478             } else if (state == prop_atoms.net_wm_state_above) {
2479                 self->above = FALSE;
2480             } else if (state == prop_atoms.net_wm_state_below) {
2481                 self->below = FALSE;
2482             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2483                 undecorated = FALSE;
2484             }
2485         }
2486     }
2487     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2488         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2489             /* toggling both */
2490             if (max_horz == max_vert) { /* both going the same way */
2491                 client_maximize(self, max_horz, 0, TRUE);
2492             } else {
2493                 client_maximize(self, max_horz, 1, TRUE);
2494                 client_maximize(self, max_vert, 2, TRUE);
2495             }
2496         } else {
2497             /* toggling one */
2498             if (max_horz != self->max_horz)
2499                 client_maximize(self, max_horz, 1, TRUE);
2500             else
2501                 client_maximize(self, max_vert, 2, TRUE);
2502         }
2503     }
2504     /* change fullscreen state before shading, as it will affect if the window
2505        can shade or not */
2506     if (fullscreen != self->fullscreen)
2507         client_fullscreen(self, fullscreen, TRUE);
2508     if (shaded != self->shaded)
2509         client_shade(self, shaded);
2510     if (undecorated != self->undecorated)
2511         client_set_undecorated(self, undecorated);
2512     client_calc_layer(self);
2513     client_change_state(self); /* change the hint to reflect these changes */
2514 }
2515
2516 ObClient *client_focus_target(ObClient *self)
2517 {
2518     ObClient *child;
2519      
2520     /* if we have a modal child, then focus it, not us */
2521     child = client_search_modal_child(client_search_top_transient(self));
2522     if (child) return child;
2523     return self;
2524 }
2525
2526 gboolean client_can_focus(ObClient *self)
2527 {
2528     XEvent ev;
2529
2530     /* choose the correct target */
2531     self = client_focus_target(self);
2532
2533     if (!self->frame->visible)
2534         return FALSE;
2535
2536     if (!((self->can_focus || self->focus_notify) &&
2537           (self->desktop == screen_desktop ||
2538            self->desktop == DESKTOP_ALL) &&
2539           !self->iconic))
2540         return FALSE;
2541
2542     /* do a check to see if the window has already been unmapped or destroyed
2543        do this intelligently while watching out for unmaps we've generated
2544        (ignore_unmaps > 0) */
2545     if (XCheckTypedWindowEvent(ob_display, self->window,
2546                                DestroyNotify, &ev)) {
2547         XPutBackEvent(ob_display, &ev);
2548         return FALSE;
2549     }
2550     while (XCheckTypedWindowEvent(ob_display, self->window,
2551                                   UnmapNotify, &ev)) {
2552         if (self->ignore_unmaps) {
2553             self->ignore_unmaps--;
2554         } else {
2555             XPutBackEvent(ob_display, &ev);
2556             return FALSE;
2557         }
2558     }
2559
2560     return TRUE;
2561 }
2562
2563 gboolean client_focus(ObClient *self)
2564 {
2565     /* choose the correct target */
2566     self = client_focus_target(self);
2567
2568     if (!client_can_focus(self)) {
2569         if (!self->frame->visible) {
2570             /* update the focus lists */
2571             focus_order_to_top(self);
2572         }
2573         return FALSE;
2574     }
2575
2576     if (self->can_focus) {
2577         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2578            I choose to use it always, hopefully to find errors quicker, if any
2579            are left. (I hate X. I hate focus events.)
2580            
2581            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2582            #799. So now it is RevertToNone again.
2583         */
2584         XSetInputFocus(ob_display, self->window, RevertToNone,
2585                        event_lasttime);
2586     }
2587
2588     if (self->focus_notify) {
2589         XEvent ce;
2590         ce.xclient.type = ClientMessage;
2591         ce.xclient.message_type = prop_atoms.wm_protocols;
2592         ce.xclient.display = ob_display;
2593         ce.xclient.window = self->window;
2594         ce.xclient.format = 32;
2595         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2596         ce.xclient.data.l[1] = event_lasttime;
2597         ce.xclient.data.l[2] = 0l;
2598         ce.xclient.data.l[3] = 0l;
2599         ce.xclient.data.l[4] = 0l;
2600         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2601     }
2602
2603 #ifdef DEBUG_FOCUS
2604     ob_debug("%sively focusing %lx at %d\n",
2605              (self->can_focus ? "act" : "pass"),
2606              self->window, (int) event_lasttime);
2607 #endif
2608
2609     /* Cause the FocusIn to come back to us. Important for desktop switches,
2610        since otherwise we'll have no FocusIn on the queue and send it off to
2611        the focus_backup. */
2612     XSync(ob_display, FALSE);
2613     return TRUE;
2614 }
2615
2616 void client_unfocus(ObClient *self)
2617 {
2618     if (focus_client == self) {
2619 #ifdef DEBUG_FOCUS
2620         ob_debug("client_unfocus for %lx\n", self->window);
2621 #endif
2622         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2623     }
2624 }
2625
2626 void client_activate(ObClient *self, gboolean here)
2627 {
2628     if (client_normal(self) && screen_showing_desktop)
2629         screen_show_desktop(FALSE);
2630     if (self->iconic)
2631         client_iconify(self, FALSE, here);
2632     if (self->desktop != DESKTOP_ALL &&
2633         self->desktop != screen_desktop) {
2634         if (here)
2635             client_set_desktop(self, screen_desktop, FALSE);
2636         else
2637             screen_set_desktop(self->desktop);
2638     } else if (!self->frame->visible)
2639         /* if its not visible for other reasons, then don't mess
2640            with it */
2641         return;
2642     if (self->shaded)
2643         client_shade(self, FALSE);
2644
2645     client_focus(self);
2646
2647     /* we do this an action here. this is rather important. this is because
2648        we want the results from the focus change to take place BEFORE we go
2649        about raising the window. when a fullscreen window loses focus, we need
2650        this or else the raise wont be able to raise above the to-lose-focus
2651        fullscreen window. */
2652     client_raise(self);
2653 }
2654
2655 void client_raise(ObClient *self)
2656 {
2657     action_run_string("Raise", self);
2658 }
2659
2660 void client_lower(ObClient *self)
2661 {
2662     action_run_string("Raise", self);
2663 }
2664
2665 gboolean client_focused(ObClient *self)
2666 {
2667     return self == focus_client;
2668 }
2669
2670 static ObClientIcon* client_icon_recursive(ObClient *self, int w, int h)
2671 {
2672     guint i;
2673     /* si is the smallest image >= req */
2674     /* li is the largest image < req */
2675     unsigned long size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2676
2677     if (!self->nicons) {
2678         ObClientIcon *parent = NULL;
2679
2680         if (self->transient_for) {
2681             if (self->transient_for != OB_TRAN_GROUP)
2682                 parent = client_icon_recursive(self->transient_for, w, h);
2683             else {
2684                 GSList *it;
2685                 for (it = self->group->members; it; it = g_slist_next(it)) {
2686                     ObClient *c = it->data;
2687                     if (c != self && !c->transient_for) {
2688                         if ((parent = client_icon_recursive(c, w, h)))
2689                             break;
2690                     }
2691                 }
2692             }
2693         }
2694         
2695         return parent;
2696     }
2697
2698     for (i = 0; i < self->nicons; ++i) {
2699         size = self->icons[i].width * self->icons[i].height;
2700         if (size < smallest && size >= (unsigned)(w * h)) {
2701             smallest = size;
2702             si = i;
2703         }
2704         if (size > largest && size <= (unsigned)(w * h)) {
2705             largest = size;
2706             li = i;
2707         }
2708     }
2709     if (largest == 0) /* didnt find one smaller than the requested size */
2710         return &self->icons[si];
2711     return &self->icons[li];
2712 }
2713
2714 const ObClientIcon* client_icon(ObClient *self, int w, int h)
2715 {
2716     ObClientIcon *ret;
2717     static ObClientIcon deficon;
2718
2719     if (!(ret = client_icon_recursive(self, w, h))) {
2720         deficon.width = deficon.height = 48;
2721         deficon.data = ob_rr_theme->def_win_icon;
2722         ret = &deficon;
2723     }
2724     return ret;
2725 }
2726
2727 /* this be mostly ripped from fvwm */
2728 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2729 {
2730     int my_cx, my_cy, his_cx, his_cy;
2731     int offset = 0;
2732     int distance = 0;
2733     int score, best_score;
2734     ObClient *best_client, *cur;
2735     GList *it;
2736
2737     if(!client_list)
2738         return NULL;
2739
2740     /* first, find the centre coords of the currently focused window */
2741     my_cx = c->frame->area.x + c->frame->area.width / 2;
2742     my_cy = c->frame->area.y + c->frame->area.height / 2;
2743
2744     best_score = -1;
2745     best_client = NULL;
2746
2747     for(it = g_list_first(client_list); it; it = it->next) {
2748         cur = it->data;
2749
2750         /* the currently selected window isn't interesting */
2751         if(cur == c)
2752             continue;
2753         if (!client_normal(cur))
2754             continue;
2755         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2756             continue;
2757         if(cur->iconic)
2758             continue;
2759         if(client_focus_target(cur) == cur &&
2760            !(cur->can_focus || cur->focus_notify))
2761             continue;
2762
2763         /* find the centre coords of this window, from the
2764          * currently focused window's point of view */
2765         his_cx = (cur->frame->area.x - my_cx)
2766             + cur->frame->area.width / 2;
2767         his_cy = (cur->frame->area.y - my_cy)
2768             + cur->frame->area.height / 2;
2769
2770         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2771            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2772             int tx;
2773             /* Rotate the diagonals 45 degrees counterclockwise.
2774              * To do this, multiply the matrix /+h +h\ with the
2775              * vector (x y).                   \-h +h/
2776              * h = sqrt(0.5). We can set h := 1 since absolute
2777              * distance doesn't matter here. */
2778             tx = his_cx + his_cy;
2779             his_cy = -his_cx + his_cy;
2780             his_cx = tx;
2781         }
2782
2783         switch(dir) {
2784         case OB_DIRECTION_NORTH:
2785         case OB_DIRECTION_SOUTH:
2786         case OB_DIRECTION_NORTHEAST:
2787         case OB_DIRECTION_SOUTHWEST:
2788             offset = (his_cx < 0) ? -his_cx : his_cx;
2789             distance = ((dir == OB_DIRECTION_NORTH ||
2790                         dir == OB_DIRECTION_NORTHEAST) ?
2791                         -his_cy : his_cy);
2792             break;
2793         case OB_DIRECTION_EAST:
2794         case OB_DIRECTION_WEST:
2795         case OB_DIRECTION_SOUTHEAST:
2796         case OB_DIRECTION_NORTHWEST:
2797             offset = (his_cy < 0) ? -his_cy : his_cy;
2798             distance = ((dir == OB_DIRECTION_WEST ||
2799                         dir == OB_DIRECTION_NORTHWEST) ?
2800                         -his_cx : his_cx);
2801             break;
2802         }
2803
2804         /* the target must be in the requested direction */
2805         if(distance <= 0)
2806             continue;
2807
2808         /* Calculate score for this window.  The smaller the better. */
2809         score = distance + offset;
2810
2811         /* windows more than 45 degrees off the direction are
2812          * heavily penalized and will only be chosen if nothing
2813          * else within a million pixels */
2814         if(offset > distance)
2815             score += 1000000;
2816
2817         if(best_score == -1 || score < best_score)
2818             best_client = cur,
2819                 best_score = score;
2820     }
2821
2822     return best_client;
2823 }
2824
2825 void client_set_layer(ObClient *self, int layer)
2826 {
2827     if (layer < 0) {
2828         self->below = TRUE;
2829         self->above = FALSE;
2830     } else if (layer == 0) {
2831         self->below = self->above = FALSE;
2832     } else {
2833         self->below = FALSE;
2834         self->above = TRUE;
2835     }
2836     client_calc_layer(self);
2837     client_change_state(self); /* reflect this in the state hints */
2838 }
2839
2840 void client_set_undecorated(ObClient *self, gboolean undecorated)
2841 {
2842     if (self->undecorated != undecorated) {
2843         self->undecorated = undecorated;
2844         client_setup_decor_and_functions(self);
2845         client_change_state(self); /* reflect this in the state hints */
2846     }
2847 }
2848
2849 guint client_monitor(ObClient *self)
2850 {
2851     guint i;
2852
2853     for (i = 0; i < screen_num_monitors; ++i) {
2854         Rect *area = screen_physical_area_monitor(i);
2855         if (RECT_INTERSECTS_RECT(*area, self->frame->area))
2856             break;
2857     }
2858     if (i == screen_num_monitors) i = 0;
2859     g_assert(i < screen_num_monitors);
2860     return i;
2861 }
2862
2863 ObClient *client_search_top_transient(ObClient *self)
2864 {
2865     /* move up the transient chain as far as possible */
2866     if (self->transient_for) {
2867         if (self->transient_for != OB_TRAN_GROUP) {
2868             return client_search_top_transient(self->transient_for);
2869         } else {
2870             GSList *it;
2871
2872             g_assert(self->group);
2873
2874             for (it = self->group->members; it; it = it->next) {
2875                 ObClient *c = it->data;
2876
2877                 /* checking transient_for prevents infinate loops! */
2878                 if (c != self && !c->transient_for)
2879                     break;
2880             }
2881             if (it)
2882                 return it->data;
2883         }
2884     }
2885
2886     return self;
2887 }
2888
2889 ObClient *client_search_focus_parent(ObClient *self)
2890 {
2891     if (self->transient_for) {
2892         if (self->transient_for != OB_TRAN_GROUP) {
2893             if (client_focused(self->transient_for))
2894                 return self->transient_for;
2895         } else {
2896             GSList *it;
2897
2898             for (it = self->group->members; it; it = it->next) {
2899                 ObClient *c = it->data;
2900
2901                 /* checking transient_for prevents infinate loops! */
2902                 if (c != self && !c->transient_for)
2903                     if (client_focused(c))
2904                         return c;
2905             }
2906         }
2907     }
2908
2909     return NULL;
2910 }
2911
2912 ObClient *client_search_parent(ObClient *self, ObClient *search)
2913 {
2914     if (self->transient_for) {
2915         if (self->transient_for != OB_TRAN_GROUP) {
2916             if (self->transient_for == search)
2917                 return search;
2918         } else {
2919             GSList *it;
2920
2921             for (it = self->group->members; it; it = it->next) {
2922                 ObClient *c = it->data;
2923
2924                 /* checking transient_for prevents infinate loops! */
2925                 if (c != self && !c->transient_for)
2926                     if (c == search)
2927                         return search;
2928             }
2929         }
2930     }
2931
2932     return NULL;
2933 }
2934
2935 ObClient *client_search_transient(ObClient *self, ObClient *search)
2936 {
2937     GSList *sit;
2938
2939     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
2940         if (sit->data == search)
2941             return search;
2942         if (client_search_transient(sit->data, search))
2943             return search;
2944     }
2945     return NULL;
2946 }
2947
2948 void client_update_sm_client_id(ObClient *self)
2949 {
2950     g_free(self->sm_client_id);
2951     self->sm_client_id = NULL;
2952
2953     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
2954         self->group)
2955         PROP_GETS(self->group->leader, sm_client_id, locale,
2956                   &self->sm_client_id);
2957 }
2958
2959 /* finds the nearest edge in the given direction from the current client
2960  * note to self: the edge is the -frame- edge (the actual one), not the
2961  * client edge.
2962  */
2963 int client_directional_edge_search(ObClient *c, ObDirection dir)
2964 {
2965     int dest;
2966     int my_edge_start, my_edge_end, my_offset;
2967     GList *it;
2968     Rect *a;
2969     
2970     if(!client_list)
2971         return -1;
2972
2973     a = screen_area(c->desktop);
2974
2975     switch(dir) {
2976     case OB_DIRECTION_NORTH:
2977         my_edge_start = c->frame->area.x;
2978         my_edge_end = c->frame->area.x + c->frame->area.width;
2979         my_offset = c->frame->area.y;
2980         
2981         dest = a->y; /* default: top of screen */
2982
2983         for(it = g_list_first(client_list); it; it = it->next) {
2984             int his_edge_start, his_edge_end, his_offset;
2985             ObClient *cur = it->data;
2986
2987             if(cur == c)
2988                 continue;
2989             if(!client_normal(cur))
2990                 continue;
2991             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2992                 continue;
2993             if(cur->iconic)
2994                 continue;
2995
2996             his_edge_start = cur->frame->area.x;
2997             his_edge_end = cur->frame->area.x + cur->frame->area.width;
2998             his_offset = cur->frame->area.y + cur->frame->area.height;
2999
3000             if(his_offset + 1 > my_offset)
3001                 continue;
3002
3003             if(his_offset < dest)
3004                 continue;
3005             
3006             if(his_edge_start >= my_edge_start &&
3007                his_edge_start <= my_edge_end)
3008                 dest = his_offset;
3009
3010             if(my_edge_start >= his_edge_start &&
3011                my_edge_start <= his_edge_end)
3012                 dest = his_offset;
3013
3014         }
3015         break;
3016     case OB_DIRECTION_SOUTH:
3017         my_edge_start = c->frame->area.x;
3018         my_edge_end = c->frame->area.x + c->frame->area.width;
3019         my_offset = c->frame->area.y + c->frame->area.height;
3020         
3021         dest = a->y + a->height; /* default: bottom of screen */
3022
3023         for(it = g_list_first(client_list); it; it = it->next) {
3024             int his_edge_start, his_edge_end, his_offset;
3025             ObClient *cur = it->data;
3026
3027             if(cur == c)
3028                 continue;
3029             if(!client_normal(cur))
3030                 continue;
3031             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3032                 continue;
3033             if(cur->iconic)
3034                 continue;
3035
3036             his_edge_start = cur->frame->area.x;
3037             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3038             his_offset = cur->frame->area.y;
3039
3040
3041             if(his_offset - 1 < my_offset)
3042                 continue;
3043             
3044             if(his_offset > dest)
3045                 continue;
3046             
3047             if(his_edge_start >= my_edge_start &&
3048                his_edge_start <= my_edge_end)
3049                 dest = his_offset;
3050
3051             if(my_edge_start >= his_edge_start &&
3052                my_edge_start <= his_edge_end)
3053                 dest = his_offset;
3054
3055         }
3056         break;
3057     case OB_DIRECTION_WEST:
3058         my_edge_start = c->frame->area.y;
3059         my_edge_end = c->frame->area.y + c->frame->area.height;
3060         my_offset = c->frame->area.x;
3061
3062         dest = a->x; /* default: leftmost egde of screen */
3063
3064         for(it = g_list_first(client_list); it; it = it->next) {
3065             int his_edge_start, his_edge_end, his_offset;
3066             ObClient *cur = it->data;
3067
3068             if(cur == c)
3069                 continue;
3070             if(!client_normal(cur))
3071                 continue;
3072             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3073                 continue;
3074             if(cur->iconic)
3075                 continue;
3076
3077             his_edge_start = cur->frame->area.y;
3078             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3079             his_offset = cur->frame->area.x + cur->frame->area.width;
3080
3081             if(his_offset + 1 > my_offset)
3082                 continue;
3083             
3084             if(his_offset < dest)
3085                 continue;
3086             
3087             if(his_edge_start >= my_edge_start &&
3088                his_edge_start <= my_edge_end)
3089                 dest = his_offset;
3090
3091             if(my_edge_start >= his_edge_start &&
3092                my_edge_start <= his_edge_end)
3093                 dest = his_offset;
3094                 
3095
3096         }
3097         break;
3098     case OB_DIRECTION_EAST:
3099         my_edge_start = c->frame->area.y;
3100         my_edge_end = c->frame->area.y + c->frame->area.height;
3101         my_offset = c->frame->area.x + c->frame->area.width;
3102         
3103         dest = a->x + a->width; /* default: rightmost edge of screen */
3104
3105         for(it = g_list_first(client_list); it; it = it->next) {
3106             int his_edge_start, his_edge_end, his_offset;
3107             ObClient *cur = it->data;
3108
3109             if(cur == c)
3110                 continue;
3111             if(!client_normal(cur))
3112                 continue;
3113             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3114                 continue;
3115             if(cur->iconic)
3116                 continue;
3117
3118             his_edge_start = cur->frame->area.y;
3119             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3120             his_offset = cur->frame->area.x;
3121
3122             if(his_offset - 1 < my_offset)
3123                 continue;
3124             
3125             if(his_offset > dest)
3126                 continue;
3127             
3128             if(his_edge_start >= my_edge_start &&
3129                his_edge_start <= my_edge_end)
3130                 dest = his_offset;
3131
3132             if(my_edge_start >= his_edge_start &&
3133                my_edge_start <= his_edge_end)
3134                 dest = his_offset;
3135
3136         }
3137         break;
3138     case OB_DIRECTION_NORTHEAST:
3139     case OB_DIRECTION_SOUTHEAST:
3140     case OB_DIRECTION_NORTHWEST:
3141     case OB_DIRECTION_SOUTHWEST:
3142         /* not implemented */
3143     default:
3144         g_assert_not_reached();
3145     }
3146     return dest;
3147 }
3148
3149 ObClient* client_under_pointer()
3150 {
3151     int x, y;
3152     GList *it;
3153     ObClient *ret = NULL;
3154
3155     if (screen_pointer_pos(&x, &y)) {
3156         for (it = stacking_list; it != NULL; it = it->next) {
3157             if (WINDOW_IS_CLIENT(it->data)) {
3158                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3159                 if (c->desktop == screen_desktop &&
3160                     RECT_CONTAINS(c->frame->area, x, y)) {
3161                     ret = c;
3162                     break;
3163                 }
3164             }
3165         }
3166     }
3167     return ret;
3168 }