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