add Left and Right contexts for resizing
[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                     XMoveResizeWindow(ob_display, self->leftresize,
402                                       -(ob_rr_theme->fbwidth * 2) - 1,
403                                       0,
404                                       1,
405                                       self->client->area.height +
406                                       self->cbwidth_y * 2);
407                     XMoveResizeWindow(ob_display, self->rightresize,
408                                       self->client->area.width +
409                                       self->cbwidth_x * 2,
410                                       0,
411                                       1,
412                                       self->client->area.height +
413                                       self->cbwidth_y * 2);
414
415                     XMapWindow(ob_display, self->topresize);
416                     XMapWindow(ob_display, self->tltresize);
417                     XMapWindow(ob_display, self->tllresize);
418                     XMapWindow(ob_display, self->trtresize);
419                     XMapWindow(ob_display, self->trrresize);
420                     XMapWindow(ob_display, self->leftresize);
421                     XMapWindow(ob_display, self->rightresize);
422                 } else {
423                     XUnmapWindow(ob_display, self->topresize);
424                     XUnmapWindow(ob_display, self->tltresize);
425                     XUnmapWindow(ob_display, self->tllresize);
426                     XUnmapWindow(ob_display, self->trtresize);
427                     XUnmapWindow(ob_display, self->trrresize);
428                     XUnmapWindow(ob_display, self->leftresize);
429                     XUnmapWindow(ob_display, self->rightresize);
430                 }
431             } else
432                 XUnmapWindow(ob_display, self->title);
433         }
434
435         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
436             /* layout the title bar elements */
437             layout_title(self);
438
439         if (!fake) {
440             if (self->decorations & OB_FRAME_DECOR_HANDLE)
441             {
442                 gint handle_height;
443
444                 if (ob_rr_theme->handle_height > 0)
445                     handle_height = ob_rr_theme->handle_height;
446                 else
447                     handle_height = 1;
448
449                 XMoveResizeWindow(ob_display, self->handle,
450                                   -self->bwidth, FRAME_HANDLE_Y(self),
451                                   self->width, handle_height);
452                 XMapWindow(ob_display, self->handle);
453
454                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
455                     XMoveWindow(ob_display, self->lgrip,
456                                 -self->rbwidth, -self->rbwidth);
457                     XMoveWindow(ob_display, self->rgrip,
458                                 -self->rbwidth + self->width -
459                                 ob_rr_theme->grip_width, -self->rbwidth);
460                     XMapWindow(ob_display, self->lgrip);
461                     XMapWindow(ob_display, self->rgrip);
462                 } else {
463                     XUnmapWindow(ob_display, self->lgrip);
464                     XUnmapWindow(ob_display, self->rgrip);
465                 }
466             } else
467                 XUnmapWindow(ob_display, self->handle);
468
469             /* move and resize the inner border window which contains the plate
470              */
471             XMoveResizeWindow(ob_display, self->inner,
472                               self->innersize.left - self->cbwidth_x -
473                               self->bwidth,
474                               self->innersize.top - self->cbwidth_y -
475                               self->bwidth,
476                               self->client->area.width +
477                               self->cbwidth_x * 2,
478                               self->client->area.height +
479                               self->cbwidth_y * 2);
480
481             /* move the plate */
482             XMoveWindow(ob_display, self->plate,
483                         self->cbwidth_x, self->cbwidth_y);
484
485             /* when the client has StaticGravity, it likes to move around. */
486             XMoveWindow(ob_display, self->client->window, 0, 0);
487         }
488
489         STRUT_SET(self->size,
490                   self->innersize.left + self->bwidth,
491                   self->innersize.top + self->bwidth,
492                   self->innersize.right + self->bwidth,
493                   self->innersize.bottom + self->bwidth);
494     }
495
496     /* shading can change without being moved or resized */
497     RECT_SET_SIZE(self->area,
498                   self->client->area.width +
499                   self->size.left + self->size.right,
500                   (self->client->shaded ?
501                    ob_rr_theme->title_height + self->rbwidth * 2:
502                    self->client->area.height +
503                    self->size.top + self->size.bottom));
504
505     if (moved || resized) {
506         /* find the new coordinates, done after setting the frame.size, for
507            frame_client_gravity. */
508         self->area.x = self->client->area.x;
509         self->area.y = self->client->area.y;
510         frame_client_gravity(self, &self->area.x, &self->area.y,
511                              self->client->area.width,
512                              self->client->area.height);
513     }
514
515     if (!fake) {
516         if (!frame_iconify_animating(self))
517             /* move and resize the top level frame.
518                shading can change without being moved or resized.
519                
520                but don't do this during an iconify animation. it will be
521                reflected afterwards.
522             */
523             XMoveResizeWindow(ob_display, self->window,
524                               self->area.x, self->area.y,
525                               self->area.width - self->bwidth * 2,
526                               self->area.height - self->bwidth * 2);
527
528         if (resized) {
529             framerender_frame(self);
530             frame_adjust_shape(self);
531         }
532
533         if (!STRUT_EQUAL(self->size, oldsize)) {
534             gulong vals[4];
535             vals[0] = self->size.left;
536             vals[1] = self->size.right;
537             vals[2] = self->size.top;
538             vals[3] = self->size.bottom;
539             PROP_SETA32(self->client->window, net_frame_extents,
540                         cardinal, vals, 4);
541             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
542                         cardinal, vals, 4);
543         }
544
545         /* if this occurs while we are focus cycling, the indicator needs to
546            match the changes */
547         if (focus_cycle_target == self->client)
548             focus_cycle_draw_indicator(self->client);
549     }
550     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
551         XResizeWindow(ob_display, self->label, self->label_width,
552                       ob_rr_theme->label_height);
553 }
554
555 void frame_adjust_client_area(ObFrame *self)
556 {
557     /* resize the plate */
558     XResizeWindow(ob_display, self->plate,
559                   self->client->area.width, self->client->area.height);
560 }
561
562 void frame_adjust_state(ObFrame *self)
563 {
564     framerender_frame(self);
565 }
566
567 void frame_adjust_focus(ObFrame *self, gboolean hilite)
568 {
569     self->focused = hilite;
570     framerender_frame(self);
571     XFlush(ob_display);
572 }
573
574 void frame_adjust_title(ObFrame *self)
575 {
576     framerender_frame(self);
577 }
578
579 void frame_adjust_icon(ObFrame *self)
580 {
581     framerender_frame(self);
582 }
583
584 void frame_grab_client(ObFrame *self)
585 {
586     /* reparent the client to the frame */
587     XReparentWindow(ob_display, self->client->window, self->plate, 0, 0);
588
589     /*
590       When reparenting the client window, it is usually not mapped yet, since
591       this occurs from a MapRequest. However, in the case where Openbox is
592       starting up, the window is already mapped, so we'll see unmap events for
593       it. There are 2 unmap events generated that we see, one with the 'event'
594       member set the root window, and one set to the client, but both get
595       handled and need to be ignored.
596     */
597     if (ob_state() == OB_STATE_STARTING)
598         self->client->ignore_unmaps += 2;
599
600     /* select the event mask on the client's parent (to receive config/map
601        req's) the ButtonPress is to catch clicks on the client border */
602     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
603
604     /* map the client so it maps when the frame does */
605     XMapWindow(ob_display, self->client->window);
606
607     /* set all the windows for the frame in the window_map */
608     g_hash_table_insert(window_map, &self->window, self->client);
609     g_hash_table_insert(window_map, &self->plate, self->client);
610     g_hash_table_insert(window_map, &self->inner, self->client);
611     g_hash_table_insert(window_map, &self->title, self->client);
612     g_hash_table_insert(window_map, &self->label, self->client);
613     g_hash_table_insert(window_map, &self->max, self->client);
614     g_hash_table_insert(window_map, &self->close, self->client);
615     g_hash_table_insert(window_map, &self->desk, self->client);
616     g_hash_table_insert(window_map, &self->shade, self->client);
617     g_hash_table_insert(window_map, &self->icon, self->client);
618     g_hash_table_insert(window_map, &self->iconify, self->client);
619     g_hash_table_insert(window_map, &self->handle, self->client);
620     g_hash_table_insert(window_map, &self->lgrip, self->client);
621     g_hash_table_insert(window_map, &self->rgrip, self->client);
622     g_hash_table_insert(window_map, &self->topresize, self->client);
623     g_hash_table_insert(window_map, &self->tltresize, self->client);
624     g_hash_table_insert(window_map, &self->tllresize, self->client);
625     g_hash_table_insert(window_map, &self->trtresize, self->client);
626     g_hash_table_insert(window_map, &self->trrresize, self->client);
627     g_hash_table_insert(window_map, &self->leftresize, self->client);
628     g_hash_table_insert(window_map, &self->rightresize, self->client);
629 }
630
631 void frame_release_client(ObFrame *self)
632 {
633     XEvent ev;
634     gboolean reparent = TRUE;
635
636     /* if there was any animation going on, kill it */
637     ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
638                                      self, FALSE);
639
640     /* check if the app has already reparented its window away */
641     while (XCheckTypedWindowEvent(ob_display, self->client->window,
642                                   ReparentNotify, &ev))
643     {
644         /* This check makes sure we don't catch our own reparent action to
645            our frame window. This doesn't count as the app reparenting itself
646            away of course.
647
648            Reparent events that are generated by us are just discarded here.
649            They are of no consequence to us anyhow.
650         */
651         if (ev.xreparent.parent != self->plate) {
652             reparent = FALSE;
653             XPutBackEvent(ob_display, &ev);
654             break;
655         }
656     }
657
658     if (reparent) {
659         /* according to the ICCCM - if the client doesn't reparent itself,
660            then we will reparent the window to root for them */
661         XReparentWindow(ob_display, self->client->window,
662                         RootWindow(ob_display, ob_screen),
663                         self->client->area.x,
664                         self->client->area.y);
665     }
666
667     /* remove all the windows for the frame from the window_map */
668     g_hash_table_remove(window_map, &self->window);
669     g_hash_table_remove(window_map, &self->plate);
670     g_hash_table_remove(window_map, &self->inner);
671     g_hash_table_remove(window_map, &self->title);
672     g_hash_table_remove(window_map, &self->label);
673     g_hash_table_remove(window_map, &self->max);
674     g_hash_table_remove(window_map, &self->close);
675     g_hash_table_remove(window_map, &self->desk);
676     g_hash_table_remove(window_map, &self->shade);
677     g_hash_table_remove(window_map, &self->icon);
678     g_hash_table_remove(window_map, &self->iconify);
679     g_hash_table_remove(window_map, &self->handle);
680     g_hash_table_remove(window_map, &self->lgrip);
681     g_hash_table_remove(window_map, &self->rgrip);
682     g_hash_table_remove(window_map, &self->topresize);
683     g_hash_table_remove(window_map, &self->tltresize);
684     g_hash_table_remove(window_map, &self->tllresize);
685     g_hash_table_remove(window_map, &self->trtresize);
686     g_hash_table_remove(window_map, &self->trrresize);
687     g_hash_table_remove(window_map, &self->leftresize);
688     g_hash_table_remove(window_map, &self->rightresize);
689
690     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
691 }
692
693 /* is there anything present between us and the label? */
694 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
695     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
696         if (*lc == ' ') continue; /* it was invalid */
697         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
698             return TRUE;
699         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
700             return TRUE;
701         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
702             return TRUE;
703         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
704             return TRUE;
705         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
706             return TRUE;
707         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
708             return TRUE;
709         if (*lc == 'L') return FALSE;
710     }
711     return FALSE;
712 }
713
714 static void layout_title(ObFrame *self)
715 {
716     gchar *lc;
717     gint i;
718
719     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
720     /* position of the left most button */
721     const gint left = ob_rr_theme->paddingx + 1;
722     /* position of the right most button */
723     const gint right = self->width - bwidth;
724
725     /* turn them all off */
726     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
727         self->max_on = self->close_on = self->label_on = FALSE;
728     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
729     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
730
731     /* figure out what's being show, find each element's position, and the
732        width of the label
733
734        do the ones before the label, then after the label,
735        i will be +1 the first time through when working to the left,
736        and -1 the second time through when working to the right */
737     for (i = 1; i >= -1; i-=2) {
738         gint x;
739         ObFrameContext *firstcon;
740
741         if (i > 0) {
742             x = left;
743             lc = config_title_layout;
744             firstcon = &self->leftmost;
745         } else {
746             x = right;
747             lc = config_title_layout + strlen(config_title_layout)-1;
748             firstcon = &self->rightmost;
749         }
750
751         /* stop at the end of the string (or the label, which calls break) */
752         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
753             if (*lc == 'L') {
754                 if (i > 0) {
755                     self->label_on = TRUE;
756                     self->label_x = x;
757                 }
758                 break; /* break the for loop, do other side of label */
759             } else if (*lc == 'N') {
760                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
761                 if ((self->icon_on = is_button_present(self, lc, i))) {
762                     /* icon is bigger than buttons */
763                     self->label_width -= bwidth + 2;
764                     self->icon_x = x;
765                     x += i * (bwidth + 2);
766                 }
767             } else if (*lc == 'D') {
768                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
769                 if ((self->desk_on = is_button_present(self, lc, i))) {
770                     self->label_width -= bwidth;
771                     self->desk_x = x;
772                     x += i * bwidth;
773                 }
774             } else if (*lc == 'S') {
775                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
776                 if ((self->shade_on = is_button_present(self, lc, i))) {
777                     self->label_width -= bwidth;
778                     self->shade_x = x;
779                     x += i * bwidth;
780                 }
781             } else if (*lc == 'I') {
782                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
783                 if ((self->iconify_on = is_button_present(self, lc, i))) {
784                     self->label_width -= bwidth;
785                     self->iconify_x = x;
786                     x += i * bwidth;
787                 }
788             } else if (*lc == 'M') {
789                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
790                 if ((self->max_on = is_button_present(self, lc, i))) {
791                     self->label_width -= bwidth;
792                     self->max_x = x;
793                     x += i * bwidth;
794                 }
795             } else if (*lc == 'C') {
796                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
797                 if ((self->close_on = is_button_present(self, lc, i))) {
798                     self->label_width -= bwidth;
799                     self->close_x = x;
800                     x += i * bwidth;
801                 }
802             } else
803                 continue; /* don't set firstcon */
804             firstcon = NULL;
805         }
806     }
807
808     /* position and map the elements */
809     if (self->icon_on) {
810         XMapWindow(ob_display, self->icon);
811         XMoveWindow(ob_display, self->icon, self->icon_x,
812                     ob_rr_theme->paddingy);
813     } else
814         XUnmapWindow(ob_display, self->icon);
815
816     if (self->desk_on) {
817         XMapWindow(ob_display, self->desk);
818         XMoveWindow(ob_display, self->desk, self->desk_x,
819                     ob_rr_theme->paddingy + 1);
820     } else
821         XUnmapWindow(ob_display, self->desk);
822
823     if (self->shade_on) {
824         XMapWindow(ob_display, self->shade);
825         XMoveWindow(ob_display, self->shade, self->shade_x,
826                     ob_rr_theme->paddingy + 1);
827     } else
828         XUnmapWindow(ob_display, self->shade);
829
830     if (self->iconify_on) {
831         XMapWindow(ob_display, self->iconify);
832         XMoveWindow(ob_display, self->iconify, self->iconify_x,
833                     ob_rr_theme->paddingy + 1);
834     } else
835         XUnmapWindow(ob_display, self->iconify);
836
837     if (self->max_on) {
838         XMapWindow(ob_display, self->max);
839         XMoveWindow(ob_display, self->max, self->max_x,
840                     ob_rr_theme->paddingy + 1);
841     } else
842         XUnmapWindow(ob_display, self->max);
843
844     if (self->close_on) {
845         XMapWindow(ob_display, self->close);
846         XMoveWindow(ob_display, self->close, self->close_x,
847                     ob_rr_theme->paddingy + 1);
848     } else
849         XUnmapWindow(ob_display, self->close);
850
851     if (self->label_on) {
852         self->label_width = MAX(1, self->label_width); /* no lower than 1 */
853         XMapWindow(ob_display, self->label);
854         XMoveWindow(ob_display, self->label, self->label_x,
855                     ob_rr_theme->paddingy);
856     } else
857         XUnmapWindow(ob_display, self->label);
858 }
859
860 ObFrameContext frame_context_from_string(const gchar *name)
861 {
862     if (!g_ascii_strcasecmp("Desktop", name))
863         return OB_FRAME_CONTEXT_DESKTOP;
864     else if (!g_ascii_strcasecmp("Client", name))
865         return OB_FRAME_CONTEXT_CLIENT;
866     else if (!g_ascii_strcasecmp("Titlebar", name))
867         return OB_FRAME_CONTEXT_TITLEBAR;
868     else if (!g_ascii_strcasecmp("Frame", name))
869         return OB_FRAME_CONTEXT_FRAME;
870     else if (!g_ascii_strcasecmp("TLCorner", name))
871         return OB_FRAME_CONTEXT_TLCORNER;
872     else if (!g_ascii_strcasecmp("TRCorner", name))
873         return OB_FRAME_CONTEXT_TRCORNER;
874     else if (!g_ascii_strcasecmp("BLCorner", name))
875         return OB_FRAME_CONTEXT_BLCORNER;
876     else if (!g_ascii_strcasecmp("BRCorner", name))
877         return OB_FRAME_CONTEXT_BRCORNER;
878     else if (!g_ascii_strcasecmp("Top", name))
879         return OB_FRAME_CONTEXT_TOP;
880     else if (!g_ascii_strcasecmp("Bottom", name))
881         return OB_FRAME_CONTEXT_BOTTOM;
882     else if (!g_ascii_strcasecmp("Left", name))
883         return OB_FRAME_CONTEXT_LEFT;
884     else if (!g_ascii_strcasecmp("Right", name))
885         return OB_FRAME_CONTEXT_RIGHT;
886     else if (!g_ascii_strcasecmp("Maximize", name))
887         return OB_FRAME_CONTEXT_MAXIMIZE;
888     else if (!g_ascii_strcasecmp("AllDesktops", name))
889         return OB_FRAME_CONTEXT_ALLDESKTOPS;
890     else if (!g_ascii_strcasecmp("Shade", name))
891         return OB_FRAME_CONTEXT_SHADE;
892     else if (!g_ascii_strcasecmp("Iconify", name))
893         return OB_FRAME_CONTEXT_ICONIFY;
894     else if (!g_ascii_strcasecmp("Icon", name))
895         return OB_FRAME_CONTEXT_ICON;
896     else if (!g_ascii_strcasecmp("Close", name))
897         return OB_FRAME_CONTEXT_CLOSE;
898     else if (!g_ascii_strcasecmp("MoveResize", name))
899         return OB_FRAME_CONTEXT_MOVE_RESIZE;
900     return OB_FRAME_CONTEXT_NONE;
901 }
902
903 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
904 {
905     ObFrame *self;
906
907     if (moveresize_in_progress)
908         return OB_FRAME_CONTEXT_MOVE_RESIZE;
909
910     if (win == RootWindow(ob_display, ob_screen))
911         return OB_FRAME_CONTEXT_DESKTOP;
912     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
913     if (win == client->window) {
914         /* conceptually, this is the desktop, as far as users are
915            concerned */
916         if (client->type == OB_CLIENT_TYPE_DESKTOP)
917             return OB_FRAME_CONTEXT_DESKTOP;
918         return OB_FRAME_CONTEXT_CLIENT;
919     }
920
921     self = client->frame;
922     if (win == self->inner || win == self->plate) {
923         /* conceptually, this is the desktop, as far as users are
924            concerned */
925         if (client->type == OB_CLIENT_TYPE_DESKTOP)
926             return OB_FRAME_CONTEXT_DESKTOP;
927         return OB_FRAME_CONTEXT_CLIENT;
928     }
929
930     if (win == self->title) {
931         /* when the user clicks in the corners of the titlebar and the client
932            is fully maximized, then treat it like they clicked in the
933            button that is there */
934         if (self->client->max_horz && self->client->max_vert &&
935             y < ob_rr_theme->paddingy + 1 + ob_rr_theme->button_size)
936         {
937             if (x < ((ob_rr_theme->paddingx + 1) * 2 +
938                      ob_rr_theme->button_size)) {
939                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
940                     return self->leftmost;
941             }
942             else if (x > (self->width -
943                           (ob_rr_theme->paddingx + 1 +
944                            ob_rr_theme->button_size)))
945             {
946                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
947                     return self->rightmost;
948             }
949         }
950         return OB_FRAME_CONTEXT_TITLEBAR;
951     }
952
953     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
954     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
955     if (win == self->handle)    return OB_FRAME_CONTEXT_BOTTOM;
956     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
957     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
958     if (win == self->topresize) return OB_FRAME_CONTEXT_TOP;
959     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
960     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
961     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
962     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
963     if (win == self->leftresize) return OB_FRAME_CONTEXT_LEFT;
964     if (win == self->rightresize) return OB_FRAME_CONTEXT_RIGHT;
965     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
966     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
967     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
968     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
969     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
970     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
971
972     return OB_FRAME_CONTEXT_NONE;
973 }
974
975 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
976 {
977     /* horizontal */
978     switch (self->client->gravity) {
979     default:
980     case NorthWestGravity:
981     case SouthWestGravity:
982     case WestGravity:
983         break;
984
985     case NorthGravity:
986     case SouthGravity:
987     case CenterGravity:
988         *x -= (self->size.left + w) / 2;
989         break;
990
991     case NorthEastGravity:
992     case SouthEastGravity:
993     case EastGravity:
994         *x -= (self->size.left + self->size.right + w) - 1;
995         break;
996
997     case ForgetGravity:
998     case StaticGravity:
999         *x -= self->size.left;
1000         break;
1001     }
1002
1003     /* vertical */
1004     switch (self->client->gravity) {
1005     default:
1006     case NorthWestGravity:
1007     case NorthEastGravity:
1008     case NorthGravity:
1009         break;
1010
1011     case CenterGravity:
1012     case EastGravity:
1013     case WestGravity:
1014         *y -= (self->size.top + h) / 2;
1015         break;
1016
1017     case SouthWestGravity:
1018     case SouthEastGravity:
1019     case SouthGravity:
1020         *y -= (self->size.top + self->size.bottom + h) - 1;
1021         break;
1022
1023     case ForgetGravity:
1024     case StaticGravity:
1025         *y -= self->size.top;
1026         break;
1027     }
1028 }
1029
1030 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
1031 {
1032     /* horizontal */
1033     switch (self->client->gravity) {
1034     default:
1035     case NorthWestGravity:
1036     case WestGravity:
1037     case SouthWestGravity:
1038         break;
1039     case NorthGravity:
1040     case CenterGravity:
1041     case SouthGravity:
1042         *x += (self->size.left + w) / 2;
1043         break;
1044     case NorthEastGravity:
1045     case EastGravity:
1046     case SouthEastGravity:
1047         *x += (self->size.left + self->size.right + w) - 1;
1048         break;
1049     case StaticGravity:
1050     case ForgetGravity:
1051         *x += self->size.left;
1052         break;
1053     }
1054
1055     /* vertical */
1056     switch (self->client->gravity) {
1057     default:
1058     case NorthWestGravity:
1059     case NorthGravity:
1060     case NorthEastGravity:
1061         break;
1062     case WestGravity:
1063     case CenterGravity:
1064     case EastGravity:
1065         *y += (self->size.top + h) / 2;
1066         break;
1067     case SouthWestGravity:
1068     case SouthGravity:
1069     case SouthEastGravity:
1070         *y += (self->size.top + self->size.bottom + h) - 1;
1071         break;
1072     case StaticGravity:
1073     case ForgetGravity:
1074         *y += self->size.top;
1075         break;
1076     }
1077 }
1078
1079 static void flash_done(gpointer data)
1080 {
1081     ObFrame *self = data;
1082
1083     if (self->focused != self->flash_on)
1084         frame_adjust_focus(self, self->focused);
1085 }
1086
1087 static gboolean flash_timeout(gpointer data)
1088 {
1089     ObFrame *self = data;
1090     GTimeVal now;
1091
1092     g_get_current_time(&now);
1093     if (now.tv_sec > self->flash_end.tv_sec ||
1094         (now.tv_sec == self->flash_end.tv_sec &&
1095          now.tv_usec >= self->flash_end.tv_usec))
1096         self->flashing = FALSE;
1097
1098     if (!self->flashing)
1099         return FALSE; /* we are done */
1100
1101     self->flash_on = !self->flash_on;
1102     if (!self->focused) {
1103         frame_adjust_focus(self, self->flash_on);
1104         self->focused = FALSE;
1105     }
1106
1107     return TRUE; /* go again */
1108 }
1109
1110 void frame_flash_start(ObFrame *self)
1111 {
1112     self->flash_on = self->focused;
1113
1114     if (!self->flashing)
1115         ob_main_loop_timeout_add(ob_main_loop,
1116                                  G_USEC_PER_SEC * 0.6,
1117                                  flash_timeout,
1118                                  self,
1119                                  g_direct_equal,
1120                                  flash_done);
1121     g_get_current_time(&self->flash_end);
1122     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1123     
1124     self->flashing = TRUE;
1125 }
1126
1127 void frame_flash_stop(ObFrame *self)
1128 {
1129     self->flashing = FALSE;
1130 }
1131
1132 static gulong frame_animate_iconify_time_left(ObFrame *self,
1133                                               const GTimeVal *now)
1134 {
1135     glong sec, usec;
1136     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1137     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1138     if (usec < 0) {
1139         usec += G_USEC_PER_SEC;
1140         sec--;
1141     }
1142     /* no negative values */
1143     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1144 }
1145
1146 static gboolean frame_animate_iconify(gpointer p)
1147 {
1148     ObFrame *self = p;
1149     gint x, y, w, h;
1150     gint iconx, icony, iconw;
1151     GTimeVal now;
1152     gulong time;
1153     gboolean iconifying;
1154
1155     if (self->client->icon_geometry.width == 0) {
1156         /* there is no icon geometry set so just go straight down */
1157         Rect *a = screen_physical_area();
1158         iconx = self->area.x + self->area.width / 2 + 32;
1159         icony = a->y + a->width;
1160         iconw = 64;
1161     } else {
1162         iconx = self->client->icon_geometry.x;
1163         icony = self->client->icon_geometry.y;
1164         iconw = self->client->icon_geometry.width;
1165     }
1166
1167     iconifying = self->iconify_animation_going > 0;
1168
1169     /* how far do we have left to go ? */
1170     g_get_current_time(&now);
1171     time = frame_animate_iconify_time_left(self, &now);
1172     
1173     if (time == 0 || iconifying) {
1174         /* start where the frame is supposed to be */
1175         x = self->area.x;
1176         y = self->area.y;
1177         w = self->area.width - self->bwidth * 2;
1178         h = self->area.height - self->bwidth * 2;
1179     } else {
1180         /* start at the icon */
1181         x = iconx;
1182         y = icony;
1183         w = iconw;
1184         h = self->innersize.top; /* just the titlebar */
1185     }
1186
1187     if (time > 0) {
1188         glong dx, dy, dw;
1189         glong elapsed;
1190
1191         dx = self->area.x - iconx;
1192         dy = self->area.y - icony;
1193         dw = self->area.width - self->bwidth * 2 - iconw;
1194          /* if restoring, we move in the opposite direction */
1195         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1196
1197         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1198         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1199         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1200         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1201         h = self->innersize.top; /* just the titlebar */
1202     }
1203
1204     if (time == 0)
1205         frame_end_iconify_animation(self);
1206     else {
1207         XMoveResizeWindow(ob_display, self->window, x, y, w, h);
1208         XFlush(ob_display);
1209     }
1210
1211     return time > 0; /* repeat until we're out of time */
1212 }
1213
1214 void frame_end_iconify_animation(ObFrame *self)
1215 {
1216     /* see if there is an animation going */
1217     if (self->iconify_animation_going == 0) return;
1218
1219     if (!self->visible)
1220         XUnmapWindow(ob_display, self->window);
1221     else
1222         /* Send a ConfigureNotify when the animation is done, this fixes
1223            KDE's pager showing the window in the wrong place. */
1224         client_reconfigure(self->client);
1225
1226     /* we're not animating any more ! */
1227     self->iconify_animation_going = 0;
1228
1229     XMoveResizeWindow(ob_display, self->window,
1230                       self->area.x, self->area.y,
1231                       self->area.width - self->bwidth * 2,
1232                       self->area.height - self->bwidth * 2);
1233     XFlush(ob_display);
1234 }
1235
1236 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1237 {
1238     gulong time;
1239     gboolean new_anim = FALSE;
1240     gboolean set_end = TRUE;
1241     GTimeVal now;
1242
1243     /* if there is no titlebar, just don't animate for now
1244        XXX it would be nice tho.. */
1245     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1246         return;
1247
1248     /* get the current time */
1249     g_get_current_time(&now);
1250
1251     /* get how long until the end */
1252     time = FRAME_ANIMATE_ICONIFY_TIME;
1253     if (self->iconify_animation_going) {
1254         if (!!iconifying != (self->iconify_animation_going > 0)) {
1255             /* animation was already going on in the opposite direction */
1256             time = time - frame_animate_iconify_time_left(self, &now);
1257         } else
1258             /* animation was already going in the same direction */
1259             set_end = FALSE;
1260     } else
1261         new_anim = TRUE;
1262     self->iconify_animation_going = iconifying ? 1 : -1;
1263
1264     /* set the ending time */
1265     if (set_end) {
1266         self->iconify_animation_end.tv_sec = now.tv_sec;
1267         self->iconify_animation_end.tv_usec = now.tv_usec;
1268         g_time_val_add(&self->iconify_animation_end, time);
1269     }
1270
1271     if (new_anim) {
1272         ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
1273                                          self, FALSE);
1274         ob_main_loop_timeout_add(ob_main_loop,
1275                                  FRAME_ANIMATE_ICONIFY_STEP_TIME,
1276                                  frame_animate_iconify, self,
1277                                  g_direct_equal, NULL);
1278
1279         /* do the first step */
1280         frame_animate_iconify(self);
1281
1282         /* show it during the animation even if it is not "visible" */
1283         if (!self->visible)
1284             XMapWindow(ob_display, self->window);
1285     }
1286 }