add a root context that only applies to the root window. it fallsback to the desktop...
[dana/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "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("Root", name))
865         return OB_FRAME_CONTEXT_ROOT;
866     else if (!g_ascii_strcasecmp("Client", name))
867         return OB_FRAME_CONTEXT_CLIENT;
868     else if (!g_ascii_strcasecmp("Titlebar", name))
869         return OB_FRAME_CONTEXT_TITLEBAR;
870     else if (!g_ascii_strcasecmp("Frame", name))
871         return OB_FRAME_CONTEXT_FRAME;
872     else if (!g_ascii_strcasecmp("TLCorner", name))
873         return OB_FRAME_CONTEXT_TLCORNER;
874     else if (!g_ascii_strcasecmp("TRCorner", name))
875         return OB_FRAME_CONTEXT_TRCORNER;
876     else if (!g_ascii_strcasecmp("BLCorner", name))
877         return OB_FRAME_CONTEXT_BLCORNER;
878     else if (!g_ascii_strcasecmp("BRCorner", name))
879         return OB_FRAME_CONTEXT_BRCORNER;
880     else if (!g_ascii_strcasecmp("Top", name))
881         return OB_FRAME_CONTEXT_TOP;
882     else if (!g_ascii_strcasecmp("Bottom", name))
883         return OB_FRAME_CONTEXT_BOTTOM;
884     else if (!g_ascii_strcasecmp("Left", name))
885         return OB_FRAME_CONTEXT_LEFT;
886     else if (!g_ascii_strcasecmp("Right", name))
887         return OB_FRAME_CONTEXT_RIGHT;
888     else if (!g_ascii_strcasecmp("Maximize", name))
889         return OB_FRAME_CONTEXT_MAXIMIZE;
890     else if (!g_ascii_strcasecmp("AllDesktops", name))
891         return OB_FRAME_CONTEXT_ALLDESKTOPS;
892     else if (!g_ascii_strcasecmp("Shade", name))
893         return OB_FRAME_CONTEXT_SHADE;
894     else if (!g_ascii_strcasecmp("Iconify", name))
895         return OB_FRAME_CONTEXT_ICONIFY;
896     else if (!g_ascii_strcasecmp("Icon", name))
897         return OB_FRAME_CONTEXT_ICON;
898     else if (!g_ascii_strcasecmp("Close", name))
899         return OB_FRAME_CONTEXT_CLOSE;
900     else if (!g_ascii_strcasecmp("MoveResize", name))
901         return OB_FRAME_CONTEXT_MOVE_RESIZE;
902     return OB_FRAME_CONTEXT_NONE;
903 }
904
905 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
906 {
907     ObFrame *self;
908
909     if (moveresize_in_progress)
910         return OB_FRAME_CONTEXT_MOVE_RESIZE;
911
912     if (win == RootWindow(ob_display, ob_screen))
913         return OB_FRAME_CONTEXT_ROOT ;
914     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
915     if (win == client->window) {
916         /* conceptually, this is the desktop, as far as users are
917            concerned */
918         if (client->type == OB_CLIENT_TYPE_DESKTOP)
919             return OB_FRAME_CONTEXT_DESKTOP;
920         return OB_FRAME_CONTEXT_CLIENT;
921     }
922
923     self = client->frame;
924     if (win == self->inner || win == self->plate) {
925         /* conceptually, this is the desktop, as far as users are
926            concerned */
927         if (client->type == OB_CLIENT_TYPE_DESKTOP)
928             return OB_FRAME_CONTEXT_DESKTOP;
929         return OB_FRAME_CONTEXT_CLIENT;
930     }
931
932     if (win == self->title) {
933         /* when the user clicks in the corners of the titlebar and the client
934            is fully maximized, then treat it like they clicked in the
935            button that is there */
936         if (self->client->max_horz && self->client->max_vert &&
937             y < ob_rr_theme->paddingy + 1 + ob_rr_theme->button_size)
938         {
939             if (x < ((ob_rr_theme->paddingx + 1) * 2 +
940                      ob_rr_theme->button_size)) {
941                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
942                     return self->leftmost;
943             }
944             else if (x > (self->width -
945                           (ob_rr_theme->paddingx + 1 +
946                            ob_rr_theme->button_size)))
947             {
948                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
949                     return self->rightmost;
950             }
951         }
952         return OB_FRAME_CONTEXT_TITLEBAR;
953     }
954
955     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
956     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
957     if (win == self->handle)    return OB_FRAME_CONTEXT_BOTTOM;
958     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
959     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
960     if (win == self->topresize) return OB_FRAME_CONTEXT_TOP;
961     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
962     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
963     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
964     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
965     if (win == self->leftresize) return OB_FRAME_CONTEXT_LEFT;
966     if (win == self->rightresize) return OB_FRAME_CONTEXT_RIGHT;
967     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
968     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
969     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
970     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
971     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
972     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
973
974     return OB_FRAME_CONTEXT_NONE;
975 }
976
977 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
978 {
979     /* horizontal */
980     switch (self->client->gravity) {
981     default:
982     case NorthWestGravity:
983     case SouthWestGravity:
984     case WestGravity:
985         break;
986
987     case NorthGravity:
988     case SouthGravity:
989     case CenterGravity:
990         *x -= (self->size.left + w) / 2;
991         break;
992
993     case NorthEastGravity:
994     case SouthEastGravity:
995     case EastGravity:
996         *x -= (self->size.left + self->size.right + w) - 1;
997         break;
998
999     case ForgetGravity:
1000     case StaticGravity:
1001         *x -= self->size.left;
1002         break;
1003     }
1004
1005     /* vertical */
1006     switch (self->client->gravity) {
1007     default:
1008     case NorthWestGravity:
1009     case NorthEastGravity:
1010     case NorthGravity:
1011         break;
1012
1013     case CenterGravity:
1014     case EastGravity:
1015     case WestGravity:
1016         *y -= (self->size.top + h) / 2;
1017         break;
1018
1019     case SouthWestGravity:
1020     case SouthEastGravity:
1021     case SouthGravity:
1022         *y -= (self->size.top + self->size.bottom + h) - 1;
1023         break;
1024
1025     case ForgetGravity:
1026     case StaticGravity:
1027         *y -= self->size.top;
1028         break;
1029     }
1030 }
1031
1032 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
1033 {
1034     /* horizontal */
1035     switch (self->client->gravity) {
1036     default:
1037     case NorthWestGravity:
1038     case WestGravity:
1039     case SouthWestGravity:
1040         break;
1041     case NorthGravity:
1042     case CenterGravity:
1043     case SouthGravity:
1044         *x += (self->size.left + w) / 2;
1045         break;
1046     case NorthEastGravity:
1047     case EastGravity:
1048     case SouthEastGravity:
1049         *x += (self->size.left + self->size.right + w) - 1;
1050         break;
1051     case StaticGravity:
1052     case ForgetGravity:
1053         *x += self->size.left;
1054         break;
1055     }
1056
1057     /* vertical */
1058     switch (self->client->gravity) {
1059     default:
1060     case NorthWestGravity:
1061     case NorthGravity:
1062     case NorthEastGravity:
1063         break;
1064     case WestGravity:
1065     case CenterGravity:
1066     case EastGravity:
1067         *y += (self->size.top + h) / 2;
1068         break;
1069     case SouthWestGravity:
1070     case SouthGravity:
1071     case SouthEastGravity:
1072         *y += (self->size.top + self->size.bottom + h) - 1;
1073         break;
1074     case StaticGravity:
1075     case ForgetGravity:
1076         *y += self->size.top;
1077         break;
1078     }
1079 }
1080
1081 static void flash_done(gpointer data)
1082 {
1083     ObFrame *self = data;
1084
1085     if (self->focused != self->flash_on)
1086         frame_adjust_focus(self, self->focused);
1087 }
1088
1089 static gboolean flash_timeout(gpointer data)
1090 {
1091     ObFrame *self = data;
1092     GTimeVal now;
1093
1094     g_get_current_time(&now);
1095     if (now.tv_sec > self->flash_end.tv_sec ||
1096         (now.tv_sec == self->flash_end.tv_sec &&
1097          now.tv_usec >= self->flash_end.tv_usec))
1098         self->flashing = FALSE;
1099
1100     if (!self->flashing)
1101         return FALSE; /* we are done */
1102
1103     self->flash_on = !self->flash_on;
1104     if (!self->focused) {
1105         frame_adjust_focus(self, self->flash_on);
1106         self->focused = FALSE;
1107     }
1108
1109     return TRUE; /* go again */
1110 }
1111
1112 void frame_flash_start(ObFrame *self)
1113 {
1114     self->flash_on = self->focused;
1115
1116     if (!self->flashing)
1117         ob_main_loop_timeout_add(ob_main_loop,
1118                                  G_USEC_PER_SEC * 0.6,
1119                                  flash_timeout,
1120                                  self,
1121                                  g_direct_equal,
1122                                  flash_done);
1123     g_get_current_time(&self->flash_end);
1124     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1125     
1126     self->flashing = TRUE;
1127 }
1128
1129 void frame_flash_stop(ObFrame *self)
1130 {
1131     self->flashing = FALSE;
1132 }
1133
1134 static gulong frame_animate_iconify_time_left(ObFrame *self,
1135                                               const GTimeVal *now)
1136 {
1137     glong sec, usec;
1138     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1139     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1140     if (usec < 0) {
1141         usec += G_USEC_PER_SEC;
1142         sec--;
1143     }
1144     /* no negative values */
1145     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1146 }
1147
1148 static gboolean frame_animate_iconify(gpointer p)
1149 {
1150     ObFrame *self = p;
1151     gint x, y, w, h;
1152     gint iconx, icony, iconw;
1153     GTimeVal now;
1154     gulong time;
1155     gboolean iconifying;
1156
1157     if (self->client->icon_geometry.width == 0) {
1158         /* there is no icon geometry set so just go straight down */
1159         Rect *a = screen_physical_area();
1160         iconx = self->area.x + self->area.width / 2 + 32;
1161         icony = a->y + a->width;
1162         iconw = 64;
1163     } else {
1164         iconx = self->client->icon_geometry.x;
1165         icony = self->client->icon_geometry.y;
1166         iconw = self->client->icon_geometry.width;
1167     }
1168
1169     iconifying = self->iconify_animation_going > 0;
1170
1171     /* how far do we have left to go ? */
1172     g_get_current_time(&now);
1173     time = frame_animate_iconify_time_left(self, &now);
1174     
1175     if (time == 0 || iconifying) {
1176         /* start where the frame is supposed to be */
1177         x = self->area.x;
1178         y = self->area.y;
1179         w = self->area.width - self->bwidth * 2;
1180         h = self->area.height - self->bwidth * 2;
1181     } else {
1182         /* start at the icon */
1183         x = iconx;
1184         y = icony;
1185         w = iconw;
1186         h = self->innersize.top; /* just the titlebar */
1187     }
1188
1189     if (time > 0) {
1190         glong dx, dy, dw;
1191         glong elapsed;
1192
1193         dx = self->area.x - iconx;
1194         dy = self->area.y - icony;
1195         dw = self->area.width - self->bwidth * 2 - iconw;
1196          /* if restoring, we move in the opposite direction */
1197         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1198
1199         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1200         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1201         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1202         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1203         h = self->innersize.top; /* just the titlebar */
1204     }
1205
1206     if (time == 0)
1207         frame_end_iconify_animation(self);
1208     else {
1209         XMoveResizeWindow(ob_display, self->window, x, y, w, h);
1210         XFlush(ob_display);
1211     }
1212
1213     return time > 0; /* repeat until we're out of time */
1214 }
1215
1216 void frame_end_iconify_animation(ObFrame *self)
1217 {
1218     /* see if there is an animation going */
1219     if (self->iconify_animation_going == 0) return;
1220
1221     if (!self->visible)
1222         XUnmapWindow(ob_display, self->window);
1223     else
1224         /* Send a ConfigureNotify when the animation is done, this fixes
1225            KDE's pager showing the window in the wrong place. */
1226         client_reconfigure(self->client);
1227
1228     /* we're not animating any more ! */
1229     self->iconify_animation_going = 0;
1230
1231     XMoveResizeWindow(ob_display, self->window,
1232                       self->area.x, self->area.y,
1233                       self->area.width - self->bwidth * 2,
1234                       self->area.height - self->bwidth * 2);
1235     XFlush(ob_display);
1236 }
1237
1238 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1239 {
1240     gulong time;
1241     gboolean new_anim = FALSE;
1242     gboolean set_end = TRUE;
1243     GTimeVal now;
1244
1245     /* if there is no titlebar, just don't animate for now
1246        XXX it would be nice tho.. */
1247     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1248         return;
1249
1250     /* get the current time */
1251     g_get_current_time(&now);
1252
1253     /* get how long until the end */
1254     time = FRAME_ANIMATE_ICONIFY_TIME;
1255     if (self->iconify_animation_going) {
1256         if (!!iconifying != (self->iconify_animation_going > 0)) {
1257             /* animation was already going on in the opposite direction */
1258             time = time - frame_animate_iconify_time_left(self, &now);
1259         } else
1260             /* animation was already going in the same direction */
1261             set_end = FALSE;
1262     } else
1263         new_anim = TRUE;
1264     self->iconify_animation_going = iconifying ? 1 : -1;
1265
1266     /* set the ending time */
1267     if (set_end) {
1268         self->iconify_animation_end.tv_sec = now.tv_sec;
1269         self->iconify_animation_end.tv_usec = now.tv_usec;
1270         g_time_val_add(&self->iconify_animation_end, time);
1271     }
1272
1273     if (new_anim) {
1274         ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
1275                                          self, FALSE);
1276         ob_main_loop_timeout_add(ob_main_loop,
1277                                  FRAME_ANIMATE_ICONIFY_STEP_TIME,
1278                                  frame_animate_iconify, self,
1279                                  g_direct_equal, NULL);
1280
1281         /* do the first step */
1282         frame_animate_iconify(self);
1283
1284         /* show it during the animation even if it is not "visible" */
1285         if (!self->visible)
1286             XMapWindow(ob_display, self->window);
1287     }
1288 }