Allow non-interactive focus cycling.
[dana/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "grab.h"
24 #include "debug.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "focus_cycle.h"
28 #include "focus_cycle_indicator.h"
29 #include "moveresize.h"
30 #include "screen.h"
31 #include "obrender/theme.h"
32 #include "obt/display.h"
33 #include "obt/xqueue.h"
34 #include "obt/prop.h"
35
36 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
37                          ButtonPressMask | ButtonReleaseMask | \
38                          SubstructureRedirectMask | FocusChangeMask)
39 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
40                            ButtonMotionMask | PointerMotionMask | \
41                            EnterWindowMask | LeaveWindowMask)
42
43 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
44 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (1000 / 60) /* 60 Hz */
45
46 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_b)
47
48 static void flash_done(gpointer data);
49 static gboolean flash_timeout(gpointer data);
50
51 static void layout_title(ObFrame *self);
52 static void set_theme_statics(ObFrame *self);
53 static void free_theme_statics(ObFrame *self);
54 static gboolean frame_animate_iconify(gpointer self);
55 static void frame_adjust_cursors(ObFrame *self);
56
57 static Window createWindow(Window parent, Visual *visual,
58                            gulong mask, XSetWindowAttributes *attrib)
59 {
60     return XCreateWindow(obt_display, parent, 0, 0, 1, 1, 0,
61                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
62                          (visual ? visual : RrVisual(ob_rr_inst)),
63                          mask, attrib);
64
65 }
66
67 static Visual *check_32bit_client(ObClient *c)
68 {
69     XWindowAttributes wattrib;
70     Status ret;
71
72     /* we're already running at 32 bit depth, yay. we don't need to use their
73        visual */
74     if (RrDepth(ob_rr_inst) == 32)
75         return NULL;
76
77     ret = XGetWindowAttributes(obt_display, c->window, &wattrib);
78     g_assert(ret != BadDrawable);
79     g_assert(ret != BadWindow);
80
81     if (wattrib.depth == 32)
82         return wattrib.visual;
83     return NULL;
84 }
85
86 ObFrame *frame_new(ObClient *client)
87 {
88     XSetWindowAttributes attrib;
89     gulong mask;
90     ObFrame *self;
91     Visual *visual;
92
93     self = g_slice_new0(ObFrame);
94     self->client = client;
95
96     visual = check_32bit_client(client);
97
98     /* create the non-visible decor windows */
99
100     mask = 0;
101     if (visual) {
102         /* client has a 32-bit visual */
103         mask = CWColormap | CWBackPixel | CWBorderPixel;
104         /* create a colormap with the visual */
105         self->colormap = attrib.colormap =
106             XCreateColormap(obt_display, obt_root(ob_screen),
107                             visual, AllocNone);
108         attrib.background_pixel = BlackPixel(obt_display, ob_screen);
109         attrib.border_pixel = BlackPixel(obt_display, ob_screen);
110     }
111     self->window = createWindow(obt_root(ob_screen), visual,
112                                 mask, &attrib);
113
114     /* create the visible decor windows */
115
116     mask = 0;
117     if (visual) {
118         /* client has a 32-bit visual */
119         mask = CWColormap | CWBackPixel | CWBorderPixel;
120         attrib.colormap = RrColormap(ob_rr_inst);
121     }
122
123     self->backback = createWindow(self->window, NULL, mask, &attrib);
124     self->backfront = createWindow(self->backback, NULL, mask, &attrib);
125
126     mask |= CWEventMask;
127     attrib.event_mask = ELEMENT_EVENTMASK;
128     self->innerleft = createWindow(self->window, NULL, mask, &attrib);
129     self->innertop = createWindow(self->window, NULL, mask, &attrib);
130     self->innerright = createWindow(self->window, NULL, mask, &attrib);
131     self->innerbottom = createWindow(self->window, NULL, mask, &attrib);
132
133     self->innerblb = createWindow(self->innerbottom, NULL, mask, &attrib);
134     self->innerbrb = createWindow(self->innerbottom, NULL, mask, &attrib);
135     self->innerbll = createWindow(self->innerleft, NULL, mask, &attrib);
136     self->innerbrr = createWindow(self->innerright, NULL, mask, &attrib);
137
138     self->title = createWindow(self->window, NULL, mask, &attrib);
139     self->titleleft = createWindow(self->window, NULL, mask, &attrib);
140     self->titletop = createWindow(self->window, NULL, mask, &attrib);
141     self->titletopleft = createWindow(self->window, NULL, mask, &attrib);
142     self->titletopright = createWindow(self->window, NULL, mask, &attrib);
143     self->titleright = createWindow(self->window, NULL, mask, &attrib);
144     self->titlebottom = createWindow(self->window, NULL, mask, &attrib);
145
146     self->topresize = createWindow(self->title, NULL, mask, &attrib);
147     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
148     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
149     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
150     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
151
152     self->left = createWindow(self->window, NULL, mask, &attrib);
153     self->right = createWindow(self->window, NULL, mask, &attrib);
154
155     self->label = createWindow(self->title, NULL, mask, &attrib);
156     self->max = createWindow(self->title, NULL, mask, &attrib);
157     self->close = createWindow(self->title, NULL, mask, &attrib);
158     self->desk = createWindow(self->title, NULL, mask, &attrib);
159     self->shade = createWindow(self->title, NULL, mask, &attrib);
160     self->icon = createWindow(self->title, NULL, mask, &attrib);
161     self->iconify = createWindow(self->title, NULL, mask, &attrib);
162
163     self->handle = createWindow(self->window, NULL, mask, &attrib);
164     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
165     self->rgrip = createWindow(self->handle, NULL, mask, &attrib);
166
167     self->handleleft = createWindow(self->handle, NULL, mask, &attrib);
168     self->handleright = createWindow(self->handle, NULL, mask, &attrib);
169
170     self->handletop = createWindow(self->window, NULL, mask, &attrib);
171     self->handlebottom = createWindow(self->window, NULL, mask, &attrib);
172     self->lgripleft = createWindow(self->window, NULL, mask, &attrib);
173     self->lgriptop = createWindow(self->window, NULL, mask, &attrib);
174     self->lgripbottom = createWindow(self->window, NULL, mask, &attrib);
175     self->rgripright = createWindow(self->window, NULL, mask, &attrib);
176     self->rgriptop = createWindow(self->window, NULL, mask, &attrib);
177     self->rgripbottom = createWindow(self->window, NULL, mask, &attrib);
178
179     self->focused = FALSE;
180
181     /* the other stuff is shown based on decor settings */
182     XMapWindow(obt_display, self->label);
183     XMapWindow(obt_display, self->backback);
184     XMapWindow(obt_display, self->backfront);
185
186     self->max_press = self->close_press = self->desk_press =
187         self->iconify_press = self->shade_press = FALSE;
188     self->max_hover = self->close_hover = self->desk_hover =
189         self->iconify_hover = self->shade_hover = FALSE;
190
191     /* make sure the size will be different the first time, so the extent hints
192        will be set */
193     STRUT_SET(self->oldsize, -1, -1, -1, -1);
194
195     set_theme_statics(self);
196
197     return self;
198 }
199
200 static void set_theme_statics(ObFrame *self)
201 {
202     /* set colors/appearance/sizes for stuff that doesn't change */
203     XResizeWindow(obt_display, self->max,
204                   ob_rr_theme->button_size, ob_rr_theme->button_size);
205     XResizeWindow(obt_display, self->iconify,
206                   ob_rr_theme->button_size, ob_rr_theme->button_size);
207     XResizeWindow(obt_display, self->icon,
208                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
209     XResizeWindow(obt_display, self->close,
210                   ob_rr_theme->button_size, ob_rr_theme->button_size);
211     XResizeWindow(obt_display, self->desk,
212                   ob_rr_theme->button_size, ob_rr_theme->button_size);
213     XResizeWindow(obt_display, self->shade,
214                   ob_rr_theme->button_size, ob_rr_theme->button_size);
215     XResizeWindow(obt_display, self->tltresize,
216                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
217     XResizeWindow(obt_display, self->trtresize,
218                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
219     XResizeWindow(obt_display, self->tllresize,
220                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
221     XResizeWindow(obt_display, self->trrresize,
222                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
223 }
224
225 static void free_theme_statics(ObFrame *self)
226 {
227 }
228
229 void frame_free(ObFrame *self)
230 {
231     free_theme_statics(self);
232
233     XDestroyWindow(obt_display, self->window);
234     if (self->colormap)
235         XFreeColormap(obt_display, self->colormap);
236
237     g_slice_free(ObFrame, self);
238 }
239
240 void frame_show(ObFrame *self)
241 {
242     if (!self->visible) {
243         self->visible = TRUE;
244         framerender_frame(self);
245         /* Grab the server to make sure that the frame window is mapped before
246            the client gets its MapNotify, i.e. to make sure the client is
247            _visible_ when it gets MapNotify. */
248         grab_server(TRUE);
249         XMapWindow(obt_display, self->client->window);
250         XMapWindow(obt_display, self->window);
251         grab_server(FALSE);
252     }
253 }
254
255 void frame_hide(ObFrame *self)
256 {
257     if (self->visible) {
258         self->visible = FALSE;
259         if (!frame_iconify_animating(self))
260             XUnmapWindow(obt_display, self->window);
261         /* we unmap the client itself so that we can get MapRequest
262            events, and because the ICCCM tells us to! */
263         XUnmapWindow(obt_display, self->client->window);
264         self->client->ignore_unmaps += 1;
265     }
266 }
267
268 void frame_adjust_theme(ObFrame *self)
269 {
270     free_theme_statics(self);
271     set_theme_statics(self);
272 }
273
274 #ifdef SHAPE
275 void frame_adjust_shape_kind(ObFrame *self, int kind)
276 {
277     gint num;
278     XRectangle xrect[2];
279     gboolean shaped;
280
281     shaped = (kind == ShapeBounding && self->client->shaped);
282 #ifdef ShapeInput
283     shaped |= (kind == ShapeInput && self->client->shaped_input);
284 #endif
285
286     if (!shaped) {
287         /* clear the shape on the frame window */
288         XShapeCombineMask(obt_display, self->window, kind,
289                           self->size.left,
290                           self->size.top,
291                           None, ShapeSet);
292     } else {
293         /* make the frame's shape match the clients */
294         XShapeCombineShape(obt_display, self->window, kind,
295                            self->size.left,
296                            self->size.top,
297                            self->client->window,
298                            kind, ShapeSet);
299
300         num = 0;
301         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
302             xrect[0].x = 0;
303             xrect[0].y = 0;
304             xrect[0].width = self->area.width;
305             xrect[0].height = self->size.top;
306             ++num;
307         }
308
309         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
310             ob_rr_theme->handle_height > 0)
311         {
312             xrect[1].x = 0;
313             xrect[1].y = FRAME_HANDLE_Y(self);
314             xrect[1].width = self->area.width;
315             xrect[1].height = ob_rr_theme->handle_height +
316                 self->bwidth * 2;
317             ++num;
318         }
319
320         XShapeCombineRectangles(obt_display, self->window,
321                                 ShapeBounding, 0, 0, xrect, num,
322                                 ShapeUnion, Unsorted);
323     }
324 }
325 #endif
326
327 void frame_adjust_shape(ObFrame *self)
328 {
329 #ifdef SHAPE
330   frame_adjust_shape_kind(self, ShapeBounding);
331 #ifdef ShapeInput
332   frame_adjust_shape_kind(self, ShapeInput);
333 #endif
334 #endif
335 }
336
337 void frame_adjust_area(ObFrame *self, gboolean moved,
338                        gboolean resized, gboolean fake)
339 {
340     if (resized) {
341         /* do this before changing the frame's status like max_horz max_vert */
342         frame_adjust_cursors(self);
343
344         self->functions = self->client->functions;
345         self->decorations = self->client->decorations;
346         self->max_horz = self->client->max_horz;
347         self->max_vert = self->client->max_vert;
348         self->shaded = self->client->shaded;
349
350         if (self->decorations & OB_FRAME_DECOR_BORDER)
351             self->bwidth = self->client->undecorated ?
352                 ob_rr_theme->ubwidth : ob_rr_theme->fbwidth;
353         else
354             self->bwidth = 0;
355
356         if (self->decorations & OB_FRAME_DECOR_BORDER &&
357             !self->client->undecorated)
358         {
359             self->cbwidth_l = self->cbwidth_r = ob_rr_theme->cbwidthx;
360             self->cbwidth_t = self->cbwidth_b = ob_rr_theme->cbwidthy;
361         } else
362             self->cbwidth_l = self->cbwidth_t =
363                 self->cbwidth_r = self->cbwidth_b = 0;
364
365         if (self->max_horz) {
366             self->cbwidth_l = self->cbwidth_r = 0;
367             self->width = self->client->area.width;
368             if (self->max_vert)
369                 self->cbwidth_b = 0;
370         } else
371             self->width = self->client->area.width +
372                 self->cbwidth_l + self->cbwidth_r;
373
374         /* some elements are sized based of the width, so don't let them have
375            negative values */
376         self->width = MAX(self->width,
377                           (ob_rr_theme->grip_width + self->bwidth) * 2 + 1);
378
379         STRUT_SET(self->size,
380                   self->cbwidth_l + (!self->max_horz ? self->bwidth : 0),
381                   self->cbwidth_t + self->bwidth,
382                   self->cbwidth_r + (!self->max_horz ? self->bwidth : 0),
383                   self->cbwidth_b +
384                   (!self->max_horz || !self->max_vert ? self->bwidth : 0));
385
386         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
387             self->size.top += ob_rr_theme->title_height + self->bwidth;
388         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
389             ob_rr_theme->handle_height > 0)
390         {
391             self->size.bottom += ob_rr_theme->handle_height + self->bwidth;
392         }
393
394         /* position/size and map/unmap all the windows */
395
396         if (!fake) {
397             gint innercornerheight =
398                 ob_rr_theme->grip_width - self->size.bottom;
399
400             if (self->cbwidth_l) {
401                 XMoveResizeWindow(obt_display, self->innerleft,
402                                   self->size.left - self->cbwidth_l,
403                                   self->size.top,
404                                   self->cbwidth_l, self->client->area.height);
405
406                 XMapWindow(obt_display, self->innerleft);
407             } else
408                 XUnmapWindow(obt_display, self->innerleft);
409
410             if (self->cbwidth_l && innercornerheight > 0) {
411                 XMoveResizeWindow(obt_display, self->innerbll,
412                                   0,
413                                   self->client->area.height - 
414                                   (ob_rr_theme->grip_width -
415                                    self->size.bottom),
416                                   self->cbwidth_l,
417                                   ob_rr_theme->grip_width - self->size.bottom);
418
419                 XMapWindow(obt_display, self->innerbll);
420             } else
421                 XUnmapWindow(obt_display, self->innerbll);
422
423             if (self->cbwidth_r) {
424                 XMoveResizeWindow(obt_display, self->innerright,
425                                   self->size.left + self->client->area.width,
426                                   self->size.top,
427                                   self->cbwidth_r, self->client->area.height);
428
429                 XMapWindow(obt_display, self->innerright);
430             } else
431                 XUnmapWindow(obt_display, self->innerright);
432
433             if (self->cbwidth_r && innercornerheight > 0) {
434                 XMoveResizeWindow(obt_display, self->innerbrr,
435                                   0,
436                                   self->client->area.height - 
437                                   (ob_rr_theme->grip_width -
438                                    self->size.bottom),
439                                   self->cbwidth_r,
440                                   ob_rr_theme->grip_width - self->size.bottom);
441
442                 XMapWindow(obt_display, self->innerbrr);
443             } else
444                 XUnmapWindow(obt_display, self->innerbrr);
445
446             if (self->cbwidth_t) {
447                 XMoveResizeWindow(obt_display, self->innertop,
448                                   self->size.left - self->cbwidth_l,
449                                   self->size.top - self->cbwidth_t,
450                                   self->client->area.width +
451                                   self->cbwidth_l + self->cbwidth_r,
452                                   self->cbwidth_t);
453
454                 XMapWindow(obt_display, self->innertop);
455             } else
456                 XUnmapWindow(obt_display, self->innertop);
457
458             if (self->cbwidth_b) {
459                 XMoveResizeWindow(obt_display, self->innerbottom,
460                                   self->size.left - self->cbwidth_l,
461                                   self->size.top + self->client->area.height,
462                                   self->client->area.width +
463                                   self->cbwidth_l + self->cbwidth_r,
464                                   self->cbwidth_b);
465
466                 XMoveResizeWindow(obt_display, self->innerblb,
467                                   0, 0,
468                                   ob_rr_theme->grip_width + self->bwidth,
469                                   self->cbwidth_b);
470                 XMoveResizeWindow(obt_display, self->innerbrb,
471                                   self->client->area.width +
472                                   self->cbwidth_l + self->cbwidth_r -
473                                   (ob_rr_theme->grip_width + self->bwidth),
474                                   0,
475                                   ob_rr_theme->grip_width + self->bwidth,
476                                   self->cbwidth_b);
477
478                 XMapWindow(obt_display, self->innerbottom);
479                 XMapWindow(obt_display, self->innerblb);
480                 XMapWindow(obt_display, self->innerbrb);
481             } else {
482                 XUnmapWindow(obt_display, self->innerbottom);
483                 XUnmapWindow(obt_display, self->innerblb);
484                 XUnmapWindow(obt_display, self->innerbrb);
485             }
486
487             if (self->bwidth) {
488                 gint titlesides;
489
490                 /* height of titleleft and titleright */
491                 titlesides = (!self->max_horz ? ob_rr_theme->grip_width : 0);
492
493                 XMoveResizeWindow(obt_display, self->titletop,
494                                   ob_rr_theme->grip_width + self->bwidth, 0,
495                                   /* width + bwidth*2 - bwidth*2 - grips*2 */
496                                   self->width - ob_rr_theme->grip_width * 2,
497                                   self->bwidth);
498                 XMoveResizeWindow(obt_display, self->titletopleft,
499                                   0, 0,
500                                   ob_rr_theme->grip_width + self->bwidth,
501                                   self->bwidth);
502                 XMoveResizeWindow(obt_display, self->titletopright,
503                                   self->client->area.width +
504                                   self->size.left + self->size.right -
505                                   ob_rr_theme->grip_width - self->bwidth,
506                                   0,
507                                   ob_rr_theme->grip_width + self->bwidth,
508                                   self->bwidth);
509
510                 if (titlesides > 0) {
511                     XMoveResizeWindow(obt_display, self->titleleft,
512                                       0, self->bwidth,
513                                       self->bwidth,
514                                       titlesides);
515                     XMoveResizeWindow(obt_display, self->titleright,
516                                       self->client->area.width +
517                                       self->size.left + self->size.right -
518                                       self->bwidth,
519                                       self->bwidth,
520                                       self->bwidth,
521                                       titlesides);
522
523                     XMapWindow(obt_display, self->titleleft);
524                     XMapWindow(obt_display, self->titleright);
525                 } else {
526                     XUnmapWindow(obt_display, self->titleleft);
527                     XUnmapWindow(obt_display, self->titleright);
528                 }
529
530                 XMapWindow(obt_display, self->titletop);
531                 XMapWindow(obt_display, self->titletopleft);
532                 XMapWindow(obt_display, self->titletopright);
533
534                 if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
535                     XMoveResizeWindow(obt_display, self->titlebottom,
536                                       (self->max_horz ? 0 : self->bwidth),
537                                       ob_rr_theme->title_height + self->bwidth,
538                                       self->width,
539                                       self->bwidth);
540
541                     XMapWindow(obt_display, self->titlebottom);
542                 } else
543                     XUnmapWindow(obt_display, self->titlebottom);
544             } else {
545                 XUnmapWindow(obt_display, self->titlebottom);
546
547                 XUnmapWindow(obt_display, self->titletop);
548                 XUnmapWindow(obt_display, self->titletopleft);
549                 XUnmapWindow(obt_display, self->titletopright);
550                 XUnmapWindow(obt_display, self->titleleft);
551                 XUnmapWindow(obt_display, self->titleright);
552             }
553
554             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
555                 XMoveResizeWindow(obt_display, self->title,
556                                   (self->max_horz ? 0 : self->bwidth),
557                                   self->bwidth,
558                                   self->width, ob_rr_theme->title_height);
559
560                 XMapWindow(obt_display, self->title);
561
562                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
563                     XMoveResizeWindow(obt_display, self->topresize,
564                                       ob_rr_theme->grip_width,
565                                       0,
566                                       self->width - ob_rr_theme->grip_width *2,
567                                       ob_rr_theme->paddingy + 1);
568
569                     XMoveWindow(obt_display, self->tltresize, 0, 0);
570                     XMoveWindow(obt_display, self->tllresize, 0, 0);
571                     XMoveWindow(obt_display, self->trtresize,
572                                 self->width - ob_rr_theme->grip_width, 0);
573                     XMoveWindow(obt_display, self->trrresize,
574                                 self->width - ob_rr_theme->paddingx - 1, 0);
575
576                     XMapWindow(obt_display, self->topresize);
577                     XMapWindow(obt_display, self->tltresize);
578                     XMapWindow(obt_display, self->tllresize);
579                     XMapWindow(obt_display, self->trtresize);
580                     XMapWindow(obt_display, self->trrresize);
581                 } else {
582                     XUnmapWindow(obt_display, self->topresize);
583                     XUnmapWindow(obt_display, self->tltresize);
584                     XUnmapWindow(obt_display, self->tllresize);
585                     XUnmapWindow(obt_display, self->trtresize);
586                     XUnmapWindow(obt_display, self->trrresize);
587                 }
588             } else
589                 XUnmapWindow(obt_display, self->title);
590         }
591
592         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
593             /* layout the title bar elements */
594             layout_title(self);
595
596         if (!fake) {
597             gint sidebwidth = self->max_horz ? 0 : self->bwidth;
598
599             if (self->bwidth && self->size.bottom) {
600                 XMoveResizeWindow(obt_display, self->handlebottom,
601                                   ob_rr_theme->grip_width +
602                                   self->bwidth + sidebwidth,
603                                   self->size.top + self->client->area.height +
604                                   self->size.bottom - self->bwidth,
605                                   self->width - (ob_rr_theme->grip_width +
606                                                  sidebwidth) * 2,
607                                   self->bwidth);
608
609
610                 if (sidebwidth) {
611                     XMoveResizeWindow(obt_display, self->lgripleft,
612                                       0,
613                                       self->size.top +
614                                       self->client->area.height +
615                                       self->size.bottom -
616                                       (!self->max_horz ?
617                                        ob_rr_theme->grip_width :
618                                        self->size.bottom - self->cbwidth_b),
619                                       self->bwidth,
620                                       (!self->max_horz ?
621                                        ob_rr_theme->grip_width :
622                                        self->size.bottom - self->cbwidth_b));
623                     XMoveResizeWindow(obt_display, self->rgripright,
624                                   self->size.left +
625                                       self->client->area.width +
626                                       self->size.right - self->bwidth,
627                                       self->size.top +
628                                       self->client->area.height +
629                                       self->size.bottom -
630                                       (!self->max_horz ?
631                                        ob_rr_theme->grip_width :
632                                        self->size.bottom - self->cbwidth_b),
633                                       self->bwidth,
634                                       (!self->max_horz ?
635                                        ob_rr_theme->grip_width :
636                                        self->size.bottom - self->cbwidth_b));
637
638                     XMapWindow(obt_display, self->lgripleft);
639                     XMapWindow(obt_display, self->rgripright);
640                 } else {
641                     XUnmapWindow(obt_display, self->lgripleft);
642                     XUnmapWindow(obt_display, self->rgripright);
643                 }
644
645                 XMoveResizeWindow(obt_display, self->lgripbottom,
646                                   sidebwidth,
647                                   self->size.top + self->client->area.height +
648                                   self->size.bottom - self->bwidth,
649                                   ob_rr_theme->grip_width + self->bwidth,
650                                   self->bwidth);
651                 XMoveResizeWindow(obt_display, self->rgripbottom,
652                                   self->size.left + self->client->area.width +
653                                   self->size.right - self->bwidth - sidebwidth-
654                                   ob_rr_theme->grip_width,
655                                   self->size.top + self->client->area.height +
656                                   self->size.bottom - self->bwidth,
657                                   ob_rr_theme->grip_width + self->bwidth,
658                                   self->bwidth);
659
660                 XMapWindow(obt_display, self->handlebottom);
661                 XMapWindow(obt_display, self->lgripbottom);
662                 XMapWindow(obt_display, self->rgripbottom);
663
664                 if (self->decorations & OB_FRAME_DECOR_HANDLE &&
665                     ob_rr_theme->handle_height > 0)
666                 {
667                     XMoveResizeWindow(obt_display, self->handletop,
668                                       ob_rr_theme->grip_width +
669                                       self->bwidth + sidebwidth,
670                                       FRAME_HANDLE_Y(self),
671                                       self->width - (ob_rr_theme->grip_width +
672                                                      sidebwidth) * 2,
673                                       self->bwidth);
674                     XMapWindow(obt_display, self->handletop);
675
676                     if (self->decorations & OB_FRAME_DECOR_GRIPS) {
677                         XMoveResizeWindow(obt_display, self->handleleft,
678                                           ob_rr_theme->grip_width,
679                                           0,
680                                           self->bwidth,
681                                           ob_rr_theme->handle_height);
682                         XMoveResizeWindow(obt_display, self->handleright,
683                                           self->width -
684                                           ob_rr_theme->grip_width -
685                                           self->bwidth,
686                                           0,
687                                           self->bwidth,
688                                           ob_rr_theme->handle_height);
689
690                         XMoveResizeWindow(obt_display, self->lgriptop,
691                                           sidebwidth,
692                                           FRAME_HANDLE_Y(self),
693                                           ob_rr_theme->grip_width +
694                                           self->bwidth,
695                                           self->bwidth);
696                         XMoveResizeWindow(obt_display, self->rgriptop,
697                                           self->size.left +
698                                           self->client->area.width +
699                                           self->size.right - self->bwidth -
700                                           sidebwidth - ob_rr_theme->grip_width,
701                                           FRAME_HANDLE_Y(self),
702                                           ob_rr_theme->grip_width +
703                                           self->bwidth,
704                                           self->bwidth);
705
706                         XMapWindow(obt_display, self->handleleft);
707                         XMapWindow(obt_display, self->handleright);
708                         XMapWindow(obt_display, self->lgriptop);
709                         XMapWindow(obt_display, self->rgriptop);
710                     } else {
711                         XUnmapWindow(obt_display, self->handleleft);
712                         XUnmapWindow(obt_display, self->handleright);
713                         XUnmapWindow(obt_display, self->lgriptop);
714                         XUnmapWindow(obt_display, self->rgriptop);
715                     }
716                 } else {
717                     XUnmapWindow(obt_display, self->handleleft);
718                     XUnmapWindow(obt_display, self->handleright);
719                     XUnmapWindow(obt_display, self->lgriptop);
720                     XUnmapWindow(obt_display, self->rgriptop);
721
722                     XUnmapWindow(obt_display, self->handletop);
723                 }
724             } else {
725                 XUnmapWindow(obt_display, self->handleleft);
726                 XUnmapWindow(obt_display, self->handleright);
727                 XUnmapWindow(obt_display, self->lgriptop);
728                 XUnmapWindow(obt_display, self->rgriptop);
729
730                 XUnmapWindow(obt_display, self->handletop);
731
732                 XUnmapWindow(obt_display, self->handlebottom);
733                 XUnmapWindow(obt_display, self->lgripleft);
734                 XUnmapWindow(obt_display, self->rgripright);
735                 XUnmapWindow(obt_display, self->lgripbottom);
736                 XUnmapWindow(obt_display, self->rgripbottom);
737             }
738
739             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
740                 ob_rr_theme->handle_height > 0)
741             {
742                 XMoveResizeWindow(obt_display, self->handle,
743                                   sidebwidth,
744                                   FRAME_HANDLE_Y(self) + self->bwidth,
745                                   self->width, ob_rr_theme->handle_height);
746                 XMapWindow(obt_display, self->handle);
747
748                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
749                     XMoveResizeWindow(obt_display, self->lgrip,
750                                       0, 0,
751                                       ob_rr_theme->grip_width,
752                                       ob_rr_theme->handle_height);
753                     XMoveResizeWindow(obt_display, self->rgrip,
754                                       self->width - ob_rr_theme->grip_width,
755                                       0,
756                                       ob_rr_theme->grip_width,
757                                       ob_rr_theme->handle_height);
758
759                     XMapWindow(obt_display, self->lgrip);
760                     XMapWindow(obt_display, self->rgrip);
761                 } else {
762                     XUnmapWindow(obt_display, self->lgrip);
763                     XUnmapWindow(obt_display, self->rgrip);
764                 }
765             } else {
766                 XUnmapWindow(obt_display, self->lgrip);
767                 XUnmapWindow(obt_display, self->rgrip);
768
769                 XUnmapWindow(obt_display, self->handle);
770             }
771
772             if (self->bwidth && !self->max_horz &&
773                 (self->client->area.height + self->size.top +
774                  self->size.bottom) > ob_rr_theme->grip_width * 2)
775             {
776                 XMoveResizeWindow(obt_display, self->left,
777                                   0,
778                                   self->bwidth + ob_rr_theme->grip_width,
779                                   self->bwidth,
780                                   self->client->area.height +
781                                   self->size.top + self->size.bottom -
782                                   ob_rr_theme->grip_width * 2);
783
784                 XMapWindow(obt_display, self->left);
785             } else
786                 XUnmapWindow(obt_display, self->left);
787
788             if (self->bwidth && !self->max_horz &&
789                 (self->client->area.height + self->size.top +
790                  self->size.bottom) > ob_rr_theme->grip_width * 2)
791             {
792                 XMoveResizeWindow(obt_display, self->right,
793                                   self->client->area.width + self->cbwidth_l +
794                                   self->cbwidth_r + self->bwidth,
795                                   self->bwidth + ob_rr_theme->grip_width,
796                                   self->bwidth,
797                                   self->client->area.height +
798                                   self->size.top + self->size.bottom -
799                                   ob_rr_theme->grip_width * 2);
800
801                 XMapWindow(obt_display, self->right);
802             } else
803                 XUnmapWindow(obt_display, self->right);
804
805             XMoveResizeWindow(obt_display, self->backback,
806                               self->size.left, self->size.top,
807                               self->client->area.width,
808                               self->client->area.height);
809         }
810     }
811
812     /* shading can change without being moved or resized */
813     RECT_SET_SIZE(self->area,
814                   self->client->area.width +
815                   self->size.left + self->size.right,
816                   (self->client->shaded ?
817                    ob_rr_theme->title_height + self->bwidth * 2:
818                    self->client->area.height +
819                    self->size.top + self->size.bottom));
820
821     if ((moved || resized) && !fake) {
822         /* find the new coordinates, done after setting the frame.size, for
823            frame_client_gravity. */
824         self->area.x = self->client->area.x;
825         self->area.y = self->client->area.y;
826         frame_client_gravity(self, &self->area.x, &self->area.y);
827     }
828
829     if (!fake) {
830         if (!frame_iconify_animating(self))
831             /* move and resize the top level frame.
832                shading can change without being moved or resized.
833
834                but don't do this during an iconify animation. it will be
835                reflected afterwards.
836             */
837             XMoveResizeWindow(obt_display, self->window,
838                               self->area.x,
839                               self->area.y,
840                               self->area.width,
841                               self->area.height);
842
843         /* when the client has StaticGravity, it likes to move around.
844            also this correctly positions the client when it maps.
845            this also needs to be run when the frame's decorations sizes change!
846         */
847         XMoveWindow(obt_display, self->client->window,
848                     self->size.left, self->size.top);
849
850         if (resized) {
851             self->need_render = TRUE;
852             framerender_frame(self);
853             frame_adjust_shape(self);
854         }
855
856         if (!STRUT_EQUAL(self->size, self->oldsize)) {
857             gulong vals[4];
858             vals[0] = self->size.left;
859             vals[1] = self->size.right;
860             vals[2] = self->size.top;
861             vals[3] = self->size.bottom;
862             OBT_PROP_SETA32(self->client->window, NET_FRAME_EXTENTS,
863                             CARDINAL, vals, 4);
864             OBT_PROP_SETA32(self->client->window, KDE_NET_WM_FRAME_STRUT,
865                             CARDINAL, vals, 4);
866             self->oldsize = self->size;
867         }
868
869         /* if this occurs while we are focus cycling, the indicator needs to
870            match the changes */
871         if (focus_cycle_target == self->client)
872             focus_cycle_update_indicator(self->client);
873     }
874     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR) &&
875         self->label_width)
876     {
877         XResizeWindow(obt_display, self->label, self->label_width,
878                       ob_rr_theme->label_height);
879     }
880 }
881
882 static void frame_adjust_cursors(ObFrame *self)
883 {
884     if ((self->functions & OB_CLIENT_FUNC_RESIZE) !=
885         (self->client->functions & OB_CLIENT_FUNC_RESIZE) ||
886         self->max_horz != self->client->max_horz ||
887         self->max_vert != self->client->max_vert ||
888         self->shaded != self->client->shaded)
889     {
890         gboolean r = (self->client->functions & OB_CLIENT_FUNC_RESIZE) &&
891             !(self->client->max_horz && self->client->max_vert);
892         gboolean topbot = !self->client->max_vert;
893         gboolean sh = self->client->shaded;
894         XSetWindowAttributes a;
895
896         /* these ones turn off when max vert, and some when shaded */
897         a.cursor = ob_cursor(r && topbot && !sh ?
898                              OB_CURSOR_NORTH : OB_CURSOR_NONE);
899         XChangeWindowAttributes(obt_display, self->topresize, CWCursor, &a);
900         XChangeWindowAttributes(obt_display, self->titletop, CWCursor, &a);
901         a.cursor = ob_cursor(r && topbot ? OB_CURSOR_SOUTH : OB_CURSOR_NONE);
902         XChangeWindowAttributes(obt_display, self->handle, CWCursor, &a);
903         XChangeWindowAttributes(obt_display, self->handletop, CWCursor, &a);
904         XChangeWindowAttributes(obt_display, self->handlebottom, CWCursor, &a);
905         XChangeWindowAttributes(obt_display, self->innerbottom, CWCursor, &a);
906
907         /* these ones change when shaded */
908         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_WEST : OB_CURSOR_NORTHWEST) :
909                              OB_CURSOR_NONE);
910         XChangeWindowAttributes(obt_display, self->titleleft, CWCursor, &a);
911         XChangeWindowAttributes(obt_display, self->tltresize, CWCursor, &a);
912         XChangeWindowAttributes(obt_display, self->tllresize, CWCursor, &a);
913         XChangeWindowAttributes(obt_display, self->titletopleft, CWCursor, &a);
914         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_EAST : OB_CURSOR_NORTHEAST) :
915                              OB_CURSOR_NONE);
916         XChangeWindowAttributes(obt_display, self->titleright, CWCursor, &a);
917         XChangeWindowAttributes(obt_display, self->trtresize, CWCursor, &a);
918         XChangeWindowAttributes(obt_display, self->trrresize, CWCursor, &a);
919         XChangeWindowAttributes(obt_display, self->titletopright, CWCursor,&a);
920
921         /* these ones are pretty static */
922         a.cursor = ob_cursor(r ? OB_CURSOR_WEST : OB_CURSOR_NONE);
923         XChangeWindowAttributes(obt_display, self->left, CWCursor, &a);
924         XChangeWindowAttributes(obt_display, self->innerleft, CWCursor, &a);
925         a.cursor = ob_cursor(r ? OB_CURSOR_EAST : OB_CURSOR_NONE);
926         XChangeWindowAttributes(obt_display, self->right, CWCursor, &a);
927         XChangeWindowAttributes(obt_display, self->innerright, CWCursor, &a);
928         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHWEST : OB_CURSOR_NONE);
929         XChangeWindowAttributes(obt_display, self->lgrip, CWCursor, &a);
930         XChangeWindowAttributes(obt_display, self->handleleft, CWCursor, &a);
931         XChangeWindowAttributes(obt_display, self->lgripleft, CWCursor, &a);
932         XChangeWindowAttributes(obt_display, self->lgriptop, CWCursor, &a);
933         XChangeWindowAttributes(obt_display, self->lgripbottom, CWCursor, &a);
934         XChangeWindowAttributes(obt_display, self->innerbll, CWCursor, &a);
935         XChangeWindowAttributes(obt_display, self->innerblb, CWCursor, &a);
936         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHEAST : OB_CURSOR_NONE);
937         XChangeWindowAttributes(obt_display, self->rgrip, CWCursor, &a);
938         XChangeWindowAttributes(obt_display, self->handleright, CWCursor, &a);
939         XChangeWindowAttributes(obt_display, self->rgripright, CWCursor, &a);
940         XChangeWindowAttributes(obt_display, self->rgriptop, CWCursor, &a);
941         XChangeWindowAttributes(obt_display, self->rgripbottom, CWCursor, &a);
942         XChangeWindowAttributes(obt_display, self->innerbrr, CWCursor, &a);
943         XChangeWindowAttributes(obt_display, self->innerbrb, CWCursor, &a);
944     }
945 }
946
947 void frame_adjust_client_area(ObFrame *self)
948 {
949     /* adjust the window which is there to prevent flashing on unmap */
950     XMoveResizeWindow(obt_display, self->backfront, 0, 0,
951                       self->client->area.width,
952                       self->client->area.height);
953 }
954
955 void frame_adjust_state(ObFrame *self)
956 {
957     self->need_render = TRUE;
958     framerender_frame(self);
959 }
960
961 void frame_adjust_focus(ObFrame *self, gboolean hilite)
962 {
963     ob_debug_type(OB_DEBUG_FOCUS,
964                   "Frame for 0x%x has focus: %d",
965                   self->client->window, hilite);
966     self->focused = hilite;
967     self->need_render = TRUE;
968     framerender_frame(self);
969     XFlush(obt_display);
970 }
971
972 void frame_adjust_title(ObFrame *self)
973 {
974     self->need_render = TRUE;
975     framerender_frame(self);
976 }
977
978 void frame_adjust_icon(ObFrame *self)
979 {
980     self->need_render = TRUE;
981     framerender_frame(self);
982 }
983
984 void frame_grab_client(ObFrame *self)
985 {
986     /* DO NOT map the client window here. we used to do that, but it is bogus.
987        we need to set up the client's dimensions and everything before we
988        send a mapnotify or we create race conditions.
989     */
990
991     /* reparent the client to the frame */
992     XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
993
994     /*
995       When reparenting the client window, it is usually not mapped yet, since
996       this occurs from a MapRequest. However, in the case where Openbox is
997       starting up, the window is already mapped, so we'll see an unmap event
998       for it.
999     */
1000     if (ob_state() == OB_STATE_STARTING)
1001         ++self->client->ignore_unmaps;
1002
1003     /* select the event mask on the client's parent (to receive config/map
1004        req's) the ButtonPress is to catch clicks on the client border */
1005     XSelectInput(obt_display, self->window, FRAME_EVENTMASK);
1006
1007     /* set all the windows for the frame in the window_map */
1008     window_add(&self->window, CLIENT_AS_WINDOW(self->client));
1009     window_add(&self->backback, CLIENT_AS_WINDOW(self->client));
1010     window_add(&self->backfront, CLIENT_AS_WINDOW(self->client));
1011     window_add(&self->innerleft, CLIENT_AS_WINDOW(self->client));
1012     window_add(&self->innertop, CLIENT_AS_WINDOW(self->client));
1013     window_add(&self->innerright, CLIENT_AS_WINDOW(self->client));
1014     window_add(&self->innerbottom, CLIENT_AS_WINDOW(self->client));
1015     window_add(&self->innerblb, CLIENT_AS_WINDOW(self->client));
1016     window_add(&self->innerbll, CLIENT_AS_WINDOW(self->client));
1017     window_add(&self->innerbrb, CLIENT_AS_WINDOW(self->client));
1018     window_add(&self->innerbrr, CLIENT_AS_WINDOW(self->client));
1019     window_add(&self->title, CLIENT_AS_WINDOW(self->client));
1020     window_add(&self->label, CLIENT_AS_WINDOW(self->client));
1021     window_add(&self->max, CLIENT_AS_WINDOW(self->client));
1022     window_add(&self->close, CLIENT_AS_WINDOW(self->client));
1023     window_add(&self->desk, CLIENT_AS_WINDOW(self->client));
1024     window_add(&self->shade, CLIENT_AS_WINDOW(self->client));
1025     window_add(&self->icon, CLIENT_AS_WINDOW(self->client));
1026     window_add(&self->iconify, CLIENT_AS_WINDOW(self->client));
1027     window_add(&self->handle, CLIENT_AS_WINDOW(self->client));
1028     window_add(&self->lgrip, CLIENT_AS_WINDOW(self->client));
1029     window_add(&self->rgrip, CLIENT_AS_WINDOW(self->client));
1030     window_add(&self->topresize, CLIENT_AS_WINDOW(self->client));
1031     window_add(&self->tltresize, CLIENT_AS_WINDOW(self->client));
1032     window_add(&self->tllresize, CLIENT_AS_WINDOW(self->client));
1033     window_add(&self->trtresize, CLIENT_AS_WINDOW(self->client));
1034     window_add(&self->trrresize, CLIENT_AS_WINDOW(self->client));
1035     window_add(&self->left, CLIENT_AS_WINDOW(self->client));
1036     window_add(&self->right, CLIENT_AS_WINDOW(self->client));
1037     window_add(&self->titleleft, CLIENT_AS_WINDOW(self->client));
1038     window_add(&self->titletop, CLIENT_AS_WINDOW(self->client));
1039     window_add(&self->titletopleft, CLIENT_AS_WINDOW(self->client));
1040     window_add(&self->titletopright, CLIENT_AS_WINDOW(self->client));
1041     window_add(&self->titleright, CLIENT_AS_WINDOW(self->client));
1042     window_add(&self->titlebottom, CLIENT_AS_WINDOW(self->client));
1043     window_add(&self->handleleft, CLIENT_AS_WINDOW(self->client));
1044     window_add(&self->handletop, CLIENT_AS_WINDOW(self->client));
1045     window_add(&self->handleright, CLIENT_AS_WINDOW(self->client));
1046     window_add(&self->handlebottom, CLIENT_AS_WINDOW(self->client));
1047     window_add(&self->lgripleft, CLIENT_AS_WINDOW(self->client));
1048     window_add(&self->lgriptop, CLIENT_AS_WINDOW(self->client));
1049     window_add(&self->lgripbottom, CLIENT_AS_WINDOW(self->client));
1050     window_add(&self->rgripright, CLIENT_AS_WINDOW(self->client));
1051     window_add(&self->rgriptop, CLIENT_AS_WINDOW(self->client));
1052     window_add(&self->rgripbottom, CLIENT_AS_WINDOW(self->client));
1053 }
1054
1055 static gboolean find_reparent(XEvent *e, gpointer data)
1056 {
1057     const ObFrame *self = data;
1058
1059     /* Find ReparentNotify events for the window that aren't being reparented into the
1060        frame, thus the client reparenting itself off the frame. */
1061     return e->type == ReparentNotify && e->xreparent.window == self->client->window &&
1062         e->xreparent.parent != self->window;
1063 }
1064
1065 void frame_release_client(ObFrame *self)
1066 {
1067     /* if there was any animation going on, kill it */
1068     if (self->iconify_animation_timer)
1069         g_source_remove(self->iconify_animation_timer);
1070
1071     /* check if the app has already reparented its window away */
1072     if (!xqueue_exists_local(find_reparent, self)) {
1073         /* according to the ICCCM - if the client doesn't reparent itself,
1074            then we will reparent the window to root for them */
1075         XReparentWindow(obt_display, self->client->window, obt_root(ob_screen),
1076                         self->client->area.x, self->client->area.y);
1077     }
1078
1079     /* remove all the windows for the frame from the window_map */
1080     window_remove(self->window);
1081     window_remove(self->backback);
1082     window_remove(self->backfront);
1083     window_remove(self->innerleft);
1084     window_remove(self->innertop);
1085     window_remove(self->innerright);
1086     window_remove(self->innerbottom);
1087     window_remove(self->innerblb);
1088     window_remove(self->innerbll);
1089     window_remove(self->innerbrb);
1090     window_remove(self->innerbrr);
1091     window_remove(self->title);
1092     window_remove(self->label);
1093     window_remove(self->max);
1094     window_remove(self->close);
1095     window_remove(self->desk);
1096     window_remove(self->shade);
1097     window_remove(self->icon);
1098     window_remove(self->iconify);
1099     window_remove(self->handle);
1100     window_remove(self->lgrip);
1101     window_remove(self->rgrip);
1102     window_remove(self->topresize);
1103     window_remove(self->tltresize);
1104     window_remove(self->tllresize);
1105     window_remove(self->trtresize);
1106     window_remove(self->trrresize);
1107     window_remove(self->left);
1108     window_remove(self->right);
1109     window_remove(self->titleleft);
1110     window_remove(self->titletop);
1111     window_remove(self->titletopleft);
1112     window_remove(self->titletopright);
1113     window_remove(self->titleright);
1114     window_remove(self->titlebottom);
1115     window_remove(self->handleleft);
1116     window_remove(self->handletop);
1117     window_remove(self->handleright);
1118     window_remove(self->handlebottom);
1119     window_remove(self->lgripleft);
1120     window_remove(self->lgriptop);
1121     window_remove(self->lgripbottom);
1122     window_remove(self->rgripright);
1123     window_remove(self->rgriptop);
1124     window_remove(self->rgripbottom);
1125
1126     if (self->flash_timer) g_source_remove(self->flash_timer);
1127 }
1128
1129 /* is there anything present between us and the label? */
1130 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
1131     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
1132         if (*lc == ' ') continue; /* it was invalid */
1133         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
1134             return TRUE;
1135         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
1136             return TRUE;
1137         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
1138             return TRUE;
1139         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
1140             return TRUE;
1141         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
1142             return TRUE;
1143         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
1144             return TRUE;
1145         if (*lc == 'L') return FALSE;
1146     }
1147     return FALSE;
1148 }
1149
1150 static void place_button(ObFrame *self, const char *lc, gint bwidth,
1151                          gint left, gint i,
1152                          gint *x, gint *button_on, gint *button_x)
1153 {
1154   if (!(*button_on = is_button_present(self, lc, i)))
1155     return;
1156
1157   self->label_width -= bwidth;
1158   if (i > 0)
1159     *button_x = *x;
1160   *x += i * bwidth;
1161   if (i < 0) {
1162     if (self->label_x <= left || *x > self->label_x) {
1163       *button_x = *x;
1164     } else {
1165       /* the button would have been drawn on top of another button */
1166       *button_on = FALSE;
1167       self->label_width += bwidth;
1168     }
1169   }
1170 }
1171
1172 static void layout_title(ObFrame *self)
1173 {
1174     gchar *lc;
1175     gint i;
1176
1177     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
1178     /* position of the leftmost button */
1179     const gint left = ob_rr_theme->paddingx + 1;
1180     /* position of the rightmost button */
1181     const gint right = self->width;
1182
1183     /* turn them all off */
1184     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
1185         self->max_on = self->close_on = self->label_on = FALSE;
1186     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
1187     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
1188
1189     /* figure out what's being shown, find each element's position, and the
1190        width of the label
1191
1192        do the ones before the label, then after the label,
1193        i will be +1 the first time through when working to the left,
1194        and -1 the second time through when working to the right */
1195     for (i = 1; i >= -1; i-=2) {
1196         gint x;
1197         ObFrameContext *firstcon;
1198
1199         if (i > 0) {
1200             x = left;
1201             lc = config_title_layout;
1202             firstcon = &self->leftmost;
1203         } else {
1204             x = right;
1205             lc = config_title_layout + strlen(config_title_layout)-1;
1206             firstcon = &self->rightmost;
1207         }
1208
1209         /* stop at the end of the string (or the label, which calls break) */
1210         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
1211             if (*lc == 'L') {
1212                 if (i > 0) {
1213                     self->label_on = TRUE;
1214                     self->label_x = x;
1215                 }
1216                 break; /* break the for loop, do other side of label */
1217             } else if (*lc == 'N') {
1218                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
1219                 /* icon is bigger than buttons */
1220                 place_button(self, lc, bwidth + 2, left, i, &x, &self->icon_on, &self->icon_x);
1221             } else if (*lc == 'D') {
1222                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
1223                 place_button(self, lc, bwidth, left, i, &x, &self->desk_on, &self->desk_x);
1224             } else if (*lc == 'S') {
1225                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
1226                 place_button(self, lc, bwidth, left, i, &x, &self->shade_on, &self->shade_x);
1227             } else if (*lc == 'I') {
1228                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
1229                 place_button(self, lc, bwidth, left, i, &x, &self->iconify_on, &self->iconify_x);
1230             } else if (*lc == 'M') {
1231                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
1232                 place_button(self, lc, bwidth, left, i, &x, &self->max_on, &self->max_x);
1233             } else if (*lc == 'C') {
1234                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
1235                 place_button(self, lc, bwidth, left, i, &x, &self->close_on, &self->close_x);
1236             } else
1237                 continue; /* don't set firstcon */
1238             firstcon = NULL;
1239         }
1240     }
1241
1242     /* position and map the elements */
1243     if (self->icon_on) {
1244         XMapWindow(obt_display, self->icon);
1245         XMoveWindow(obt_display, self->icon, self->icon_x,
1246                     ob_rr_theme->paddingy);
1247     } else
1248         XUnmapWindow(obt_display, self->icon);
1249
1250     if (self->desk_on) {
1251         XMapWindow(obt_display, self->desk);
1252         XMoveWindow(obt_display, self->desk, self->desk_x,
1253                     ob_rr_theme->paddingy + 1);
1254     } else
1255         XUnmapWindow(obt_display, self->desk);
1256
1257     if (self->shade_on) {
1258         XMapWindow(obt_display, self->shade);
1259         XMoveWindow(obt_display, self->shade, self->shade_x,
1260                     ob_rr_theme->paddingy + 1);
1261     } else
1262         XUnmapWindow(obt_display, self->shade);
1263
1264     if (self->iconify_on) {
1265         XMapWindow(obt_display, self->iconify);
1266         XMoveWindow(obt_display, self->iconify, self->iconify_x,
1267                     ob_rr_theme->paddingy + 1);
1268     } else
1269         XUnmapWindow(obt_display, self->iconify);
1270
1271     if (self->max_on) {
1272         XMapWindow(obt_display, self->max);
1273         XMoveWindow(obt_display, self->max, self->max_x,
1274                     ob_rr_theme->paddingy + 1);
1275     } else
1276         XUnmapWindow(obt_display, self->max);
1277
1278     if (self->close_on) {
1279         XMapWindow(obt_display, self->close);
1280         XMoveWindow(obt_display, self->close, self->close_x,
1281                     ob_rr_theme->paddingy + 1);
1282     } else
1283         XUnmapWindow(obt_display, self->close);
1284
1285     if (self->label_on && self->label_width > 0) {
1286         XMapWindow(obt_display, self->label);
1287         XMoveWindow(obt_display, self->label, self->label_x,
1288                     ob_rr_theme->paddingy);
1289     } else
1290         XUnmapWindow(obt_display, self->label);
1291 }
1292
1293 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
1294 {
1295     gchar *p, *n;
1296
1297     if (!*names) /* empty string */
1298         return FALSE;
1299
1300     /* find the first space */
1301     for (p = names; *p; p = g_utf8_next_char(p)) {
1302         const gunichar c = g_utf8_get_char(p);
1303         if (g_unichar_isspace(c)) break;
1304     }
1305
1306     if (p == names) {
1307         /* leading spaces in the string */
1308         n = g_utf8_next_char(names);
1309         if (!frame_next_context_from_string(n, cx))
1310             return FALSE;
1311     } else {
1312         n = p;
1313         if (*p) {
1314             /* delete the space with null zero(s) */
1315             while (n < g_utf8_next_char(p))
1316                 *(n++) = '\0';
1317         }
1318
1319         *cx = frame_context_from_string(names);
1320
1321         /* find the next non-space */
1322         for (; *n; n = g_utf8_next_char(n)) {
1323             const gunichar c = g_utf8_get_char(n);
1324             if (!g_unichar_isspace(c)) break;
1325         }
1326     }
1327
1328     /* delete everything we just read (copy everything at n to the start of
1329        the string */
1330     for (p = names; *n; ++p, ++n)
1331         *p = *n;
1332     *p = *n;
1333
1334     return TRUE;
1335 }
1336
1337 ObFrameContext frame_context_from_string(const gchar *name)
1338 {
1339     if (!g_ascii_strcasecmp("Desktop", name))
1340         return OB_FRAME_CONTEXT_DESKTOP;
1341     else if (!g_ascii_strcasecmp("Root", name))
1342         return OB_FRAME_CONTEXT_ROOT;
1343     else if (!g_ascii_strcasecmp("Client", name))
1344         return OB_FRAME_CONTEXT_CLIENT;
1345     else if (!g_ascii_strcasecmp("Titlebar", name))
1346         return OB_FRAME_CONTEXT_TITLEBAR;
1347     else if (!g_ascii_strcasecmp("Frame", name))
1348         return OB_FRAME_CONTEXT_FRAME;
1349     else if (!g_ascii_strcasecmp("TLCorner", name))
1350         return OB_FRAME_CONTEXT_TLCORNER;
1351     else if (!g_ascii_strcasecmp("TRCorner", name))
1352         return OB_FRAME_CONTEXT_TRCORNER;
1353     else if (!g_ascii_strcasecmp("BLCorner", name))
1354         return OB_FRAME_CONTEXT_BLCORNER;
1355     else if (!g_ascii_strcasecmp("BRCorner", name))
1356         return OB_FRAME_CONTEXT_BRCORNER;
1357     else if (!g_ascii_strcasecmp("Top", name))
1358         return OB_FRAME_CONTEXT_TOP;
1359     else if (!g_ascii_strcasecmp("Bottom", name))
1360         return OB_FRAME_CONTEXT_BOTTOM;
1361     else if (!g_ascii_strcasecmp("Left", name))
1362         return OB_FRAME_CONTEXT_LEFT;
1363     else if (!g_ascii_strcasecmp("Right", name))
1364         return OB_FRAME_CONTEXT_RIGHT;
1365     else if (!g_ascii_strcasecmp("Maximize", name))
1366         return OB_FRAME_CONTEXT_MAXIMIZE;
1367     else if (!g_ascii_strcasecmp("AllDesktops", name))
1368         return OB_FRAME_CONTEXT_ALLDESKTOPS;
1369     else if (!g_ascii_strcasecmp("Shade", name))
1370         return OB_FRAME_CONTEXT_SHADE;
1371     else if (!g_ascii_strcasecmp("Iconify", name))
1372         return OB_FRAME_CONTEXT_ICONIFY;
1373     else if (!g_ascii_strcasecmp("Icon", name))
1374         return OB_FRAME_CONTEXT_ICON;
1375     else if (!g_ascii_strcasecmp("Close", name))
1376         return OB_FRAME_CONTEXT_CLOSE;
1377     else if (!g_ascii_strcasecmp("MoveResize", name))
1378         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1379     else if (!g_ascii_strcasecmp("Dock", name))
1380         return OB_FRAME_CONTEXT_DOCK;
1381
1382     return OB_FRAME_CONTEXT_NONE;
1383 }
1384
1385 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
1386 {
1387     ObFrame *self;
1388     ObWindow *obwin;
1389
1390     if (moveresize_in_progress)
1391         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1392
1393     if (win == obt_root(ob_screen))
1394         return OB_FRAME_CONTEXT_ROOT;
1395     if ((obwin = window_find(win))) {
1396         if (WINDOW_IS_DOCK(obwin)) {
1397           return OB_FRAME_CONTEXT_DOCK;
1398         }
1399     }
1400     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
1401     if (win == client->window) {
1402         /* conceptually, this is the desktop, as far as users are
1403            concerned */
1404         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1405             return OB_FRAME_CONTEXT_DESKTOP;
1406         return OB_FRAME_CONTEXT_CLIENT;
1407     }
1408
1409     self = client->frame;
1410
1411     /* when the user clicks in the corners of the titlebar and the client
1412        is fully maximized, then treat it like they clicked in the
1413        button that is there */
1414     if (self->max_horz && self->max_vert &&
1415         (win == self->title || win == self->titletop ||
1416          win == self->titleleft || win == self->titletopleft ||
1417          win == self->titleright || win == self->titletopright))
1418     {
1419         /* get the mouse coords in reference to the whole frame */
1420         gint fx = x;
1421         gint fy = y;
1422
1423         /* these windows are down a border width from the top of the frame */
1424         if (win == self->title ||
1425             win == self->titleleft || win == self->titleright)
1426             fy += self->bwidth;
1427
1428         /* title is a border width in from the edge */
1429         if (win == self->title)
1430             fx += self->bwidth;
1431         /* titletop is a bit to the right */
1432         else if (win == self->titletop)
1433             fx += ob_rr_theme->grip_width + self->bwidth;
1434         /* titletopright is way to the right edge */
1435         else if (win == self->titletopright)
1436             fx += self->area.width - (ob_rr_theme->grip_width + self->bwidth);
1437         /* titleright is even more way to the right edge */
1438         else if (win == self->titleright)
1439             fx += self->area.width - self->bwidth;
1440
1441         /* figure out if we're over the area that should be considered a
1442            button */
1443         if (fy < self->bwidth + ob_rr_theme->paddingy + 1 +
1444             ob_rr_theme->button_size)
1445         {
1446             if (fx < (self->bwidth + ob_rr_theme->paddingx + 1 +
1447                       ob_rr_theme->button_size))
1448             {
1449                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
1450                     return self->leftmost;
1451             }
1452             else if (fx >= (self->area.width -
1453                             (self->bwidth + ob_rr_theme->paddingx + 1 +
1454                              ob_rr_theme->button_size)))
1455             {
1456                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
1457                     return self->rightmost;
1458             }
1459         }
1460
1461         /* there is no resizing maximized windows so make them the titlebar
1462            context */
1463         return OB_FRAME_CONTEXT_TITLEBAR;
1464     }
1465     else if (self->max_vert &&
1466              (win == self->titletop || win == self->topresize))
1467         /* can't resize vertically when max vert */
1468         return OB_FRAME_CONTEXT_TITLEBAR;
1469     else if (self->shaded &&
1470              (win == self->titletop || win == self->topresize))
1471         /* can't resize vertically when shaded */
1472         return OB_FRAME_CONTEXT_TITLEBAR;
1473
1474     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
1475     if (win == self->label)             return OB_FRAME_CONTEXT_TITLEBAR;
1476     if (win == self->handle)            return OB_FRAME_CONTEXT_BOTTOM;
1477     if (win == self->handletop)         return OB_FRAME_CONTEXT_BOTTOM;
1478     if (win == self->handlebottom)      return OB_FRAME_CONTEXT_BOTTOM;
1479     if (win == self->handleleft)        return OB_FRAME_CONTEXT_BLCORNER;
1480     if (win == self->lgrip)             return OB_FRAME_CONTEXT_BLCORNER;
1481     if (win == self->lgripleft)         return OB_FRAME_CONTEXT_BLCORNER;
1482     if (win == self->lgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1483     if (win == self->lgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1484     if (win == self->handleright)       return OB_FRAME_CONTEXT_BRCORNER;
1485     if (win == self->rgrip)             return OB_FRAME_CONTEXT_BRCORNER;
1486     if (win == self->rgripright)        return OB_FRAME_CONTEXT_BRCORNER;
1487     if (win == self->rgriptop)          return OB_FRAME_CONTEXT_BRCORNER;
1488     if (win == self->rgripbottom)       return OB_FRAME_CONTEXT_BRCORNER;
1489     if (win == self->title)             return OB_FRAME_CONTEXT_TITLEBAR;
1490     if (win == self->titlebottom)       return OB_FRAME_CONTEXT_TITLEBAR;
1491     if (win == self->titleleft)         return OB_FRAME_CONTEXT_TLCORNER;
1492     if (win == self->titletopleft)      return OB_FRAME_CONTEXT_TLCORNER;
1493     if (win == self->titleright)        return OB_FRAME_CONTEXT_TRCORNER;
1494     if (win == self->titletopright)     return OB_FRAME_CONTEXT_TRCORNER;
1495     if (win == self->titletop)          return OB_FRAME_CONTEXT_TOP;
1496     if (win == self->topresize)         return OB_FRAME_CONTEXT_TOP;
1497     if (win == self->tltresize)         return OB_FRAME_CONTEXT_TLCORNER;
1498     if (win == self->tllresize)         return OB_FRAME_CONTEXT_TLCORNER;
1499     if (win == self->trtresize)         return OB_FRAME_CONTEXT_TRCORNER;
1500     if (win == self->trrresize)         return OB_FRAME_CONTEXT_TRCORNER;
1501     if (win == self->left)              return OB_FRAME_CONTEXT_LEFT;
1502     if (win == self->right)             return OB_FRAME_CONTEXT_RIGHT;
1503     if (win == self->innertop)          return OB_FRAME_CONTEXT_TITLEBAR;
1504     if (win == self->innerleft)         return OB_FRAME_CONTEXT_LEFT;
1505     if (win == self->innerbottom)       return OB_FRAME_CONTEXT_BOTTOM;
1506     if (win == self->innerright)        return OB_FRAME_CONTEXT_RIGHT;
1507     if (win == self->innerbll)          return OB_FRAME_CONTEXT_BLCORNER;
1508     if (win == self->innerblb)          return OB_FRAME_CONTEXT_BLCORNER;
1509     if (win == self->innerbrr)          return OB_FRAME_CONTEXT_BRCORNER;
1510     if (win == self->innerbrb)          return OB_FRAME_CONTEXT_BRCORNER;
1511     if (win == self->max)               return OB_FRAME_CONTEXT_MAXIMIZE;
1512     if (win == self->iconify)           return OB_FRAME_CONTEXT_ICONIFY;
1513     if (win == self->close)             return OB_FRAME_CONTEXT_CLOSE;
1514     if (win == self->icon)              return OB_FRAME_CONTEXT_ICON;
1515     if (win == self->desk)              return OB_FRAME_CONTEXT_ALLDESKTOPS;
1516     if (win == self->shade)             return OB_FRAME_CONTEXT_SHADE;
1517
1518     return OB_FRAME_CONTEXT_NONE;
1519 }
1520
1521 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
1522 {
1523     /* horizontal */
1524     switch (self->client->gravity) {
1525     default:
1526     case NorthWestGravity:
1527     case SouthWestGravity:
1528     case WestGravity:
1529         break;
1530
1531     case NorthGravity:
1532     case SouthGravity:
1533     case CenterGravity:
1534         /* the middle of the client will be the middle of the frame */
1535         *x -= (self->size.right - self->size.left) / 2;
1536         break;
1537
1538     case NorthEastGravity:
1539     case SouthEastGravity:
1540     case EastGravity:
1541         /* the right side of the client will be the right side of the frame */
1542         *x -= self->size.right + self->size.left -
1543             self->client->border_width * 2;
1544         break;
1545
1546     case ForgetGravity:
1547     case StaticGravity:
1548         /* the client's position won't move */
1549         *x -= self->size.left - self->client->border_width;
1550         break;
1551     }
1552
1553     /* vertical */
1554     switch (self->client->gravity) {
1555     default:
1556     case NorthWestGravity:
1557     case NorthEastGravity:
1558     case NorthGravity:
1559         break;
1560
1561     case CenterGravity:
1562     case EastGravity:
1563     case WestGravity:
1564         /* the middle of the client will be the middle of the frame */
1565         *y -= (self->size.bottom - self->size.top) / 2;
1566         break;
1567
1568     case SouthWestGravity:
1569     case SouthEastGravity:
1570     case SouthGravity:
1571         /* the bottom of the client will be the bottom of the frame */
1572         *y -= self->size.bottom + self->size.top -
1573             self->client->border_width * 2;
1574         break;
1575
1576     case ForgetGravity:
1577     case StaticGravity:
1578         /* the client's position won't move */
1579         *y -= self->size.top - self->client->border_width;
1580         break;
1581     }
1582 }
1583
1584 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
1585 {
1586     /* horizontal */
1587     switch (self->client->gravity) {
1588     default:
1589     case NorthWestGravity:
1590     case WestGravity:
1591     case SouthWestGravity:
1592         break;
1593     case NorthGravity:
1594     case CenterGravity:
1595     case SouthGravity:
1596         /* the middle of the client will be the middle of the frame */
1597         *x += (self->size.right - self->size.left) / 2;
1598         break;
1599     case NorthEastGravity:
1600     case EastGravity:
1601     case SouthEastGravity:
1602         /* the right side of the client will be the right side of the frame */
1603         *x += self->size.right + self->size.left -
1604             self->client->border_width * 2;
1605         break;
1606     case StaticGravity:
1607     case ForgetGravity:
1608         /* the client's position won't move */
1609         *x += self->size.left - self->client->border_width;
1610         break;
1611     }
1612
1613     /* vertical */
1614     switch (self->client->gravity) {
1615     default:
1616     case NorthWestGravity:
1617     case NorthGravity:
1618     case NorthEastGravity:
1619         break;
1620     case WestGravity:
1621     case CenterGravity:
1622     case EastGravity:
1623         /* the middle of the client will be the middle of the frame */
1624         *y += (self->size.bottom - self->size.top) / 2;
1625         break;
1626     case SouthWestGravity:
1627     case SouthGravity:
1628     case SouthEastGravity:
1629         /* the bottom of the client will be the bottom of the frame */
1630         *y += self->size.bottom + self->size.top -
1631             self->client->border_width * 2;
1632         break;
1633     case StaticGravity:
1634     case ForgetGravity:
1635         /* the client's position won't move */
1636         *y += self->size.top - self->client->border_width;
1637         break;
1638     }
1639 }
1640
1641 void frame_rect_to_frame(ObFrame *self, Rect *r)
1642 {
1643     r->width += self->size.left + self->size.right;
1644     r->height += self->size.top + self->size.bottom;
1645     frame_client_gravity(self, &r->x, &r->y);
1646 }
1647
1648 void frame_rect_to_client(ObFrame *self, Rect *r)
1649 {
1650     r->width -= self->size.left + self->size.right;
1651     r->height -= self->size.top + self->size.bottom;
1652     frame_frame_gravity(self, &r->x, &r->y);
1653 }
1654
1655 static void flash_done(gpointer data)
1656 {
1657     ObFrame *self = data;
1658
1659     if (self->focused != self->flash_on)
1660         frame_adjust_focus(self, self->focused);
1661 }
1662
1663 static gboolean flash_timeout(gpointer data)
1664 {
1665     ObFrame *self = data;
1666     GTimeVal now;
1667
1668     g_get_current_time(&now);
1669     if (now.tv_sec > self->flash_end.tv_sec ||
1670         (now.tv_sec == self->flash_end.tv_sec &&
1671          now.tv_usec >= self->flash_end.tv_usec))
1672         self->flashing = FALSE;
1673
1674     if (!self->flashing)
1675         return FALSE; /* we are done */
1676
1677     self->flash_on = !self->flash_on;
1678     if (!self->focused) {
1679         frame_adjust_focus(self, self->flash_on);
1680         self->focused = FALSE;
1681     }
1682
1683     return TRUE; /* go again */
1684 }
1685
1686 void frame_flash_start(ObFrame *self)
1687 {
1688     self->flash_on = self->focused;
1689
1690     if (!self->flashing)
1691         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
1692                                                600, flash_timeout, self,
1693                                                flash_done);
1694     g_get_current_time(&self->flash_end);
1695     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1696
1697     self->flashing = TRUE;
1698 }
1699
1700 void frame_flash_stop(ObFrame *self)
1701 {
1702     self->flashing = FALSE;
1703 }
1704
1705 static gulong frame_animate_iconify_time_left(ObFrame *self,
1706                                               const GTimeVal *now)
1707 {
1708     glong sec, usec;
1709     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1710     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1711     if (usec < 0) {
1712         usec += G_USEC_PER_SEC;
1713         sec--;
1714     }
1715     /* no negative values */
1716     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1717 }
1718
1719 static gboolean frame_animate_iconify(gpointer p)
1720 {
1721     ObFrame *self = p;
1722     gint x, y, w, h;
1723     gint iconx, icony, iconw;
1724     GTimeVal now;
1725     gulong time;
1726     gboolean iconifying;
1727
1728     if (self->client->icon_geometry.width == 0) {
1729         /* there is no icon geometry set so just go straight down */
1730         const Rect *a;
1731
1732         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
1733         iconx = self->area.x + self->area.width / 2 + 32;
1734         icony = a->y + a->width;
1735         iconw = 64;
1736     } else {
1737         iconx = self->client->icon_geometry.x;
1738         icony = self->client->icon_geometry.y;
1739         iconw = self->client->icon_geometry.width;
1740     }
1741
1742     iconifying = self->iconify_animation_going > 0;
1743
1744     /* how far do we have left to go ? */
1745     g_get_current_time(&now);
1746     time = frame_animate_iconify_time_left(self, &now);
1747
1748     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
1749         /* start where the frame is supposed to be */
1750         x = self->area.x;
1751         y = self->area.y;
1752         w = self->area.width;
1753         h = self->area.height;
1754     } else {
1755         /* start at the icon */
1756         x = iconx;
1757         y = icony;
1758         w = iconw;
1759         h = self->size.top; /* just the titlebar */
1760     }
1761
1762     if (time > 0) {
1763         glong dx, dy, dw;
1764         glong elapsed;
1765
1766         dx = self->area.x - iconx;
1767         dy = self->area.y - icony;
1768         dw = self->area.width - self->bwidth * 2 - iconw;
1769          /* if restoring, we move in the opposite direction */
1770         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1771
1772         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1773         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1774         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1775         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1776         h = self->size.top; /* just the titlebar */
1777     }
1778
1779     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
1780     XFlush(obt_display);
1781
1782     if (time == 0)
1783         frame_end_iconify_animation(self);
1784
1785     return time > 0; /* repeat until we're out of time */
1786 }
1787
1788 void frame_end_iconify_animation(ObFrame *self)
1789 {
1790     /* see if there is an animation going */
1791     if (self->iconify_animation_going == 0) return;
1792
1793     if (!self->visible)
1794         XUnmapWindow(obt_display, self->window);
1795     else {
1796         /* Send a ConfigureNotify when the animation is done, this fixes
1797            KDE's pager showing the window in the wrong place.  since the
1798            window is mapped at a different location and is then moved, we
1799            need to send the synthetic configurenotify, since apps may have
1800            read the position when the client mapped, apparently. */
1801         client_reconfigure(self->client, TRUE);
1802     }
1803
1804     /* we're not animating any more ! */
1805     self->iconify_animation_going = 0;
1806
1807     XMoveResizeWindow(obt_display, self->window,
1808                       self->area.x, self->area.y,
1809                       self->area.width, self->area.height);
1810     /* we delay re-rendering until after we're done animating */
1811     framerender_frame(self);
1812     XFlush(obt_display);
1813 }
1814
1815 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1816 {
1817     gulong time;
1818     gboolean new_anim = FALSE;
1819     gboolean set_end = TRUE;
1820     GTimeVal now;
1821
1822     /* if there is no titlebar, just don't animate for now
1823        XXX it would be nice tho.. */
1824     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1825         return;
1826
1827     /* get the current time */
1828     g_get_current_time(&now);
1829
1830     /* get how long until the end */
1831     time = FRAME_ANIMATE_ICONIFY_TIME;
1832     if (self->iconify_animation_going) {
1833         if (!!iconifying != (self->iconify_animation_going > 0)) {
1834             /* animation was already going on in the opposite direction */
1835             time = time - frame_animate_iconify_time_left(self, &now);
1836         } else
1837             /* animation was already going in the same direction */
1838             set_end = FALSE;
1839     } else
1840         new_anim = TRUE;
1841     self->iconify_animation_going = iconifying ? 1 : -1;
1842
1843     /* set the ending time */
1844     if (set_end) {
1845         self->iconify_animation_end.tv_sec = now.tv_sec;
1846         self->iconify_animation_end.tv_usec = now.tv_usec;
1847         g_time_val_add(&self->iconify_animation_end, time);
1848     }
1849
1850     if (new_anim) {
1851         if (self->iconify_animation_timer)
1852             g_source_remove(self->iconify_animation_timer);
1853         self->iconify_animation_timer =
1854             g_timeout_add_full(G_PRIORITY_DEFAULT,
1855                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1856                                frame_animate_iconify, self, NULL);
1857                                
1858
1859         /* do the first step */
1860         frame_animate_iconify(self);
1861
1862         /* show it during the animation even if it is not "visible" */
1863         if (!self->visible)
1864             XMapWindow(obt_display, self->window);
1865     }
1866 }