]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/frame.c
maybe smaller icons look nicer?
[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 "extensions.h"
24 #include "prop.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "mainloop.h"
28 #include "focus_cycle.h"
29 #include "focus_cycle_indicator.h"
30 #include "moveresize.h"
31 #include "screen.h"
32 #include "render/theme.h"
33
34 #define PLATE_EVENTMASK (SubstructureRedirectMask | FocusChangeMask)
35 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
36                          ButtonPressMask | ButtonReleaseMask)
37 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
38                            ButtonMotionMask | PointerMotionMask | \
39                            EnterWindowMask | LeaveWindowMask)
40 /* The inner window does not need enter/leave events.
41    If it does get them, then it needs its own context for enter events
42    because sloppy focus will focus the window when you enter the inner window
43    from the frame. */
44 #define INNER_EVENTMASK (ButtonPressMask)
45
46 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
47 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (G_USEC_PER_SEC / 60) /* 60 Hz */
48
49 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
50                            f->cbwidth_y)
51
52 static void flash_done(gpointer data);
53 static gboolean flash_timeout(gpointer data);
54
55 static void layout_title(ObFrame *self);
56 static void set_theme_statics(ObFrame *self);
57 static void free_theme_statics(ObFrame *self);
58 static gboolean frame_animate_iconify(gpointer self);
59
60 static Window createWindow(Window parent, Visual *visual,
61                            gulong mask, XSetWindowAttributes *attrib)
62 {
63     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
64                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
65                          (visual ? visual : RrVisual(ob_rr_inst)),
66                          mask, attrib);
67                        
68 }
69
70 static Visual *check_32bit_client(ObClient *c)
71 {
72     XWindowAttributes wattrib;
73     Status ret;
74
75     ret = XGetWindowAttributes(ob_display, c->window, &wattrib);
76     g_assert(ret != BadDrawable);
77     g_assert(ret != BadWindow);
78
79     if (wattrib.depth == 32)
80         return wattrib.visual;
81     return NULL;
82 }
83
84 ObFrame *frame_new(ObClient *client)
85 {
86     XSetWindowAttributes attrib;
87     gulong mask;
88     ObFrame *self;
89     Visual *visual;
90
91     self = g_new0(ObFrame, 1);
92     self->client = client;
93
94     visual = check_32bit_client(client);
95
96     /* create the non-visible decor windows */
97
98     mask = CWEventMask;
99     if (visual) {
100         /* client has a 32-bit visual */
101         mask |= CWColormap | CWBackPixel | CWBorderPixel;
102         /* create a colormap with the visual */
103         self->colormap = attrib.colormap =
104             XCreateColormap(ob_display,
105                             RootWindow(ob_display, ob_screen),
106                             visual, AllocNone);
107         attrib.background_pixel = BlackPixel(ob_display, 0);
108         attrib.border_pixel = BlackPixel(ob_display, 0);
109     }
110     attrib.event_mask = FRAME_EVENTMASK;
111     self->window = createWindow(RootWindow(ob_display, ob_screen), visual,
112                                 mask, &attrib);
113
114     attrib.event_mask = INNER_EVENTMASK;
115     self->inner = createWindow(self->window, visual, mask, &attrib);
116
117     mask &= ~CWEventMask;
118     self->plate = createWindow(self->inner, visual, mask, &attrib);
119
120     /* create the visible decor windows */
121
122     mask = CWEventMask;
123     if (visual) {
124         /* client has a 32-bit visual */
125         mask |= CWColormap | CWBackPixel | CWBorderPixel;
126         attrib.colormap = RrColormap(ob_rr_inst);
127     }
128     attrib.event_mask = ELEMENT_EVENTMASK;
129     self->title = createWindow(self->window, NULL, mask, &attrib);
130
131     mask |= CWCursor;
132     attrib.cursor = ob_cursor(OB_CURSOR_NORTH);
133     self->topresize = createWindow(self->title, NULL, mask, &attrib);
134     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
135     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
136     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
137     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
138     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
139     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
140
141     attrib.cursor = ob_cursor(OB_CURSOR_WEST);
142     self->leftresize = createWindow(self->inner, NULL, mask, &attrib);
143     attrib.cursor = ob_cursor(OB_CURSOR_EAST);
144     self->rightresize = createWindow(self->inner, NULL, mask, &attrib);
145
146     mask &= ~CWCursor;
147     self->label = createWindow(self->title, NULL, mask, &attrib);
148     self->max = createWindow(self->title, NULL, mask, &attrib);
149     self->close = createWindow(self->title, NULL, mask, &attrib);
150     self->desk = createWindow(self->title, NULL, mask, &attrib);
151     self->shade = createWindow(self->title, NULL, mask, &attrib);
152     self->icon = createWindow(self->title, NULL, mask, &attrib);
153     self->iconify = createWindow(self->title, NULL, mask, &attrib);
154
155     mask |= CWCursor;
156     attrib.cursor = ob_cursor(OB_CURSOR_SOUTH);
157     self->handle = createWindow(self->window, NULL, mask, &attrib);
158     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
159     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
160     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
161     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
162
163     self->focused = FALSE;
164
165     /* the other stuff is shown based on decor settings */
166     XMapWindow(ob_display, self->plate);
167     XMapWindow(ob_display, self->inner);
168     XMapWindow(ob_display, self->lgrip);
169     XMapWindow(ob_display, self->rgrip);
170     XMapWindow(ob_display, self->label);
171
172     self->max_press = self->close_press = self->desk_press = 
173         self->iconify_press = self->shade_press = FALSE;
174     self->max_hover = self->close_hover = self->desk_hover = 
175         self->iconify_hover = self->shade_hover = FALSE;
176
177     set_theme_statics(self);
178
179     return (ObFrame*)self;
180 }
181
182 static void set_theme_statics(ObFrame *self)
183 {
184     gint handle_height;
185
186     if (ob_rr_theme->handle_height > 0)
187         handle_height = ob_rr_theme->handle_height;
188     else
189         handle_height = 1;
190         
191
192     /* set colors/appearance/sizes for stuff that doesn't change */
193     XResizeWindow(ob_display, self->max,
194                   ob_rr_theme->button_size, ob_rr_theme->button_size);
195     XResizeWindow(ob_display, self->iconify,
196                   ob_rr_theme->button_size, ob_rr_theme->button_size);
197     XResizeWindow(ob_display, self->icon,
198                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
199     XResizeWindow(ob_display, self->close,
200                   ob_rr_theme->button_size, ob_rr_theme->button_size);
201     XResizeWindow(ob_display, self->desk,
202                   ob_rr_theme->button_size, ob_rr_theme->button_size);
203     XResizeWindow(ob_display, self->shade,
204                   ob_rr_theme->button_size, ob_rr_theme->button_size);
205     XResizeWindow(ob_display, self->lgrip,
206                   ob_rr_theme->grip_width, handle_height);
207     XResizeWindow(ob_display, self->rgrip,
208                   ob_rr_theme->grip_width, handle_height);
209     XResizeWindow(ob_display, self->tltresize,
210                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
211     XResizeWindow(ob_display, self->trtresize,
212                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
213     XResizeWindow(ob_display, self->tllresize,
214                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
215     XResizeWindow(ob_display, self->trrresize,
216                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
217
218     /* set up the dynamic appearances */
219     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
220     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
221     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
222     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
223     self->a_unfocused_handle =
224         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
225     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
226     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
227 }
228
229 static void free_theme_statics(ObFrame *self)
230 {
231     RrAppearanceFree(self->a_unfocused_title); 
232     RrAppearanceFree(self->a_focused_title);
233     RrAppearanceFree(self->a_unfocused_label);
234     RrAppearanceFree(self->a_focused_label);
235     RrAppearanceFree(self->a_unfocused_handle);
236     RrAppearanceFree(self->a_focused_handle);
237     RrAppearanceFree(self->a_icon);
238 }
239
240 void frame_free(ObFrame *self)
241 {
242     free_theme_statics(self);
243
244     XDestroyWindow(ob_display, self->window);
245     if (self->colormap)
246         XFreeColormap(ob_display, self->colormap);
247
248     g_free(self);
249 }
250
251 void frame_show(ObFrame *self)
252 {
253     if (!self->visible) {
254         self->visible = TRUE;
255         XMapWindow(ob_display, self->client->window);
256         XMapWindow(ob_display, self->window);
257     }
258 }
259
260 void frame_hide(ObFrame *self)
261 {
262     if (self->visible) {
263         self->visible = FALSE;
264         if (!frame_iconify_animating(self))
265             XUnmapWindow(ob_display, self->window);
266         /* we unmap the client itself so that we can get MapRequest
267            events, and because the ICCCM tells us to! */
268         XUnmapWindow(ob_display, self->client->window);
269         self->client->ignore_unmaps += 1;
270     }
271 }
272
273 void frame_adjust_theme(ObFrame *self)
274 {
275     free_theme_statics(self);
276     set_theme_statics(self);
277 }
278
279 void frame_adjust_shape(ObFrame *self)
280 {
281 #ifdef SHAPE
282     gint num;
283     XRectangle xrect[2];
284
285     if (!self->client->shaped) {
286         /* clear the shape on the frame window */
287         XShapeCombineMask(ob_display, self->window, ShapeBounding,
288                           self->innersize.left,
289                           self->innersize.top,
290                           None, ShapeSet);
291     } else {
292         /* make the frame's shape match the clients */
293         XShapeCombineShape(ob_display, self->window, ShapeBounding,
294                            self->innersize.left,
295                            self->innersize.top,
296                            self->client->window,
297                            ShapeBounding, ShapeSet);
298
299         num = 0;
300         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
301             xrect[0].x = -ob_rr_theme->fbwidth;
302             xrect[0].y = -ob_rr_theme->fbwidth;
303             xrect[0].width = self->width + self->rbwidth * 2;
304             xrect[0].height = ob_rr_theme->title_height +
305                 self->bwidth * 2;
306             ++num;
307         }
308
309         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
310             xrect[1].x = -ob_rr_theme->fbwidth;
311             xrect[1].y = FRAME_HANDLE_Y(self);
312             xrect[1].width = self->width + self->rbwidth * 2;
313             xrect[1].height = ob_rr_theme->handle_height +
314                 self->bwidth * 2;
315             ++num;
316         }
317
318         XShapeCombineRectangles(ob_display, self->window,
319                                 ShapeBounding, 0, 0, xrect, num,
320                                 ShapeUnion, Unsorted);
321     }
322 #endif
323 }
324
325 void frame_adjust_area(ObFrame *self, gboolean moved,
326                        gboolean resized, gboolean fake)
327 {
328     Strut oldsize;
329
330     oldsize = self->size;
331
332     if (resized) {
333         self->decorations = self->client->decorations;
334         self->max_horz = self->client->max_horz;
335
336         if (self->decorations & OB_FRAME_DECOR_BORDER) {
337             self->bwidth = ob_rr_theme->fbwidth;
338             self->cbwidth_x = ob_rr_theme->cbwidthx;
339             self->cbwidth_y = ob_rr_theme->cbwidthy;
340         } else {
341             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
342         }
343         self->rbwidth = self->bwidth;
344
345         if (self->max_horz)
346             self->bwidth = self->cbwidth_x = 0;
347
348         STRUT_SET(self->innersize,
349                   self->cbwidth_x,
350                   self->cbwidth_y,
351                   self->cbwidth_x,
352                   self->cbwidth_y);
353         self->width = self->client->area.width + self->cbwidth_x * 2 -
354             (self->max_horz ? self->rbwidth * 2 : 0);
355         self->width = MAX(self->width, 1); /* no lower than 1 */
356
357         /* set border widths */
358         if (!fake) {
359             XSetWindowBorderWidth(ob_display, self->window, self->bwidth);
360             XSetWindowBorderWidth(ob_display, self->inner, self->bwidth);
361             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
362             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
363             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
364             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
365             XSetWindowBorderWidth(ob_display, self->leftresize, self->bwidth);
366             XSetWindowBorderWidth(ob_display, self->rightresize, self->bwidth);
367         }
368
369         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
370             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
371                 (self->rbwidth - self->bwidth);
372         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
373             ob_rr_theme->handle_height > 0)
374             self->innersize.bottom += ob_rr_theme->handle_height +
375                 self->rbwidth + (self->rbwidth - self->bwidth);
376   
377         /* position/size and map/unmap all the windows */
378
379         if (!fake) {
380             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
381                 XMoveResizeWindow(ob_display, self->title,
382                                   -self->bwidth, -self->bwidth,
383                                   self->width, ob_rr_theme->title_height);
384                 XMapWindow(ob_display, self->title);
385
386                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
387                     XMoveResizeWindow(ob_display, self->topresize,
388                                       ob_rr_theme->grip_width + self->bwidth,
389                                       0,
390                                       self->width - (ob_rr_theme->grip_width +
391                                                      self->bwidth) * 2,
392                                       ob_rr_theme->paddingy + 1);
393
394                     XMoveWindow(ob_display, self->tltresize, 0, 0);
395                     XMoveWindow(ob_display, self->tllresize, 0, 0);
396                     XMoveWindow(ob_display, self->trtresize,
397                                 self->width - ob_rr_theme->grip_width, 0);
398                     XMoveWindow(ob_display, self->trrresize,
399                                 self->width - ob_rr_theme->paddingx - 1, 0);
400
401                     XMapWindow(ob_display, self->topresize);
402                     XMapWindow(ob_display, self->tltresize);
403                     XMapWindow(ob_display, self->tllresize);
404                     XMapWindow(ob_display, self->trtresize);
405                     XMapWindow(ob_display, self->trrresize);
406                 } else {
407                     XUnmapWindow(ob_display, self->topresize);
408                     XUnmapWindow(ob_display, self->tltresize);
409                     XUnmapWindow(ob_display, self->tllresize);
410                     XUnmapWindow(ob_display, self->trtresize);
411                     XUnmapWindow(ob_display, self->trrresize);
412                 }
413             } else
414                 XUnmapWindow(ob_display, self->title);
415         }
416
417         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
418             /* layout the title bar elements */
419             layout_title(self);
420
421         if (!fake) {
422             if (self->decorations & OB_FRAME_DECOR_HANDLE)
423             {
424                 gint handle_height;
425
426                 if (ob_rr_theme->handle_height > 0)
427                     handle_height = ob_rr_theme->handle_height;
428                 else
429                     handle_height = 1;
430
431                 XMoveResizeWindow(ob_display, self->handle,
432                                   -self->bwidth, FRAME_HANDLE_Y(self),
433                                   self->width, handle_height);
434                 XMapWindow(ob_display, self->handle);
435
436                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
437                     XMoveWindow(ob_display, self->lgrip,
438                                 -self->rbwidth, -self->rbwidth);
439                     XMoveWindow(ob_display, self->rgrip,
440                                 -self->rbwidth + self->width -
441                                 ob_rr_theme->grip_width, -self->rbwidth);
442                     XMapWindow(ob_display, self->lgrip);
443                     XMapWindow(ob_display, self->rgrip);
444                 } else {
445                     XUnmapWindow(ob_display, self->lgrip);
446                     XUnmapWindow(ob_display, self->rgrip);
447                 }
448             } else
449                 XUnmapWindow(ob_display, self->handle);
450
451             if (self->decorations & OB_FRAME_DECOR_GRIPS) {
452                 XMoveResizeWindow(ob_display, self->leftresize,
453                                   -(ob_rr_theme->fbwidth * 2) - 1,
454                                   0,
455                                   1,
456                                   self->client->area.height +
457                                   self->cbwidth_y * 2);
458                 XMoveResizeWindow(ob_display, self->rightresize,
459                                   self->client->area.width +
460                                   self->cbwidth_x * 2,
461                                   0,
462                                   1,
463                                   self->client->area.height +
464                                   self->cbwidth_y * 2);
465
466                 XMapWindow(ob_display, self->leftresize);
467                 XMapWindow(ob_display, self->rightresize);
468             } else {
469                 XUnmapWindow(ob_display, self->leftresize);
470                 XUnmapWindow(ob_display, self->rightresize);
471             }
472
473             /* move and resize the inner border window which contains the plate
474              */
475             XMoveResizeWindow(ob_display, self->inner,
476                               self->innersize.left - self->cbwidth_x -
477                               self->bwidth,
478                               self->innersize.top - self->cbwidth_y -
479                               self->bwidth,
480                               self->client->area.width +
481                               self->cbwidth_x * 2,
482                               self->client->area.height +
483                               self->cbwidth_y * 2);
484
485             /* move the plate */
486             XMoveWindow(ob_display, self->plate,
487                         self->cbwidth_x, self->cbwidth_y);
488
489             /* when the client has StaticGravity, it likes to move around. */
490             XMoveWindow(ob_display, self->client->window, 0, 0);
491         }
492
493         STRUT_SET(self->size,
494                   self->innersize.left + self->bwidth,
495                   self->innersize.top + self->bwidth,
496                   self->innersize.right + self->bwidth,
497                   self->innersize.bottom + self->bwidth);
498     }
499
500     /* shading can change without being moved or resized */
501     RECT_SET_SIZE(self->area,
502                   self->client->area.width +
503                   self->size.left + self->size.right,
504                   (self->client->shaded ?
505                    ob_rr_theme->title_height + self->rbwidth * 2:
506                    self->client->area.height +
507                    self->size.top + self->size.bottom));
508
509     if (moved || resized) {
510         /* find the new coordinates, done after setting the frame.size, for
511            frame_client_gravity. */
512         self->area.x = self->client->area.x;
513         self->area.y = self->client->area.y;
514         frame_client_gravity(self, &self->area.x, &self->area.y,
515                              self->client->area.width,
516                              self->client->area.height);
517     }
518
519     if (!fake) {
520         if (!frame_iconify_animating(self))
521             /* move and resize the top level frame.
522                shading can change without being moved or resized.
523                
524                but don't do this during an iconify animation. it will be
525                reflected afterwards.
526             */
527             XMoveResizeWindow(ob_display, self->window,
528                               self->area.x, self->area.y,
529                               self->area.width - self->bwidth * 2,
530                               self->area.height - self->bwidth * 2);
531
532         if (resized) {
533             framerender_frame(self);
534             frame_adjust_shape(self);
535         }
536
537         if (!STRUT_EQUAL(self->size, oldsize)) {
538             gulong vals[4];
539             vals[0] = self->size.left;
540             vals[1] = self->size.right;
541             vals[2] = self->size.top;
542             vals[3] = self->size.bottom;
543             PROP_SETA32(self->client->window, net_frame_extents,
544                         cardinal, vals, 4);
545             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
546                         cardinal, vals, 4);
547         }
548
549         /* if this occurs while we are focus cycling, the indicator needs to
550            match the changes */
551         if (focus_cycle_target == self->client)
552             focus_cycle_draw_indicator(self->client);
553     }
554     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
555         XResizeWindow(ob_display, self->label, self->label_width,
556                       ob_rr_theme->label_height);
557 }
558
559 void frame_adjust_client_area(ObFrame *self)
560 {
561     /* resize the plate */
562     XResizeWindow(ob_display, self->plate,
563                   self->client->area.width, self->client->area.height);
564 }
565
566 void frame_adjust_state(ObFrame *self)
567 {
568     framerender_frame(self);
569 }
570
571 void frame_adjust_focus(ObFrame *self, gboolean hilite)
572 {
573     self->focused = hilite;
574     framerender_frame(self);
575     XFlush(ob_display);
576 }
577
578 void frame_adjust_title(ObFrame *self)
579 {
580     framerender_frame(self);
581 }
582
583 void frame_adjust_icon(ObFrame *self)
584 {
585     framerender_frame(self);
586 }
587
588 void frame_grab_client(ObFrame *self)
589 {
590     /* reparent the client to the frame */
591     XReparentWindow(ob_display, self->client->window, self->plate, 0, 0);
592
593     /*
594       When reparenting the client window, it is usually not mapped yet, since
595       this occurs from a MapRequest. However, in the case where Openbox is
596       starting up, the window is already mapped, so we'll see unmap events for
597       it. There are 2 unmap events generated that we see, one with the 'event'
598       member set the root window, and one set to the client, but both get
599       handled and need to be ignored.
600     */
601     if (ob_state() == OB_STATE_STARTING)
602         self->client->ignore_unmaps += 2;
603
604     /* select the event mask on the client's parent (to receive config/map
605        req's) the ButtonPress is to catch clicks on the client border */
606     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
607
608     /* map the client so it maps when the frame does */
609     XMapWindow(ob_display, self->client->window);
610
611     /* set all the windows for the frame in the window_map */
612     g_hash_table_insert(window_map, &self->window, self->client);
613     g_hash_table_insert(window_map, &self->plate, self->client);
614     g_hash_table_insert(window_map, &self->inner, self->client);
615     g_hash_table_insert(window_map, &self->title, self->client);
616     g_hash_table_insert(window_map, &self->label, self->client);
617     g_hash_table_insert(window_map, &self->max, self->client);
618     g_hash_table_insert(window_map, &self->close, self->client);
619     g_hash_table_insert(window_map, &self->desk, self->client);
620     g_hash_table_insert(window_map, &self->shade, self->client);
621     g_hash_table_insert(window_map, &self->icon, self->client);
622     g_hash_table_insert(window_map, &self->iconify, self->client);
623     g_hash_table_insert(window_map, &self->handle, self->client);
624     g_hash_table_insert(window_map, &self->lgrip, self->client);
625     g_hash_table_insert(window_map, &self->rgrip, self->client);
626     g_hash_table_insert(window_map, &self->topresize, self->client);
627     g_hash_table_insert(window_map, &self->tltresize, self->client);
628     g_hash_table_insert(window_map, &self->tllresize, self->client);
629     g_hash_table_insert(window_map, &self->trtresize, self->client);
630     g_hash_table_insert(window_map, &self->trrresize, self->client);
631     g_hash_table_insert(window_map, &self->leftresize, self->client);
632     g_hash_table_insert(window_map, &self->rightresize, self->client);
633 }
634
635 void frame_release_client(ObFrame *self)
636 {
637     XEvent ev;
638     gboolean reparent = TRUE;
639
640     /* if there was any animation going on, kill it */
641     ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
642                                      self, FALSE);
643
644     /* check if the app has already reparented its window away */
645     while (XCheckTypedWindowEvent(ob_display, self->client->window,
646                                   ReparentNotify, &ev))
647     {
648         /* This check makes sure we don't catch our own reparent action to
649            our frame window. This doesn't count as the app reparenting itself
650            away of course.
651
652            Reparent events that are generated by us are just discarded here.
653            They are of no consequence to us anyhow.
654         */
655         if (ev.xreparent.parent != self->plate) {
656             reparent = FALSE;
657             XPutBackEvent(ob_display, &ev);
658             break;
659         }
660     }
661
662     if (reparent) {
663         /* according to the ICCCM - if the client doesn't reparent itself,
664            then we will reparent the window to root for them */
665         XReparentWindow(ob_display, self->client->window,
666                         RootWindow(ob_display, ob_screen),
667                         self->client->area.x,
668                         self->client->area.y);
669     }
670
671     /* remove all the windows for the frame from the window_map */
672     g_hash_table_remove(window_map, &self->window);
673     g_hash_table_remove(window_map, &self->plate);
674     g_hash_table_remove(window_map, &self->inner);
675     g_hash_table_remove(window_map, &self->title);
676     g_hash_table_remove(window_map, &self->label);
677     g_hash_table_remove(window_map, &self->max);
678     g_hash_table_remove(window_map, &self->close);
679     g_hash_table_remove(window_map, &self->desk);
680     g_hash_table_remove(window_map, &self->shade);
681     g_hash_table_remove(window_map, &self->icon);
682     g_hash_table_remove(window_map, &self->iconify);
683     g_hash_table_remove(window_map, &self->handle);
684     g_hash_table_remove(window_map, &self->lgrip);
685     g_hash_table_remove(window_map, &self->rgrip);
686     g_hash_table_remove(window_map, &self->topresize);
687     g_hash_table_remove(window_map, &self->tltresize);
688     g_hash_table_remove(window_map, &self->tllresize);
689     g_hash_table_remove(window_map, &self->trtresize);
690     g_hash_table_remove(window_map, &self->trrresize);
691     g_hash_table_remove(window_map, &self->leftresize);
692     g_hash_table_remove(window_map, &self->rightresize);
693
694     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
695 }
696
697 /* is there anything present between us and the label? */
698 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
699     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
700         if (*lc == ' ') continue; /* it was invalid */
701         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
702             return TRUE;
703         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
704             return TRUE;
705         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
706             return TRUE;
707         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
708             return TRUE;
709         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
710             return TRUE;
711         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
712             return TRUE;
713         if (*lc == 'L') return FALSE;
714     }
715     return FALSE;
716 }
717
718 static void layout_title(ObFrame *self)
719 {
720     gchar *lc;
721     gint i;
722
723     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
724     /* position of the left most button */
725     const gint left = ob_rr_theme->paddingx + 1;
726     /* position of the right most button */
727     const gint right = self->width - bwidth;
728
729     /* turn them all off */
730     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
731         self->max_on = self->close_on = self->label_on = FALSE;
732     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
733     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
734
735     /* figure out what's being show, find each element's position, and the
736        width of the label
737
738        do the ones before the label, then after the label,
739        i will be +1 the first time through when working to the left,
740        and -1 the second time through when working to the right */
741     for (i = 1; i >= -1; i-=2) {
742         gint x;
743         ObFrameContext *firstcon;
744
745         if (i > 0) {
746             x = left;
747             lc = config_title_layout;
748             firstcon = &self->leftmost;
749         } else {
750             x = right;
751             lc = config_title_layout + strlen(config_title_layout)-1;
752             firstcon = &self->rightmost;
753         }
754
755         /* stop at the end of the string (or the label, which calls break) */
756         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
757             if (*lc == 'L') {
758                 if (i > 0) {
759                     self->label_on = TRUE;
760                     self->label_x = x;
761                 }
762                 break; /* break the for loop, do other side of label */
763             } else if (*lc == 'N') {
764                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
765                 if ((self->icon_on = is_button_present(self, lc, i))) {
766                     /* icon is bigger than buttons */
767                     self->label_width -= bwidth + 2;
768                     self->icon_x = x;
769                     x += i * (bwidth + 2);
770                 }
771             } else if (*lc == 'D') {
772                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
773                 if ((self->desk_on = is_button_present(self, lc, i))) {
774                     self->label_width -= bwidth;
775                     self->desk_x = x;
776                     x += i * bwidth;
777                 }
778             } else if (*lc == 'S') {
779                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
780                 if ((self->shade_on = is_button_present(self, lc, i))) {
781                     self->label_width -= bwidth;
782                     self->shade_x = x;
783                     x += i * bwidth;
784                 }
785             } else if (*lc == 'I') {
786                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
787                 if ((self->iconify_on = is_button_present(self, lc, i))) {
788                     self->label_width -= bwidth;
789                     self->iconify_x = x;
790                     x += i * bwidth;
791                 }
792             } else if (*lc == 'M') {
793                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
794                 if ((self->max_on = is_button_present(self, lc, i))) {
795                     self->label_width -= bwidth;
796                     self->max_x = x;
797                     x += i * bwidth;
798                 }
799             } else if (*lc == 'C') {
800                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
801                 if ((self->close_on = is_button_present(self, lc, i))) {
802                     self->label_width -= bwidth;
803                     self->close_x = x;
804                     x += i * bwidth;
805                 }
806             } else
807                 continue; /* don't set firstcon */
808             firstcon = NULL;
809         }
810     }
811
812     /* position and map the elements */
813     if (self->icon_on) {
814         XMapWindow(ob_display, self->icon);
815         XMoveWindow(ob_display, self->icon, self->icon_x,
816                     ob_rr_theme->paddingy);
817     } else
818         XUnmapWindow(ob_display, self->icon);
819
820     if (self->desk_on) {
821         XMapWindow(ob_display, self->desk);
822         XMoveWindow(ob_display, self->desk, self->desk_x,
823                     ob_rr_theme->paddingy + 1);
824     } else
825         XUnmapWindow(ob_display, self->desk);
826
827     if (self->shade_on) {
828         XMapWindow(ob_display, self->shade);
829         XMoveWindow(ob_display, self->shade, self->shade_x,
830                     ob_rr_theme->paddingy + 1);
831     } else
832         XUnmapWindow(ob_display, self->shade);
833
834     if (self->iconify_on) {
835         XMapWindow(ob_display, self->iconify);
836         XMoveWindow(ob_display, self->iconify, self->iconify_x,
837                     ob_rr_theme->paddingy + 1);
838     } else
839         XUnmapWindow(ob_display, self->iconify);
840
841     if (self->max_on) {
842         XMapWindow(ob_display, self->max);
843         XMoveWindow(ob_display, self->max, self->max_x,
844                     ob_rr_theme->paddingy + 1);
845     } else
846         XUnmapWindow(ob_display, self->max);
847
848     if (self->close_on) {
849         XMapWindow(ob_display, self->close);
850         XMoveWindow(ob_display, self->close, self->close_x,
851                     ob_rr_theme->paddingy + 1);
852     } else
853         XUnmapWindow(ob_display, self->close);
854
855     if (self->label_on) {
856         self->label_width = MAX(1, self->label_width); /* no lower than 1 */
857         XMapWindow(ob_display, self->label);
858         XMoveWindow(ob_display, self->label, self->label_x,
859                     ob_rr_theme->paddingy);
860     } else
861         XUnmapWindow(ob_display, self->label);
862 }
863
864 ObFrameContext frame_context_from_string(const gchar *name)
865 {
866     if (!g_ascii_strcasecmp("Desktop", name))
867         return OB_FRAME_CONTEXT_DESKTOP;
868     else if (!g_ascii_strcasecmp("Root", name))
869         return OB_FRAME_CONTEXT_ROOT;
870     else if (!g_ascii_strcasecmp("Client", name))
871         return OB_FRAME_CONTEXT_CLIENT;
872     else if (!g_ascii_strcasecmp("Titlebar", name))
873         return OB_FRAME_CONTEXT_TITLEBAR;
874     else if (!g_ascii_strcasecmp("Frame", name))
875         return OB_FRAME_CONTEXT_FRAME;
876     else if (!g_ascii_strcasecmp("TLCorner", name))
877         return OB_FRAME_CONTEXT_TLCORNER;
878     else if (!g_ascii_strcasecmp("TRCorner", name))
879         return OB_FRAME_CONTEXT_TRCORNER;
880     else if (!g_ascii_strcasecmp("BLCorner", name))
881         return OB_FRAME_CONTEXT_BLCORNER;
882     else if (!g_ascii_strcasecmp("BRCorner", name))
883         return OB_FRAME_CONTEXT_BRCORNER;
884     else if (!g_ascii_strcasecmp("Top", name))
885         return OB_FRAME_CONTEXT_TOP;
886     else if (!g_ascii_strcasecmp("Bottom", name))
887         return OB_FRAME_CONTEXT_BOTTOM;
888     else if (!g_ascii_strcasecmp("Left", name))
889         return OB_FRAME_CONTEXT_LEFT;
890     else if (!g_ascii_strcasecmp("Right", name))
891         return OB_FRAME_CONTEXT_RIGHT;
892     else if (!g_ascii_strcasecmp("Maximize", name))
893         return OB_FRAME_CONTEXT_MAXIMIZE;
894     else if (!g_ascii_strcasecmp("AllDesktops", name))
895         return OB_FRAME_CONTEXT_ALLDESKTOPS;
896     else if (!g_ascii_strcasecmp("Shade", name))
897         return OB_FRAME_CONTEXT_SHADE;
898     else if (!g_ascii_strcasecmp("Iconify", name))
899         return OB_FRAME_CONTEXT_ICONIFY;
900     else if (!g_ascii_strcasecmp("Icon", name))
901         return OB_FRAME_CONTEXT_ICON;
902     else if (!g_ascii_strcasecmp("Close", name))
903         return OB_FRAME_CONTEXT_CLOSE;
904     else if (!g_ascii_strcasecmp("MoveResize", name))
905         return OB_FRAME_CONTEXT_MOVE_RESIZE;
906     return OB_FRAME_CONTEXT_NONE;
907 }
908
909 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
910 {
911     ObFrame *self;
912
913     if (moveresize_in_progress)
914         return OB_FRAME_CONTEXT_MOVE_RESIZE;
915
916     if (win == RootWindow(ob_display, ob_screen))
917         return OB_FRAME_CONTEXT_ROOT ;
918     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
919     if (win == client->window) {
920         /* conceptually, this is the desktop, as far as users are
921            concerned */
922         if (client->type == OB_CLIENT_TYPE_DESKTOP)
923             return OB_FRAME_CONTEXT_DESKTOP;
924         return OB_FRAME_CONTEXT_CLIENT;
925     }
926
927     self = client->frame;
928     if (win == self->inner || win == self->plate) {
929         /* conceptually, this is the desktop, as far as users are
930            concerned */
931         if (client->type == OB_CLIENT_TYPE_DESKTOP)
932             return OB_FRAME_CONTEXT_DESKTOP;
933         return OB_FRAME_CONTEXT_CLIENT;
934     }
935
936     if (win == self->title) {
937         /* when the user clicks in the corners of the titlebar and the client
938            is fully maximized, then treat it like they clicked in the
939            button that is there */
940         if (self->client->max_horz && self->client->max_vert &&
941             y < ob_rr_theme->paddingy + 1 + ob_rr_theme->button_size)
942         {
943             if (x < ((ob_rr_theme->paddingx + 1) * 2 +
944                      ob_rr_theme->button_size)) {
945                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
946                     return self->leftmost;
947             }
948             else if (x > (self->width -
949                           (ob_rr_theme->paddingx + 1 +
950                            ob_rr_theme->button_size)))
951             {
952                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
953                     return self->rightmost;
954             }
955         }
956         return OB_FRAME_CONTEXT_TITLEBAR;
957     }
958
959     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
960     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
961     if (win == self->handle)    return OB_FRAME_CONTEXT_BOTTOM;
962     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
963     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
964     if (win == self->topresize) return OB_FRAME_CONTEXT_TOP;
965     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
966     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
967     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
968     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
969     if (win == self->leftresize) return OB_FRAME_CONTEXT_LEFT;
970     if (win == self->rightresize) return OB_FRAME_CONTEXT_RIGHT;
971     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
972     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
973     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
974     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
975     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
976     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
977
978     return OB_FRAME_CONTEXT_NONE;
979 }
980
981 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
982 {
983     /* horizontal */
984     switch (self->client->gravity) {
985     default:
986     case NorthWestGravity:
987     case SouthWestGravity:
988     case WestGravity:
989         break;
990
991     case NorthGravity:
992     case SouthGravity:
993     case CenterGravity:
994         *x -= (self->size.left + w) / 2;
995         break;
996
997     case NorthEastGravity:
998     case SouthEastGravity:
999     case EastGravity:
1000         *x -= (self->size.left + self->size.right + w) - 1;
1001         break;
1002
1003     case ForgetGravity:
1004     case StaticGravity:
1005         *x -= self->size.left;
1006         break;
1007     }
1008
1009     /* vertical */
1010     switch (self->client->gravity) {
1011     default:
1012     case NorthWestGravity:
1013     case NorthEastGravity:
1014     case NorthGravity:
1015         break;
1016
1017     case CenterGravity:
1018     case EastGravity:
1019     case WestGravity:
1020         *y -= (self->size.top + h) / 2;
1021         break;
1022
1023     case SouthWestGravity:
1024     case SouthEastGravity:
1025     case SouthGravity:
1026         *y -= (self->size.top + self->size.bottom + h) - 1;
1027         break;
1028
1029     case ForgetGravity:
1030     case StaticGravity:
1031         *y -= self->size.top;
1032         break;
1033     }
1034 }
1035
1036 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
1037 {
1038     /* horizontal */
1039     switch (self->client->gravity) {
1040     default:
1041     case NorthWestGravity:
1042     case WestGravity:
1043     case SouthWestGravity:
1044         break;
1045     case NorthGravity:
1046     case CenterGravity:
1047     case SouthGravity:
1048         *x += (self->size.left + w) / 2;
1049         break;
1050     case NorthEastGravity:
1051     case EastGravity:
1052     case SouthEastGravity:
1053         *x += (self->size.left + self->size.right + w) - 1;
1054         break;
1055     case StaticGravity:
1056     case ForgetGravity:
1057         *x += self->size.left;
1058         break;
1059     }
1060
1061     /* vertical */
1062     switch (self->client->gravity) {
1063     default:
1064     case NorthWestGravity:
1065     case NorthGravity:
1066     case NorthEastGravity:
1067         break;
1068     case WestGravity:
1069     case CenterGravity:
1070     case EastGravity:
1071         *y += (self->size.top + h) / 2;
1072         break;
1073     case SouthWestGravity:
1074     case SouthGravity:
1075     case SouthEastGravity:
1076         *y += (self->size.top + self->size.bottom + h) - 1;
1077         break;
1078     case StaticGravity:
1079     case ForgetGravity:
1080         *y += self->size.top;
1081         break;
1082     }
1083 }
1084
1085 static void flash_done(gpointer data)
1086 {
1087     ObFrame *self = data;
1088
1089     if (self->focused != self->flash_on)
1090         frame_adjust_focus(self, self->focused);
1091 }
1092
1093 static gboolean flash_timeout(gpointer data)
1094 {
1095     ObFrame *self = data;
1096     GTimeVal now;
1097
1098     g_get_current_time(&now);
1099     if (now.tv_sec > self->flash_end.tv_sec ||
1100         (now.tv_sec == self->flash_end.tv_sec &&
1101          now.tv_usec >= self->flash_end.tv_usec))
1102         self->flashing = FALSE;
1103
1104     if (!self->flashing)
1105         return FALSE; /* we are done */
1106
1107     self->flash_on = !self->flash_on;
1108     if (!self->focused) {
1109         frame_adjust_focus(self, self->flash_on);
1110         self->focused = FALSE;
1111     }
1112
1113     return TRUE; /* go again */
1114 }
1115
1116 void frame_flash_start(ObFrame *self)
1117 {
1118     self->flash_on = self->focused;
1119
1120     if (!self->flashing)
1121         ob_main_loop_timeout_add(ob_main_loop,
1122                                  G_USEC_PER_SEC * 0.6,
1123                                  flash_timeout,
1124                                  self,
1125                                  g_direct_equal,
1126                                  flash_done);
1127     g_get_current_time(&self->flash_end);
1128     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1129     
1130     self->flashing = TRUE;
1131 }
1132
1133 void frame_flash_stop(ObFrame *self)
1134 {
1135     self->flashing = FALSE;
1136 }
1137
1138 static gulong frame_animate_iconify_time_left(ObFrame *self,
1139                                               const GTimeVal *now)
1140 {
1141     glong sec, usec;
1142     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1143     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1144     if (usec < 0) {
1145         usec += G_USEC_PER_SEC;
1146         sec--;
1147     }
1148     /* no negative values */
1149     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1150 }
1151
1152 static gboolean frame_animate_iconify(gpointer p)
1153 {
1154     ObFrame *self = p;
1155     gint x, y, w, h;
1156     gint iconx, icony, iconw;
1157     GTimeVal now;
1158     gulong time;
1159     gboolean iconifying;
1160
1161     if (self->client->icon_geometry.width == 0) {
1162         /* there is no icon geometry set so just go straight down */
1163         Rect *a = screen_physical_area();
1164         iconx = self->area.x + self->area.width / 2 + 32;
1165         icony = a->y + a->width;
1166         iconw = 64;
1167     } else {
1168         iconx = self->client->icon_geometry.x;
1169         icony = self->client->icon_geometry.y;
1170         iconw = self->client->icon_geometry.width;
1171     }
1172
1173     iconifying = self->iconify_animation_going > 0;
1174
1175     /* how far do we have left to go ? */
1176     g_get_current_time(&now);
1177     time = frame_animate_iconify_time_left(self, &now);
1178     
1179     if (time == 0 || iconifying) {
1180         /* start where the frame is supposed to be */
1181         x = self->area.x;
1182         y = self->area.y;
1183         w = self->area.width - self->bwidth * 2;
1184         h = self->area.height - self->bwidth * 2;
1185     } else {
1186         /* start at the icon */
1187         x = iconx;
1188         y = icony;
1189         w = iconw;
1190         h = self->innersize.top; /* just the titlebar */
1191     }
1192
1193     if (time > 0) {
1194         glong dx, dy, dw;
1195         glong elapsed;
1196
1197         dx = self->area.x - iconx;
1198         dy = self->area.y - icony;
1199         dw = self->area.width - self->bwidth * 2 - iconw;
1200          /* if restoring, we move in the opposite direction */
1201         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1202
1203         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1204         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1205         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1206         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1207         h = self->innersize.top; /* just the titlebar */
1208     }
1209
1210     if (time == 0)
1211         frame_end_iconify_animation(self);
1212     else {
1213         XMoveResizeWindow(ob_display, self->window, x, y, w, h);
1214         XFlush(ob_display);
1215     }
1216
1217     return time > 0; /* repeat until we're out of time */
1218 }
1219
1220 void frame_end_iconify_animation(ObFrame *self)
1221 {
1222     /* see if there is an animation going */
1223     if (self->iconify_animation_going == 0) return;
1224
1225     if (!self->visible)
1226         XUnmapWindow(ob_display, self->window);
1227     else
1228         /* Send a ConfigureNotify when the animation is done, this fixes
1229            KDE's pager showing the window in the wrong place. */
1230         client_reconfigure(self->client);
1231
1232     /* we're not animating any more ! */
1233     self->iconify_animation_going = 0;
1234
1235     XMoveResizeWindow(ob_display, self->window,
1236                       self->area.x, self->area.y,
1237                       self->area.width - self->bwidth * 2,
1238                       self->area.height - self->bwidth * 2);
1239     XFlush(ob_display);
1240 }
1241
1242 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1243 {
1244     gulong time;
1245     gboolean new_anim = FALSE;
1246     gboolean set_end = TRUE;
1247     GTimeVal now;
1248
1249     /* if there is no titlebar, just don't animate for now
1250        XXX it would be nice tho.. */
1251     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1252         return;
1253
1254     /* get the current time */
1255     g_get_current_time(&now);
1256
1257     /* get how long until the end */
1258     time = FRAME_ANIMATE_ICONIFY_TIME;
1259     if (self->iconify_animation_going) {
1260         if (!!iconifying != (self->iconify_animation_going > 0)) {
1261             /* animation was already going on in the opposite direction */
1262             time = time - frame_animate_iconify_time_left(self, &now);
1263         } else
1264             /* animation was already going in the same direction */
1265             set_end = FALSE;
1266     } else
1267         new_anim = TRUE;
1268     self->iconify_animation_going = iconifying ? 1 : -1;
1269
1270     /* set the ending time */
1271     if (set_end) {
1272         self->iconify_animation_end.tv_sec = now.tv_sec;
1273         self->iconify_animation_end.tv_usec = now.tv_usec;
1274         g_time_val_add(&self->iconify_animation_end, time);
1275     }
1276
1277     if (new_anim) {
1278         ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
1279                                          self, FALSE);
1280         ob_main_loop_timeout_add(ob_main_loop,
1281                                  FRAME_ANIMATE_ICONIFY_STEP_TIME,
1282                                  frame_animate_iconify, self,
1283                                  g_direct_equal, NULL);
1284
1285         /* do the first step */
1286         frame_animate_iconify(self);
1287
1288         /* show it during the animation even if it is not "visible" */
1289         if (!self->visible)
1290             XMapWindow(ob_display, self->window);
1291     }
1292 }