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