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