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