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