]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/frame.c
Merge branch 'wip/edges' into wip/mikabox
[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 "grab.h"
24 #include "debug.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "focus_cycle.h"
28 #include "focus_cycle_indicator.h"
29 #include "moveresize.h"
30 #include "screen.h"
31 #include "edges.h"
32 #include "obrender/theme.h"
33 #include "obt/display.h"
34 #include "obt/xqueue.h"
35 #include "obt/prop.h"
36
37 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
38                          ButtonPressMask | ButtonReleaseMask | \
39                          SubstructureRedirectMask | FocusChangeMask)
40 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
41                            ButtonMotionMask | PointerMotionMask | \
42                            EnterWindowMask | LeaveWindowMask)
43
44 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
45 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (1000 / 60) /* 60 Hz */
46
47 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_b)
48
49 static void flash_done(gpointer data);
50 static gboolean flash_timeout(gpointer data);
51
52 static void layout_title(ObFrame *self);
53 static void set_theme_statics(ObFrame *self);
54 static void free_theme_statics(ObFrame *self);
55 static gboolean frame_animate_iconify(gpointer self);
56 static void frame_adjust_cursors(ObFrame *self);
57
58 static Window createWindow(Window parent, Visual *visual,
59                            gulong mask, XSetWindowAttributes *attrib)
60 {
61     return XCreateWindow(obt_display, parent, 0, 0, 1, 1, 0,
62                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
63                          (visual ? visual : RrVisual(ob_rr_inst)),
64                          mask, attrib);
65
66 }
67
68 static Visual *check_32bit_client(ObClient *c)
69 {
70     XWindowAttributes wattrib;
71     Status ret;
72
73     /* we're already running at 32 bit depth, yay. we don't need to use their
74        visual */
75     if (RrDepth(ob_rr_inst) == 32)
76         return NULL;
77
78     ret = XGetWindowAttributes(obt_display, c->window, &wattrib);
79     g_assert(ret != BadDrawable);
80     g_assert(ret != BadWindow);
81
82     if (wattrib.depth == 32)
83         return wattrib.visual;
84     return NULL;
85 }
86
87 ObFrame *frame_new(ObClient *client)
88 {
89     XSetWindowAttributes attrib;
90     gulong mask;
91     ObFrame *self;
92     Visual *visual;
93
94     self = g_slice_new0(ObFrame);
95     self->client = client;
96
97     visual = check_32bit_client(client);
98
99     /* create the non-visible decor windows */
100
101     mask = 0;
102     if (visual) {
103         /* client has a 32-bit visual */
104         mask = CWColormap | CWBackPixel | CWBorderPixel;
105         /* create a colormap with the visual */
106         self->colormap = attrib.colormap =
107             XCreateColormap(obt_display, obt_root(ob_screen),
108                             visual, AllocNone);
109         attrib.background_pixel = BlackPixel(obt_display, ob_screen);
110         attrib.border_pixel = BlackPixel(obt_display, ob_screen);
111     }
112     self->window = createWindow(obt_root(ob_screen), visual,
113                                 mask, &attrib);
114
115     /* create the visible decor windows */
116
117     mask = 0;
118     if (visual) {
119         /* client has a 32-bit visual */
120         mask = CWColormap | CWBackPixel | CWBorderPixel;
121         attrib.colormap = RrColormap(ob_rr_inst);
122     }
123
124     self->backback = createWindow(self->window, NULL, mask, &attrib);
125     self->backfront = createWindow(self->backback, NULL, mask, &attrib);
126
127     mask |= CWEventMask;
128     attrib.event_mask = ELEMENT_EVENTMASK;
129     self->innerleft = createWindow(self->window, NULL, mask, &attrib);
130     self->innertop = createWindow(self->window, NULL, mask, &attrib);
131     self->innerright = createWindow(self->window, NULL, mask, &attrib);
132     self->innerbottom = createWindow(self->window, NULL, mask, &attrib);
133
134     self->innerblb = createWindow(self->innerbottom, NULL, mask, &attrib);
135     self->innerbrb = createWindow(self->innerbottom, NULL, mask, &attrib);
136     self->innerbll = createWindow(self->innerleft, NULL, mask, &attrib);
137     self->innerbrr = createWindow(self->innerright, NULL, mask, &attrib);
138
139     self->title = createWindow(self->window, NULL, mask, &attrib);
140     self->titleleft = createWindow(self->window, NULL, mask, &attrib);
141     self->titletop = createWindow(self->window, NULL, mask, &attrib);
142     self->titletopleft = createWindow(self->window, NULL, mask, &attrib);
143     self->titletopright = createWindow(self->window, NULL, mask, &attrib);
144     self->titleright = createWindow(self->window, NULL, mask, &attrib);
145     self->titlebottom = createWindow(self->window, NULL, mask, &attrib);
146
147     self->topresize = createWindow(self->title, NULL, mask, &attrib);
148     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
149     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
150     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
151     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
152
153     self->left = createWindow(self->window, NULL, mask, &attrib);
154     self->right = createWindow(self->window, NULL, mask, &attrib);
155
156     self->label = createWindow(self->title, NULL, mask, &attrib);
157     self->max = createWindow(self->title, NULL, mask, &attrib);
158     self->close = createWindow(self->title, NULL, mask, &attrib);
159     self->desk = createWindow(self->title, NULL, mask, &attrib);
160     self->shade = createWindow(self->title, NULL, mask, &attrib);
161     self->icon = createWindow(self->title, NULL, mask, &attrib);
162     self->iconify = createWindow(self->title, NULL, mask, &attrib);
163
164     self->handle = createWindow(self->window, NULL, mask, &attrib);
165     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
166     self->rgrip = createWindow(self->handle, NULL, mask, &attrib);
167
168     self->handleleft = createWindow(self->handle, NULL, mask, &attrib);
169     self->handleright = createWindow(self->handle, NULL, mask, &attrib);
170
171     self->handletop = createWindow(self->window, NULL, mask, &attrib);
172     self->handlebottom = createWindow(self->window, NULL, mask, &attrib);
173     self->lgripleft = createWindow(self->window, NULL, mask, &attrib);
174     self->lgriptop = createWindow(self->window, NULL, mask, &attrib);
175     self->lgripbottom = createWindow(self->window, NULL, mask, &attrib);
176     self->rgripright = createWindow(self->window, NULL, mask, &attrib);
177     self->rgriptop = createWindow(self->window, NULL, mask, &attrib);
178     self->rgripbottom = createWindow(self->window, NULL, mask, &attrib);
179
180     self->focused = FALSE;
181
182     /* the other stuff is shown based on decor settings */
183     XMapWindow(obt_display, self->label);
184     XMapWindow(obt_display, self->backback);
185     XMapWindow(obt_display, self->backfront);
186
187     self->max_press = self->close_press = self->desk_press =
188         self->iconify_press = self->shade_press = FALSE;
189     self->max_hover = self->close_hover = self->desk_hover =
190         self->iconify_hover = self->shade_hover = FALSE;
191
192     /* make sure the size will be different the first time, so the extent hints
193        will be set */
194     STRUT_SET(self->oldsize, -1, -1, -1, -1);
195
196     set_theme_statics(self);
197
198     return self;
199 }
200
201 static void set_theme_statics(ObFrame *self)
202 {
203     /* set colors/appearance/sizes for stuff that doesn't change */
204     XResizeWindow(obt_display, self->max,
205                   ob_rr_theme->button_size, ob_rr_theme->button_size);
206     XResizeWindow(obt_display, self->iconify,
207                   ob_rr_theme->button_size, ob_rr_theme->button_size);
208     XResizeWindow(obt_display, self->icon,
209                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
210     XResizeWindow(obt_display, self->close,
211                   ob_rr_theme->button_size, ob_rr_theme->button_size);
212     XResizeWindow(obt_display, self->desk,
213                   ob_rr_theme->button_size, ob_rr_theme->button_size);
214     XResizeWindow(obt_display, self->shade,
215                   ob_rr_theme->button_size, ob_rr_theme->button_size);
216     XResizeWindow(obt_display, self->tltresize,
217                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
218     XResizeWindow(obt_display, self->trtresize,
219                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
220     XResizeWindow(obt_display, self->tllresize,
221                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
222     XResizeWindow(obt_display, self->trrresize,
223                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
224 }
225
226 static void free_theme_statics(ObFrame *self)
227 {
228 }
229
230 void frame_free(ObFrame *self)
231 {
232     free_theme_statics(self);
233
234     XDestroyWindow(obt_display, self->window);
235     if (self->colormap)
236         XFreeColormap(obt_display, self->colormap);
237
238     g_slice_free(ObFrame, self);
239 }
240
241 void frame_show(ObFrame *self)
242 {
243     if (!self->visible) {
244         self->visible = TRUE;
245         framerender_frame(self);
246         /* Grab the server to make sure that the frame window is mapped before
247            the client gets its MapNotify, i.e. to make sure the client is
248            _visible_ when it gets MapNotify. */
249         grab_server(TRUE);
250         XMapWindow(obt_display, self->client->window);
251         XMapWindow(obt_display, self->window);
252         grab_server(FALSE);
253     }
254 }
255
256 void frame_hide(ObFrame *self)
257 {
258     if (self->visible) {
259         self->visible = FALSE;
260         if (!frame_iconify_animating(self))
261             XUnmapWindow(obt_display, self->window);
262         /* we unmap the client itself so that we can get MapRequest
263            events, and because the ICCCM tells us to! */
264         XUnmapWindow(obt_display, self->client->window);
265         self->client->ignore_unmaps += 1;
266     }
267 }
268
269 void frame_adjust_theme(ObFrame *self)
270 {
271     free_theme_statics(self);
272     set_theme_statics(self);
273 }
274
275 #ifdef SHAPE
276 void frame_adjust_shape_kind(ObFrame *self, int kind)
277 {
278     gint num;
279     XRectangle xrect[2];
280     gboolean shaped;
281
282     shaped = (kind == ShapeBounding && self->client->shaped);
283 #ifdef ShapeInput
284     shaped |= (kind == ShapeInput && self->client->shaped_input);
285 #endif
286
287     if (!shaped) {
288         /* clear the shape on the frame window */
289         XShapeCombineMask(obt_display, self->window, kind,
290                           self->size.left,
291                           self->size.top,
292                           None, ShapeSet);
293     } else {
294         /* make the frame's shape match the clients */
295         XShapeCombineShape(obt_display, self->window, kind,
296                            self->size.left,
297                            self->size.top,
298                            self->client->window,
299                            kind, ShapeSet);
300
301         num = 0;
302         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
303             xrect[0].x = 0;
304             xrect[0].y = 0;
305             xrect[0].width = self->area.width;
306             xrect[0].height = self->size.top;
307             ++num;
308         }
309
310         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
311             ob_rr_theme->handle_height > 0)
312         {
313             xrect[1].x = 0;
314             xrect[1].y = FRAME_HANDLE_Y(self);
315             xrect[1].width = self->area.width;
316             xrect[1].height = ob_rr_theme->handle_height +
317                 self->bwidth * 2;
318             ++num;
319         }
320
321         XShapeCombineRectangles(obt_display, self->window,
322                                 ShapeBounding, 0, 0, xrect, num,
323                                 ShapeUnion, Unsorted);
324     }
325 }
326 #endif
327
328 void frame_adjust_shape(ObFrame *self)
329 {
330 #ifdef SHAPE
331   frame_adjust_shape_kind(self, ShapeBounding);
332 #ifdef ShapeInput
333   frame_adjust_shape_kind(self, ShapeInput);
334 #endif
335 #endif
336 }
337
338 void frame_adjust_area(ObFrame *self, gboolean moved,
339                        gboolean resized, gboolean fake)
340 {
341     if (resized) {
342         /* do this before changing the frame's status like max_horz max_vert */
343         frame_adjust_cursors(self);
344
345         self->functions = self->client->functions;
346         self->decorations = self->client->decorations;
347         self->max_horz = self->client->max_horz;
348         self->max_vert = self->client->max_vert;
349         self->shaded = self->client->shaded;
350
351         if (self->decorations & OB_FRAME_DECOR_BORDER)
352             self->bwidth = self->client->undecorated ?
353                 ob_rr_theme->ubwidth : ob_rr_theme->fbwidth;
354         else
355             self->bwidth = 0;
356
357         if (self->decorations & OB_FRAME_DECOR_BORDER &&
358             !self->client->undecorated)
359         {
360             self->cbwidth_l = self->cbwidth_r = ob_rr_theme->cbwidthx;
361             self->cbwidth_t = self->cbwidth_b = ob_rr_theme->cbwidthy;
362         } else
363             self->cbwidth_l = self->cbwidth_t =
364                 self->cbwidth_r = self->cbwidth_b = 0;
365
366         if (self->max_horz) {
367             self->cbwidth_l = self->cbwidth_r = 0;
368             self->width = self->client->area.width;
369             if (self->max_vert)
370                 self->cbwidth_b = 0;
371         } else
372             self->width = self->client->area.width +
373                 self->cbwidth_l + self->cbwidth_r;
374
375         /* some elements are sized based of the width, so don't let them have
376            negative values */
377         self->width = MAX(self->width,
378                           (ob_rr_theme->grip_width + self->bwidth) * 2 + 1);
379
380         STRUT_SET(self->size,
381                   self->cbwidth_l + (!self->max_horz ? self->bwidth : 0),
382                   self->cbwidth_t +
383                   (!self->max_horz || !self->max_vert ? self->bwidth : 0),
384                   self->cbwidth_r + (!self->max_horz ? self->bwidth : 0),
385                   self->cbwidth_b +
386                   (!self->max_horz || !self->max_vert ? self->bwidth : 0));
387
388         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
389             self->size.top += ob_rr_theme->title_height + self->bwidth;
390         else if (self->max_horz && self->max_vert) {
391             /* A maximized and undecorated window needs a small border on the
392                top of the window to let the user still undecorate/unmaximize the
393                window via the client menu. */
394             /* XXX This size should probably be a theme option. */
395             self->size.top += 1;
396         }
397
398         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
399             ob_rr_theme->handle_height > 0)
400         {
401             self->size.bottom += ob_rr_theme->handle_height + self->bwidth;
402         }
403
404         /* position/size and map/unmap all the windows */
405
406         if (!fake) {
407             gint innercornerheight =
408                 ob_rr_theme->grip_width - self->size.bottom;
409
410             if (self->cbwidth_l) {
411                 XMoveResizeWindow(obt_display, self->innerleft,
412                                   self->size.left - self->cbwidth_l,
413                                   self->size.top,
414                                   self->cbwidth_l, self->client->area.height);
415
416                 XMapWindow(obt_display, self->innerleft);
417             } else
418                 XUnmapWindow(obt_display, self->innerleft);
419
420             if (self->cbwidth_l && innercornerheight > 0) {
421                 XMoveResizeWindow(obt_display, self->innerbll,
422                                   0,
423                                   self->client->area.height - 
424                                   (ob_rr_theme->grip_width -
425                                    self->size.bottom),
426                                   self->cbwidth_l,
427                                   ob_rr_theme->grip_width - self->size.bottom);
428
429                 XMapWindow(obt_display, self->innerbll);
430             } else
431                 XUnmapWindow(obt_display, self->innerbll);
432
433             if (self->cbwidth_r) {
434                 XMoveResizeWindow(obt_display, self->innerright,
435                                   self->size.left + self->client->area.width,
436                                   self->size.top,
437                                   self->cbwidth_r, self->client->area.height);
438
439                 XMapWindow(obt_display, self->innerright);
440             } else
441                 XUnmapWindow(obt_display, self->innerright);
442
443             if (self->cbwidth_r && innercornerheight > 0) {
444                 XMoveResizeWindow(obt_display, self->innerbrr,
445                                   0,
446                                   self->client->area.height - 
447                                   (ob_rr_theme->grip_width -
448                                    self->size.bottom),
449                                   self->cbwidth_r,
450                                   ob_rr_theme->grip_width - self->size.bottom);
451
452                 XMapWindow(obt_display, self->innerbrr);
453             } else
454                 XUnmapWindow(obt_display, self->innerbrr);
455
456             if (self->cbwidth_t) {
457                 XMoveResizeWindow(obt_display, self->innertop,
458                                   self->size.left - self->cbwidth_l,
459                                   self->size.top - self->cbwidth_t,
460                                   self->client->area.width +
461                                   self->cbwidth_l + self->cbwidth_r,
462                                   self->cbwidth_t);
463
464                 XMapWindow(obt_display, self->innertop);
465             } else
466                 XUnmapWindow(obt_display, self->innertop);
467
468             if (self->cbwidth_b) {
469                 XMoveResizeWindow(obt_display, self->innerbottom,
470                                   self->size.left - self->cbwidth_l,
471                                   self->size.top + self->client->area.height,
472                                   self->client->area.width +
473                                   self->cbwidth_l + self->cbwidth_r,
474                                   self->cbwidth_b);
475
476                 XMoveResizeWindow(obt_display, self->innerblb,
477                                   0, 0,
478                                   ob_rr_theme->grip_width + self->bwidth,
479                                   self->cbwidth_b);
480                 XMoveResizeWindow(obt_display, self->innerbrb,
481                                   self->client->area.width +
482                                   self->cbwidth_l + self->cbwidth_r -
483                                   (ob_rr_theme->grip_width + self->bwidth),
484                                   0,
485                                   ob_rr_theme->grip_width + self->bwidth,
486                                   self->cbwidth_b);
487
488                 XMapWindow(obt_display, self->innerbottom);
489                 XMapWindow(obt_display, self->innerblb);
490                 XMapWindow(obt_display, self->innerbrb);
491             } else {
492                 XUnmapWindow(obt_display, self->innerbottom);
493                 XUnmapWindow(obt_display, self->innerblb);
494                 XUnmapWindow(obt_display, self->innerbrb);
495             }
496
497             if (self->bwidth) {
498                 gint titlesides;
499
500                 /* height of titleleft and titleright */
501                 titlesides = (!self->max_horz ? ob_rr_theme->grip_width : 0);
502
503                 XMoveResizeWindow(obt_display, self->titletop,
504                                   ob_rr_theme->grip_width + self->bwidth, 0,
505                                   /* width + bwidth*2 - bwidth*2 - grips*2 */
506                                   self->width - ob_rr_theme->grip_width * 2,
507                                   self->bwidth);
508                 XMoveResizeWindow(obt_display, self->titletopleft,
509                                   0, 0,
510                                   ob_rr_theme->grip_width + self->bwidth,
511                                   self->bwidth);
512                 XMoveResizeWindow(obt_display, self->titletopright,
513                                   self->client->area.width +
514                                   self->size.left + self->size.right -
515                                   ob_rr_theme->grip_width - self->bwidth,
516                                   0,
517                                   ob_rr_theme->grip_width + self->bwidth,
518                                   self->bwidth);
519
520                 if (titlesides > 0) {
521                     XMoveResizeWindow(obt_display, self->titleleft,
522                                       0, self->bwidth,
523                                       self->bwidth,
524                                       titlesides);
525                     XMoveResizeWindow(obt_display, self->titleright,
526                                       self->client->area.width +
527                                       self->size.left + self->size.right -
528                                       self->bwidth,
529                                       self->bwidth,
530                                       self->bwidth,
531                                       titlesides);
532
533                     XMapWindow(obt_display, self->titleleft);
534                     XMapWindow(obt_display, self->titleright);
535                 } else {
536                     XUnmapWindow(obt_display, self->titleleft);
537                     XUnmapWindow(obt_display, self->titleright);
538                 }
539
540                 XMapWindow(obt_display, self->titletop);
541                 XMapWindow(obt_display, self->titletopleft);
542                 XMapWindow(obt_display, self->titletopright);
543
544                 if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
545                     XMoveResizeWindow(obt_display, self->titlebottom,
546                                       (self->max_horz ? 0 : self->bwidth),
547                                       ob_rr_theme->title_height + self->bwidth,
548                                       self->width,
549                                       self->bwidth);
550
551                     XMapWindow(obt_display, self->titlebottom);
552                 } else
553                     XUnmapWindow(obt_display, self->titlebottom);
554             } else {
555                 XUnmapWindow(obt_display, self->titlebottom);
556
557                 XUnmapWindow(obt_display, self->titletop);
558                 XUnmapWindow(obt_display, self->titletopleft);
559                 XUnmapWindow(obt_display, self->titletopright);
560                 XUnmapWindow(obt_display, self->titleleft);
561                 XUnmapWindow(obt_display, self->titleright);
562             }
563
564             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
565                 XMoveResizeWindow(obt_display, self->title,
566                                   (self->max_horz ? 0 : self->bwidth),
567                                   self->bwidth,
568                                   self->width, ob_rr_theme->title_height);
569
570                 XMapWindow(obt_display, self->title);
571
572                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
573                     XMoveResizeWindow(obt_display, self->topresize,
574                                       ob_rr_theme->grip_width,
575                                       0,
576                                       self->width - ob_rr_theme->grip_width *2,
577                                       ob_rr_theme->paddingy + 1);
578
579                     XMoveWindow(obt_display, self->tltresize, 0, 0);
580                     XMoveWindow(obt_display, self->tllresize, 0, 0);
581                     XMoveWindow(obt_display, self->trtresize,
582                                 self->width - ob_rr_theme->grip_width, 0);
583                     XMoveWindow(obt_display, self->trrresize,
584                                 self->width - ob_rr_theme->paddingx - 1, 0);
585
586                     XMapWindow(obt_display, self->topresize);
587                     XMapWindow(obt_display, self->tltresize);
588                     XMapWindow(obt_display, self->tllresize);
589                     XMapWindow(obt_display, self->trtresize);
590                     XMapWindow(obt_display, self->trrresize);
591                 } else {
592                     XUnmapWindow(obt_display, self->topresize);
593                     XUnmapWindow(obt_display, self->tltresize);
594                     XUnmapWindow(obt_display, self->tllresize);
595                     XUnmapWindow(obt_display, self->trtresize);
596                     XUnmapWindow(obt_display, self->trrresize);
597                 }
598             } else
599                 XUnmapWindow(obt_display, self->title);
600         }
601
602         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
603             /* layout the title bar elements */
604             layout_title(self);
605
606         if (!fake) {
607             gint sidebwidth = self->max_horz ? 0 : self->bwidth;
608
609             if (self->bwidth && self->size.bottom) {
610                 XMoveResizeWindow(obt_display, self->handlebottom,
611                                   ob_rr_theme->grip_width +
612                                   self->bwidth + sidebwidth,
613                                   self->size.top + self->client->area.height +
614                                   self->size.bottom - self->bwidth,
615                                   self->width - (ob_rr_theme->grip_width +
616                                                  sidebwidth) * 2,
617                                   self->bwidth);
618
619
620                 if (sidebwidth) {
621                     XMoveResizeWindow(obt_display, self->lgripleft,
622                                       0,
623                                       self->size.top +
624                                       self->client->area.height +
625                                       self->size.bottom -
626                                       (!self->max_horz ?
627                                        ob_rr_theme->grip_width :
628                                        self->size.bottom - self->cbwidth_b),
629                                       self->bwidth,
630                                       (!self->max_horz ?
631                                        ob_rr_theme->grip_width :
632                                        self->size.bottom - self->cbwidth_b));
633                     XMoveResizeWindow(obt_display, self->rgripright,
634                                   self->size.left +
635                                       self->client->area.width +
636                                       self->size.right - self->bwidth,
637                                       self->size.top +
638                                       self->client->area.height +
639                                       self->size.bottom -
640                                       (!self->max_horz ?
641                                        ob_rr_theme->grip_width :
642                                        self->size.bottom - self->cbwidth_b),
643                                       self->bwidth,
644                                       (!self->max_horz ?
645                                        ob_rr_theme->grip_width :
646                                        self->size.bottom - self->cbwidth_b));
647
648                     XMapWindow(obt_display, self->lgripleft);
649                     XMapWindow(obt_display, self->rgripright);
650                 } else {
651                     XUnmapWindow(obt_display, self->lgripleft);
652                     XUnmapWindow(obt_display, self->rgripright);
653                 }
654
655                 XMoveResizeWindow(obt_display, self->lgripbottom,
656                                   sidebwidth,
657                                   self->size.top + self->client->area.height +
658                                   self->size.bottom - self->bwidth,
659                                   ob_rr_theme->grip_width + self->bwidth,
660                                   self->bwidth);
661                 XMoveResizeWindow(obt_display, self->rgripbottom,
662                                   self->size.left + self->client->area.width +
663                                   self->size.right - self->bwidth - sidebwidth-
664                                   ob_rr_theme->grip_width,
665                                   self->size.top + self->client->area.height +
666                                   self->size.bottom - self->bwidth,
667                                   ob_rr_theme->grip_width + self->bwidth,
668                                   self->bwidth);
669
670                 XMapWindow(obt_display, self->handlebottom);
671                 XMapWindow(obt_display, self->lgripbottom);
672                 XMapWindow(obt_display, self->rgripbottom);
673
674                 if (self->decorations & OB_FRAME_DECOR_HANDLE &&
675                     ob_rr_theme->handle_height > 0)
676                 {
677                     XMoveResizeWindow(obt_display, self->handletop,
678                                       ob_rr_theme->grip_width +
679                                       self->bwidth + sidebwidth,
680                                       FRAME_HANDLE_Y(self),
681                                       self->width - (ob_rr_theme->grip_width +
682                                                      sidebwidth) * 2,
683                                       self->bwidth);
684                     XMapWindow(obt_display, self->handletop);
685
686                     if (self->decorations & OB_FRAME_DECOR_GRIPS) {
687                         XMoveResizeWindow(obt_display, self->handleleft,
688                                           ob_rr_theme->grip_width,
689                                           0,
690                                           self->bwidth,
691                                           ob_rr_theme->handle_height);
692                         XMoveResizeWindow(obt_display, self->handleright,
693                                           self->width -
694                                           ob_rr_theme->grip_width -
695                                           self->bwidth,
696                                           0,
697                                           self->bwidth,
698                                           ob_rr_theme->handle_height);
699
700                         XMoveResizeWindow(obt_display, self->lgriptop,
701                                           sidebwidth,
702                                           FRAME_HANDLE_Y(self),
703                                           ob_rr_theme->grip_width +
704                                           self->bwidth,
705                                           self->bwidth);
706                         XMoveResizeWindow(obt_display, self->rgriptop,
707                                           self->size.left +
708                                           self->client->area.width +
709                                           self->size.right - self->bwidth -
710                                           sidebwidth - ob_rr_theme->grip_width,
711                                           FRAME_HANDLE_Y(self),
712                                           ob_rr_theme->grip_width +
713                                           self->bwidth,
714                                           self->bwidth);
715
716                         XMapWindow(obt_display, self->handleleft);
717                         XMapWindow(obt_display, self->handleright);
718                         XMapWindow(obt_display, self->lgriptop);
719                         XMapWindow(obt_display, self->rgriptop);
720                     } else {
721                         XUnmapWindow(obt_display, self->handleleft);
722                         XUnmapWindow(obt_display, self->handleright);
723                         XUnmapWindow(obt_display, self->lgriptop);
724                         XUnmapWindow(obt_display, self->rgriptop);
725                     }
726                 } else {
727                     XUnmapWindow(obt_display, self->handleleft);
728                     XUnmapWindow(obt_display, self->handleright);
729                     XUnmapWindow(obt_display, self->lgriptop);
730                     XUnmapWindow(obt_display, self->rgriptop);
731
732                     XUnmapWindow(obt_display, self->handletop);
733                 }
734             } else {
735                 XUnmapWindow(obt_display, self->handleleft);
736                 XUnmapWindow(obt_display, self->handleright);
737                 XUnmapWindow(obt_display, self->lgriptop);
738                 XUnmapWindow(obt_display, self->rgriptop);
739
740                 XUnmapWindow(obt_display, self->handletop);
741
742                 XUnmapWindow(obt_display, self->handlebottom);
743                 XUnmapWindow(obt_display, self->lgripleft);
744                 XUnmapWindow(obt_display, self->rgripright);
745                 XUnmapWindow(obt_display, self->lgripbottom);
746                 XUnmapWindow(obt_display, self->rgripbottom);
747             }
748
749             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
750                 ob_rr_theme->handle_height > 0)
751             {
752                 XMoveResizeWindow(obt_display, self->handle,
753                                   sidebwidth,
754                                   FRAME_HANDLE_Y(self) + self->bwidth,
755                                   self->width, ob_rr_theme->handle_height);
756                 XMapWindow(obt_display, self->handle);
757
758                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
759                     XMoveResizeWindow(obt_display, self->lgrip,
760                                       0, 0,
761                                       ob_rr_theme->grip_width,
762                                       ob_rr_theme->handle_height);
763                     XMoveResizeWindow(obt_display, self->rgrip,
764                                       self->width - ob_rr_theme->grip_width,
765                                       0,
766                                       ob_rr_theme->grip_width,
767                                       ob_rr_theme->handle_height);
768
769                     XMapWindow(obt_display, self->lgrip);
770                     XMapWindow(obt_display, self->rgrip);
771                 } else {
772                     XUnmapWindow(obt_display, self->lgrip);
773                     XUnmapWindow(obt_display, self->rgrip);
774                 }
775             } else {
776                 XUnmapWindow(obt_display, self->lgrip);
777                 XUnmapWindow(obt_display, self->rgrip);
778
779                 XUnmapWindow(obt_display, self->handle);
780             }
781
782             if (self->bwidth && !self->max_horz &&
783                 (self->client->area.height + self->size.top +
784                  self->size.bottom) > ob_rr_theme->grip_width * 2)
785             {
786                 XMoveResizeWindow(obt_display, self->left,
787                                   0,
788                                   self->bwidth + ob_rr_theme->grip_width,
789                                   self->bwidth,
790                                   self->client->area.height +
791                                   self->size.top + self->size.bottom -
792                                   ob_rr_theme->grip_width * 2);
793
794                 XMapWindow(obt_display, self->left);
795             } else
796                 XUnmapWindow(obt_display, self->left);
797
798             if (self->bwidth && !self->max_horz &&
799                 (self->client->area.height + self->size.top +
800                  self->size.bottom) > ob_rr_theme->grip_width * 2)
801             {
802                 XMoveResizeWindow(obt_display, self->right,
803                                   self->client->area.width + self->cbwidth_l +
804                                   self->cbwidth_r + self->bwidth,
805                                   self->bwidth + ob_rr_theme->grip_width,
806                                   self->bwidth,
807                                   self->client->area.height +
808                                   self->size.top + self->size.bottom -
809                                   ob_rr_theme->grip_width * 2);
810
811                 XMapWindow(obt_display, self->right);
812             } else
813                 XUnmapWindow(obt_display, self->right);
814
815             XMoveResizeWindow(obt_display, self->backback,
816                               self->size.left, self->size.top,
817                               self->client->area.width,
818                               self->client->area.height);
819         }
820     }
821
822     /* shading can change without being moved or resized */
823     RECT_SET_SIZE(self->area,
824                   self->client->area.width +
825                   self->size.left + self->size.right,
826                   (self->client->shaded ?
827                    ob_rr_theme->title_height + self->bwidth * 2:
828                    self->client->area.height +
829                    self->size.top + self->size.bottom));
830
831     if ((moved || resized) && !fake) {
832         /* find the new coordinates, done after setting the frame.size, for
833            frame_client_gravity. */
834         self->area.x = self->client->area.x;
835         self->area.y = self->client->area.y;
836         frame_client_gravity(self, &self->area.x, &self->area.y);
837     }
838
839     if (!fake) {
840         if (!frame_iconify_animating(self))
841             /* move and resize the top level frame.
842                shading can change without being moved or resized.
843
844                but don't do this during an iconify animation. it will be
845                reflected afterwards.
846             */
847             XMoveResizeWindow(obt_display, self->window,
848                               self->area.x,
849                               self->area.y,
850                               self->area.width,
851                               self->area.height);
852
853         /* when the client has StaticGravity, it likes to move around.
854            also this correctly positions the client when it maps.
855            this also needs to be run when the frame's decorations sizes change!
856         */
857         XMoveWindow(obt_display, self->client->window,
858                     self->size.left, self->size.top);
859
860         if (resized) {
861             self->need_render = TRUE;
862             framerender_frame(self);
863             frame_adjust_shape(self);
864         }
865
866         if (!STRUT_EQUAL(self->size, self->oldsize)) {
867             gulong vals[4];
868             vals[0] = self->size.left;
869             vals[1] = self->size.right;
870             vals[2] = self->size.top;
871             vals[3] = self->size.bottom;
872             OBT_PROP_SETA32(self->client->window, NET_FRAME_EXTENTS,
873                             CARDINAL, vals, 4);
874             OBT_PROP_SETA32(self->client->window, KDE_NET_WM_FRAME_STRUT,
875                             CARDINAL, vals, 4);
876             self->oldsize = self->size;
877         }
878
879         /* if this occurs while we are focus cycling, the indicator needs to
880            match the changes */
881         if (focus_cycle_target == self->client)
882             focus_cycle_update_indicator(self->client);
883     }
884     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR) &&
885         self->label_width)
886     {
887         XResizeWindow(obt_display, self->label, self->label_width,
888                       ob_rr_theme->label_height);
889     }
890 }
891
892 static void frame_adjust_cursors(ObFrame *self)
893 {
894     if ((self->functions & OB_CLIENT_FUNC_RESIZE) !=
895         (self->client->functions & OB_CLIENT_FUNC_RESIZE) ||
896         self->max_horz != self->client->max_horz ||
897         self->max_vert != self->client->max_vert ||
898         self->shaded != self->client->shaded)
899     {
900         gboolean r = (self->client->functions & OB_CLIENT_FUNC_RESIZE) &&
901             !(self->client->max_horz && self->client->max_vert);
902         gboolean topbot = !self->client->max_vert;
903         gboolean sh = self->client->shaded;
904         XSetWindowAttributes a;
905
906         /* these ones turn off when max vert, and some when shaded */
907         a.cursor = ob_cursor(r && topbot && !sh ?
908                              OB_CURSOR_NORTH : OB_CURSOR_NONE);
909         XChangeWindowAttributes(obt_display, self->topresize, CWCursor, &a);
910         XChangeWindowAttributes(obt_display, self->titletop, CWCursor, &a);
911         a.cursor = ob_cursor(r && topbot ? OB_CURSOR_SOUTH : OB_CURSOR_NONE);
912         XChangeWindowAttributes(obt_display, self->handle, CWCursor, &a);
913         XChangeWindowAttributes(obt_display, self->handletop, CWCursor, &a);
914         XChangeWindowAttributes(obt_display, self->handlebottom, CWCursor, &a);
915         XChangeWindowAttributes(obt_display, self->innerbottom, CWCursor, &a);
916
917         /* these ones change when shaded */
918         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_WEST : OB_CURSOR_NORTHWEST) :
919                              OB_CURSOR_NONE);
920         XChangeWindowAttributes(obt_display, self->titleleft, CWCursor, &a);
921         XChangeWindowAttributes(obt_display, self->tltresize, CWCursor, &a);
922         XChangeWindowAttributes(obt_display, self->tllresize, CWCursor, &a);
923         XChangeWindowAttributes(obt_display, self->titletopleft, CWCursor, &a);
924         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_EAST : OB_CURSOR_NORTHEAST) :
925                              OB_CURSOR_NONE);
926         XChangeWindowAttributes(obt_display, self->titleright, CWCursor, &a);
927         XChangeWindowAttributes(obt_display, self->trtresize, CWCursor, &a);
928         XChangeWindowAttributes(obt_display, self->trrresize, CWCursor, &a);
929         XChangeWindowAttributes(obt_display, self->titletopright, CWCursor,&a);
930
931         /* these ones are pretty static */
932         a.cursor = ob_cursor(r ? OB_CURSOR_WEST : OB_CURSOR_NONE);
933         XChangeWindowAttributes(obt_display, self->left, CWCursor, &a);
934         XChangeWindowAttributes(obt_display, self->innerleft, CWCursor, &a);
935         a.cursor = ob_cursor(r ? OB_CURSOR_EAST : OB_CURSOR_NONE);
936         XChangeWindowAttributes(obt_display, self->right, CWCursor, &a);
937         XChangeWindowAttributes(obt_display, self->innerright, CWCursor, &a);
938         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHWEST : OB_CURSOR_NONE);
939         XChangeWindowAttributes(obt_display, self->lgrip, CWCursor, &a);
940         XChangeWindowAttributes(obt_display, self->handleleft, CWCursor, &a);
941         XChangeWindowAttributes(obt_display, self->lgripleft, CWCursor, &a);
942         XChangeWindowAttributes(obt_display, self->lgriptop, CWCursor, &a);
943         XChangeWindowAttributes(obt_display, self->lgripbottom, CWCursor, &a);
944         XChangeWindowAttributes(obt_display, self->innerbll, CWCursor, &a);
945         XChangeWindowAttributes(obt_display, self->innerblb, CWCursor, &a);
946         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHEAST : OB_CURSOR_NONE);
947         XChangeWindowAttributes(obt_display, self->rgrip, CWCursor, &a);
948         XChangeWindowAttributes(obt_display, self->handleright, CWCursor, &a);
949         XChangeWindowAttributes(obt_display, self->rgripright, CWCursor, &a);
950         XChangeWindowAttributes(obt_display, self->rgriptop, CWCursor, &a);
951         XChangeWindowAttributes(obt_display, self->rgripbottom, CWCursor, &a);
952         XChangeWindowAttributes(obt_display, self->innerbrr, CWCursor, &a);
953         XChangeWindowAttributes(obt_display, self->innerbrb, CWCursor, &a);
954     }
955 }
956
957 void frame_adjust_client_area(ObFrame *self)
958 {
959     /* adjust the window which is there to prevent flashing on unmap */
960     XMoveResizeWindow(obt_display, self->backfront, 0, 0,
961                       self->client->area.width,
962                       self->client->area.height);
963 }
964
965 void frame_adjust_state(ObFrame *self)
966 {
967     self->need_render = TRUE;
968     framerender_frame(self);
969 }
970
971 void frame_adjust_focus(ObFrame *self, gboolean hilite)
972 {
973     ob_debug_type(OB_DEBUG_FOCUS,
974                   "Frame for 0x%x has focus: %d",
975                   self->client->window, hilite);
976     self->focused = hilite;
977     self->need_render = TRUE;
978     framerender_frame(self);
979     XFlush(obt_display);
980 }
981
982 void frame_adjust_title(ObFrame *self)
983 {
984     self->need_render = TRUE;
985     framerender_frame(self);
986 }
987
988 void frame_adjust_icon(ObFrame *self)
989 {
990     self->need_render = TRUE;
991     framerender_frame(self);
992 }
993
994 void frame_grab_client(ObFrame *self)
995 {
996     /* DO NOT map the client window here. we used to do that, but it is bogus.
997        we need to set up the client's dimensions and everything before we
998        send a mapnotify or we create race conditions.
999     */
1000
1001     /* reparent the client to the frame */
1002     XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
1003
1004     /*
1005       When reparenting the client window, it is usually not mapped yet, since
1006       this occurs from a MapRequest. However, in the case where Openbox is
1007       starting up, the window is already mapped, so we'll see an unmap event
1008       for it.
1009     */
1010     if (ob_state() == OB_STATE_STARTING)
1011         ++self->client->ignore_unmaps;
1012
1013     /* select the event mask on the client's parent (to receive config/map
1014        req's) the ButtonPress is to catch clicks on the client border */
1015     XSelectInput(obt_display, self->window, FRAME_EVENTMASK);
1016
1017     /* set all the windows for the frame in the window_map */
1018     window_add(&self->window, CLIENT_AS_WINDOW(self->client));
1019     window_add(&self->backback, CLIENT_AS_WINDOW(self->client));
1020     window_add(&self->backfront, CLIENT_AS_WINDOW(self->client));
1021     window_add(&self->innerleft, CLIENT_AS_WINDOW(self->client));
1022     window_add(&self->innertop, CLIENT_AS_WINDOW(self->client));
1023     window_add(&self->innerright, CLIENT_AS_WINDOW(self->client));
1024     window_add(&self->innerbottom, CLIENT_AS_WINDOW(self->client));
1025     window_add(&self->innerblb, CLIENT_AS_WINDOW(self->client));
1026     window_add(&self->innerbll, CLIENT_AS_WINDOW(self->client));
1027     window_add(&self->innerbrb, CLIENT_AS_WINDOW(self->client));
1028     window_add(&self->innerbrr, CLIENT_AS_WINDOW(self->client));
1029     window_add(&self->title, CLIENT_AS_WINDOW(self->client));
1030     window_add(&self->label, CLIENT_AS_WINDOW(self->client));
1031     window_add(&self->max, CLIENT_AS_WINDOW(self->client));
1032     window_add(&self->close, CLIENT_AS_WINDOW(self->client));
1033     window_add(&self->desk, CLIENT_AS_WINDOW(self->client));
1034     window_add(&self->shade, CLIENT_AS_WINDOW(self->client));
1035     window_add(&self->icon, CLIENT_AS_WINDOW(self->client));
1036     window_add(&self->iconify, CLIENT_AS_WINDOW(self->client));
1037     window_add(&self->handle, CLIENT_AS_WINDOW(self->client));
1038     window_add(&self->lgrip, CLIENT_AS_WINDOW(self->client));
1039     window_add(&self->rgrip, CLIENT_AS_WINDOW(self->client));
1040     window_add(&self->topresize, CLIENT_AS_WINDOW(self->client));
1041     window_add(&self->tltresize, CLIENT_AS_WINDOW(self->client));
1042     window_add(&self->tllresize, CLIENT_AS_WINDOW(self->client));
1043     window_add(&self->trtresize, CLIENT_AS_WINDOW(self->client));
1044     window_add(&self->trrresize, CLIENT_AS_WINDOW(self->client));
1045     window_add(&self->left, CLIENT_AS_WINDOW(self->client));
1046     window_add(&self->right, CLIENT_AS_WINDOW(self->client));
1047     window_add(&self->titleleft, CLIENT_AS_WINDOW(self->client));
1048     window_add(&self->titletop, CLIENT_AS_WINDOW(self->client));
1049     window_add(&self->titletopleft, CLIENT_AS_WINDOW(self->client));
1050     window_add(&self->titletopright, CLIENT_AS_WINDOW(self->client));
1051     window_add(&self->titleright, CLIENT_AS_WINDOW(self->client));
1052     window_add(&self->titlebottom, CLIENT_AS_WINDOW(self->client));
1053     window_add(&self->handleleft, CLIENT_AS_WINDOW(self->client));
1054     window_add(&self->handletop, CLIENT_AS_WINDOW(self->client));
1055     window_add(&self->handleright, CLIENT_AS_WINDOW(self->client));
1056     window_add(&self->handlebottom, CLIENT_AS_WINDOW(self->client));
1057     window_add(&self->lgripleft, CLIENT_AS_WINDOW(self->client));
1058     window_add(&self->lgriptop, CLIENT_AS_WINDOW(self->client));
1059     window_add(&self->lgripbottom, CLIENT_AS_WINDOW(self->client));
1060     window_add(&self->rgripright, CLIENT_AS_WINDOW(self->client));
1061     window_add(&self->rgriptop, CLIENT_AS_WINDOW(self->client));
1062     window_add(&self->rgripbottom, CLIENT_AS_WINDOW(self->client));
1063 }
1064
1065 static gboolean find_reparent(XEvent *e, gpointer data)
1066 {
1067     const ObFrame *self = data;
1068
1069     /* Find ReparentNotify events for the window that aren't being reparented into the
1070        frame, thus the client reparenting itself off the frame. */
1071     return e->type == ReparentNotify && e->xreparent.window == self->client->window &&
1072         e->xreparent.parent != self->window;
1073 }
1074
1075 void frame_release_client(ObFrame *self)
1076 {
1077     /* if there was any animation going on, kill it */
1078     if (self->iconify_animation_timer)
1079         g_source_remove(self->iconify_animation_timer);
1080
1081     /* check if the app has already reparented its window away */
1082     if (!xqueue_exists_local(find_reparent, self)) {
1083         /* according to the ICCCM - if the client doesn't reparent itself,
1084            then we will reparent the window to root for them */
1085         XReparentWindow(obt_display, self->client->window, obt_root(ob_screen),
1086                         self->client->area.x, self->client->area.y);
1087     }
1088
1089     /* remove all the windows for the frame from the window_map */
1090     window_remove(self->window);
1091     window_remove(self->backback);
1092     window_remove(self->backfront);
1093     window_remove(self->innerleft);
1094     window_remove(self->innertop);
1095     window_remove(self->innerright);
1096     window_remove(self->innerbottom);
1097     window_remove(self->innerblb);
1098     window_remove(self->innerbll);
1099     window_remove(self->innerbrb);
1100     window_remove(self->innerbrr);
1101     window_remove(self->title);
1102     window_remove(self->label);
1103     window_remove(self->max);
1104     window_remove(self->close);
1105     window_remove(self->desk);
1106     window_remove(self->shade);
1107     window_remove(self->icon);
1108     window_remove(self->iconify);
1109     window_remove(self->handle);
1110     window_remove(self->lgrip);
1111     window_remove(self->rgrip);
1112     window_remove(self->topresize);
1113     window_remove(self->tltresize);
1114     window_remove(self->tllresize);
1115     window_remove(self->trtresize);
1116     window_remove(self->trrresize);
1117     window_remove(self->left);
1118     window_remove(self->right);
1119     window_remove(self->titleleft);
1120     window_remove(self->titletop);
1121     window_remove(self->titletopleft);
1122     window_remove(self->titletopright);
1123     window_remove(self->titleright);
1124     window_remove(self->titlebottom);
1125     window_remove(self->handleleft);
1126     window_remove(self->handletop);
1127     window_remove(self->handleright);
1128     window_remove(self->handlebottom);
1129     window_remove(self->lgripleft);
1130     window_remove(self->lgriptop);
1131     window_remove(self->lgripbottom);
1132     window_remove(self->rgripright);
1133     window_remove(self->rgriptop);
1134     window_remove(self->rgripbottom);
1135
1136     if (self->flash_timer) g_source_remove(self->flash_timer);
1137 }
1138
1139 /* is there anything present between us and the label? */
1140 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
1141     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
1142         if (*lc == ' ') continue; /* it was invalid */
1143         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
1144             return TRUE;
1145         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
1146             return TRUE;
1147         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
1148             return TRUE;
1149         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
1150             return TRUE;
1151         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
1152             return TRUE;
1153         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
1154             return TRUE;
1155         if (*lc == 'L') return FALSE;
1156     }
1157     return FALSE;
1158 }
1159
1160 static void place_button(ObFrame *self, const char *lc, gint bwidth,
1161                          gint left, gint i,
1162                          gint *x, gint *button_on, gint *button_x)
1163 {
1164   if (!(*button_on = is_button_present(self, lc, i)))
1165     return;
1166
1167   self->label_width -= bwidth;
1168   if (i > 0)
1169     *button_x = *x;
1170   *x += i * bwidth;
1171   if (i < 0) {
1172     if (self->label_x <= left || *x > self->label_x) {
1173       *button_x = *x;
1174     } else {
1175       /* the button would have been drawn on top of another button */
1176       *button_on = FALSE;
1177       self->label_width += bwidth;
1178     }
1179   }
1180 }
1181
1182 static void layout_title(ObFrame *self)
1183 {
1184     gchar *lc;
1185     gint i;
1186
1187     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
1188     /* position of the leftmost button */
1189     const gint left = ob_rr_theme->paddingx + 1;
1190     /* position of the rightmost button */
1191     const gint right = self->width;
1192
1193     /* turn them all off */
1194     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
1195         self->max_on = self->close_on = self->label_on = FALSE;
1196     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
1197     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
1198
1199     /* figure out what's being shown, find each element's position, and the
1200        width of the label
1201
1202        do the ones before the label, then after the label,
1203        i will be +1 the first time through when working to the left,
1204        and -1 the second time through when working to the right */
1205     for (i = 1; i >= -1; i-=2) {
1206         gint x;
1207         ObFrameContext *firstcon;
1208
1209         if (i > 0) {
1210             x = left;
1211             lc = config_title_layout;
1212             firstcon = &self->leftmost;
1213         } else {
1214             x = right;
1215             lc = config_title_layout + strlen(config_title_layout)-1;
1216             firstcon = &self->rightmost;
1217         }
1218
1219         /* stop at the end of the string (or the label, which calls break) */
1220         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
1221             if (*lc == 'L') {
1222                 if (i > 0) {
1223                     self->label_on = TRUE;
1224                     self->label_x = x;
1225                 }
1226                 break; /* break the for loop, do other side of label */
1227             } else if (*lc == 'N') {
1228                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
1229                 /* icon is bigger than buttons */
1230                 place_button(self, lc, bwidth + 2, left, i, &x, &self->icon_on, &self->icon_x);
1231             } else if (*lc == 'D') {
1232                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
1233                 place_button(self, lc, bwidth, left, i, &x, &self->desk_on, &self->desk_x);
1234             } else if (*lc == 'S') {
1235                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
1236                 place_button(self, lc, bwidth, left, i, &x, &self->shade_on, &self->shade_x);
1237             } else if (*lc == 'I') {
1238                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
1239                 place_button(self, lc, bwidth, left, i, &x, &self->iconify_on, &self->iconify_x);
1240             } else if (*lc == 'M') {
1241                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
1242                 place_button(self, lc, bwidth, left, i, &x, &self->max_on, &self->max_x);
1243             } else if (*lc == 'C') {
1244                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
1245                 place_button(self, lc, bwidth, left, i, &x, &self->close_on, &self->close_x);
1246             } else
1247                 continue; /* don't set firstcon */
1248             firstcon = NULL;
1249         }
1250     }
1251
1252     /* position and map the elements */
1253     if (self->icon_on) {
1254         XMapWindow(obt_display, self->icon);
1255         XMoveWindow(obt_display, self->icon, self->icon_x,
1256                     ob_rr_theme->paddingy);
1257     } else
1258         XUnmapWindow(obt_display, self->icon);
1259
1260     if (self->desk_on) {
1261         XMapWindow(obt_display, self->desk);
1262         XMoveWindow(obt_display, self->desk, self->desk_x,
1263                     ob_rr_theme->paddingy + 1);
1264     } else
1265         XUnmapWindow(obt_display, self->desk);
1266
1267     if (self->shade_on) {
1268         XMapWindow(obt_display, self->shade);
1269         XMoveWindow(obt_display, self->shade, self->shade_x,
1270                     ob_rr_theme->paddingy + 1);
1271     } else
1272         XUnmapWindow(obt_display, self->shade);
1273
1274     if (self->iconify_on) {
1275         XMapWindow(obt_display, self->iconify);
1276         XMoveWindow(obt_display, self->iconify, self->iconify_x,
1277                     ob_rr_theme->paddingy + 1);
1278     } else
1279         XUnmapWindow(obt_display, self->iconify);
1280
1281     if (self->max_on) {
1282         XMapWindow(obt_display, self->max);
1283         XMoveWindow(obt_display, self->max, self->max_x,
1284                     ob_rr_theme->paddingy + 1);
1285     } else
1286         XUnmapWindow(obt_display, self->max);
1287
1288     if (self->close_on) {
1289         XMapWindow(obt_display, self->close);
1290         XMoveWindow(obt_display, self->close, self->close_x,
1291                     ob_rr_theme->paddingy + 1);
1292     } else
1293         XUnmapWindow(obt_display, self->close);
1294
1295     if (self->label_on && self->label_width > 0) {
1296         XMapWindow(obt_display, self->label);
1297         XMoveWindow(obt_display, self->label, self->label_x,
1298                     ob_rr_theme->paddingy);
1299     } else
1300         XUnmapWindow(obt_display, self->label);
1301 }
1302
1303 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
1304 {
1305     gchar *p, *n;
1306
1307     if (!*names) /* empty string */
1308         return FALSE;
1309
1310     /* find the first space */
1311     for (p = names; *p; p = g_utf8_next_char(p)) {
1312         const gunichar c = g_utf8_get_char(p);
1313         if (g_unichar_isspace(c)) break;
1314     }
1315
1316     if (p == names) {
1317         /* leading spaces in the string */
1318         n = g_utf8_next_char(names);
1319         if (!frame_next_context_from_string(n, cx))
1320             return FALSE;
1321     } else {
1322         n = p;
1323         if (*p) {
1324             /* delete the space with null zero(s) */
1325             while (n < g_utf8_next_char(p))
1326                 *(n++) = '\0';
1327         }
1328
1329         *cx = frame_context_from_string(names);
1330
1331         /* find the next non-space */
1332         for (; *n; n = g_utf8_next_char(n)) {
1333             const gunichar c = g_utf8_get_char(n);
1334             if (!g_unichar_isspace(c)) break;
1335         }
1336     }
1337
1338     /* delete everything we just read (copy everything at n to the start of
1339        the string */
1340     for (p = names; *n; ++p, ++n)
1341         *p = *n;
1342     *p = *n;
1343
1344     return TRUE;
1345 }
1346
1347 ObFrameContext frame_context_from_string(const gchar *name)
1348 {
1349     if (!g_ascii_strcasecmp("Desktop", name))
1350         return OB_FRAME_CONTEXT_DESKTOP;
1351     else if (!g_ascii_strcasecmp("Root", name))
1352         return OB_FRAME_CONTEXT_ROOT;
1353     else if (!g_ascii_strcasecmp("Client", name))
1354         return OB_FRAME_CONTEXT_CLIENT;
1355     else if (!g_ascii_strcasecmp("Titlebar", name))
1356         return OB_FRAME_CONTEXT_TITLEBAR;
1357     else if (!g_ascii_strcasecmp("Frame", name))
1358         return OB_FRAME_CONTEXT_FRAME;
1359     else if (!g_ascii_strcasecmp("TLCorner", name))
1360         return OB_FRAME_CONTEXT_TLCORNER;
1361     else if (!g_ascii_strcasecmp("TRCorner", name))
1362         return OB_FRAME_CONTEXT_TRCORNER;
1363     else if (!g_ascii_strcasecmp("BLCorner", name))
1364         return OB_FRAME_CONTEXT_BLCORNER;
1365     else if (!g_ascii_strcasecmp("BRCorner", name))
1366         return OB_FRAME_CONTEXT_BRCORNER;
1367     else if (!g_ascii_strcasecmp("Top", name))
1368         return OB_FRAME_CONTEXT_TOP;
1369     else if (!g_ascii_strcasecmp("Bottom", name))
1370         return OB_FRAME_CONTEXT_BOTTOM;
1371     else if (!g_ascii_strcasecmp("Left", name))
1372         return OB_FRAME_CONTEXT_LEFT;
1373     else if (!g_ascii_strcasecmp("Right", name))
1374         return OB_FRAME_CONTEXT_RIGHT;
1375     else if (!g_ascii_strcasecmp("Maximize", name))
1376         return OB_FRAME_CONTEXT_MAXIMIZE;
1377     else if (!g_ascii_strcasecmp("AllDesktops", name))
1378         return OB_FRAME_CONTEXT_ALLDESKTOPS;
1379     else if (!g_ascii_strcasecmp("Shade", name))
1380         return OB_FRAME_CONTEXT_SHADE;
1381     else if (!g_ascii_strcasecmp("Iconify", name))
1382         return OB_FRAME_CONTEXT_ICONIFY;
1383     else if (!g_ascii_strcasecmp("Icon", name))
1384         return OB_FRAME_CONTEXT_ICON;
1385     else if (!g_ascii_strcasecmp("Close", name))
1386         return OB_FRAME_CONTEXT_CLOSE;
1387     else if (!g_ascii_strcasecmp("MoveResize", name))
1388         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1389     else if (!g_ascii_strcasecmp("Dock", name))
1390         return OB_FRAME_CONTEXT_DOCK;
1391     else if (!g_ascii_strcasecmp("ScreenTop", name))
1392         return OB_FRAME_CONTEXT_EDGE_TOP;
1393     else if (!g_ascii_strcasecmp("ScreenTopRight", name))
1394         return OB_FRAME_CONTEXT_EDGE_TOPRIGHT;
1395     else if (!g_ascii_strcasecmp("ScreenRight", name))
1396         return OB_FRAME_CONTEXT_EDGE_RIGHT;
1397     else if (!g_ascii_strcasecmp("ScreenBottomRight", name))
1398         return OB_FRAME_CONTEXT_EDGE_BOTTOMRIGHT;
1399     else if (!g_ascii_strcasecmp("ScreenBottom", name))
1400         return OB_FRAME_CONTEXT_EDGE_BOTTOM;
1401     else if (!g_ascii_strcasecmp("ScreenBottomLeft", name))
1402         return OB_FRAME_CONTEXT_EDGE_BOTTOMLEFT;
1403     else if (!g_ascii_strcasecmp("ScreenLeft", name))
1404         return OB_FRAME_CONTEXT_EDGE_LEFT;
1405     else if (!g_ascii_strcasecmp("ScreenTopLeft", name))
1406         return OB_FRAME_CONTEXT_EDGE_TOPLEFT;
1407
1408     return OB_FRAME_CONTEXT_NONE;
1409 }
1410
1411 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
1412 {
1413     ObFrame *self;
1414     ObWindow *obwin;
1415
1416     if (moveresize_in_progress)
1417         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1418     if (win == obt_root(ob_screen))
1419         return OB_FRAME_CONTEXT_ROOT;
1420     if ((obwin = window_find(win))) {
1421         if (WINDOW_IS_DOCK(obwin)) {
1422           return OB_FRAME_CONTEXT_DOCK;
1423         } else if (WINDOW_IS_EDGE(obwin)) {
1424           ObEdge *edge = (ObEdge *)obwin;
1425           return OB_FRAME_CONTEXT_EDGE_TOP + edge->location;
1426         }
1427     }
1428     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
1429     if (win == client->window) {
1430         /* conceptually, this is the desktop, as far as users are
1431            concerned */
1432         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1433             return OB_FRAME_CONTEXT_DESKTOP;
1434         return OB_FRAME_CONTEXT_CLIENT;
1435     }
1436
1437     self = client->frame;
1438
1439     /* when the user clicks in the corners of the titlebar and the client
1440        is fully maximized, then treat it like they clicked in the
1441        button that is there */
1442     if (self->max_horz && self->max_vert &&
1443         (win == self->title || win == self->titletop ||
1444          win == self->titleleft || win == self->titletopleft ||
1445          win == self->titleright || win == self->titletopright))
1446     {
1447         /* get the mouse coords in reference to the whole frame */
1448         gint fx = x;
1449         gint fy = y;
1450
1451         /* these windows are down a border width from the top of the frame */
1452         if (win == self->title ||
1453             win == self->titleleft || win == self->titleright)
1454             fy += self->bwidth;
1455
1456         /* title is a border width in from the edge */
1457         if (win == self->title)
1458             fx += self->bwidth;
1459         /* titletop is a bit to the right */
1460         else if (win == self->titletop)
1461             fx += ob_rr_theme->grip_width + self->bwidth;
1462         /* titletopright is way to the right edge */
1463         else if (win == self->titletopright)
1464             fx += self->area.width - (ob_rr_theme->grip_width + self->bwidth);
1465         /* titleright is even more way to the right edge */
1466         else if (win == self->titleright)
1467             fx += self->area.width - self->bwidth;
1468
1469         /* figure out if we're over the area that should be considered a
1470            button */
1471         if (fy < self->bwidth + ob_rr_theme->paddingy + 1 +
1472             ob_rr_theme->button_size)
1473         {
1474             if (fx < (self->bwidth + ob_rr_theme->paddingx + 1 +
1475                       ob_rr_theme->button_size))
1476             {
1477                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
1478                     return self->leftmost;
1479             }
1480             else if (fx >= (self->area.width -
1481                             (self->bwidth + ob_rr_theme->paddingx + 1 +
1482                              ob_rr_theme->button_size)))
1483             {
1484                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
1485                     return self->rightmost;
1486             }
1487         }
1488
1489         /* there is no resizing maximized windows so make them the titlebar
1490            context */
1491         return OB_FRAME_CONTEXT_TITLEBAR;
1492     }
1493     else if (self->max_vert &&
1494              (win == self->titletop || win == self->topresize))
1495         /* can't resize vertically when max vert */
1496         return OB_FRAME_CONTEXT_TITLEBAR;
1497     else if (self->shaded &&
1498              (win == self->titletop || win == self->topresize))
1499         /* can't resize vertically when shaded */
1500         return OB_FRAME_CONTEXT_TITLEBAR;
1501
1502     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
1503     if (win == self->label)             return OB_FRAME_CONTEXT_TITLEBAR;
1504     if (win == self->handle)            return OB_FRAME_CONTEXT_BOTTOM;
1505     if (win == self->handletop)         return OB_FRAME_CONTEXT_BOTTOM;
1506     if (win == self->handlebottom)      return OB_FRAME_CONTEXT_BOTTOM;
1507     if (win == self->handleleft)        return OB_FRAME_CONTEXT_BLCORNER;
1508     if (win == self->lgrip)             return OB_FRAME_CONTEXT_BLCORNER;
1509     if (win == self->lgripleft)         return OB_FRAME_CONTEXT_BLCORNER;
1510     if (win == self->lgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1511     if (win == self->lgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1512     if (win == self->handleright)       return OB_FRAME_CONTEXT_BRCORNER;
1513     if (win == self->rgrip)             return OB_FRAME_CONTEXT_BRCORNER;
1514     if (win == self->rgripright)        return OB_FRAME_CONTEXT_BRCORNER;
1515     if (win == self->rgriptop)          return OB_FRAME_CONTEXT_BRCORNER;
1516     if (win == self->rgripbottom)       return OB_FRAME_CONTEXT_BRCORNER;
1517     if (win == self->title)             return OB_FRAME_CONTEXT_TITLEBAR;
1518     if (win == self->titlebottom)       return OB_FRAME_CONTEXT_TITLEBAR;
1519     if (win == self->titleleft)         return OB_FRAME_CONTEXT_TLCORNER;
1520     if (win == self->titletopleft)      return OB_FRAME_CONTEXT_TLCORNER;
1521     if (win == self->titleright)        return OB_FRAME_CONTEXT_TRCORNER;
1522     if (win == self->titletopright)     return OB_FRAME_CONTEXT_TRCORNER;
1523     if (win == self->titletop)          return OB_FRAME_CONTEXT_TOP;
1524     if (win == self->topresize)         return OB_FRAME_CONTEXT_TOP;
1525     if (win == self->tltresize)         return OB_FRAME_CONTEXT_TLCORNER;
1526     if (win == self->tllresize)         return OB_FRAME_CONTEXT_TLCORNER;
1527     if (win == self->trtresize)         return OB_FRAME_CONTEXT_TRCORNER;
1528     if (win == self->trrresize)         return OB_FRAME_CONTEXT_TRCORNER;
1529     if (win == self->left)              return OB_FRAME_CONTEXT_LEFT;
1530     if (win == self->right)             return OB_FRAME_CONTEXT_RIGHT;
1531     if (win == self->innertop)          return OB_FRAME_CONTEXT_TITLEBAR;
1532     if (win == self->innerleft)         return OB_FRAME_CONTEXT_LEFT;
1533     if (win == self->innerbottom)       return OB_FRAME_CONTEXT_BOTTOM;
1534     if (win == self->innerright)        return OB_FRAME_CONTEXT_RIGHT;
1535     if (win == self->innerbll)          return OB_FRAME_CONTEXT_BLCORNER;
1536     if (win == self->innerblb)          return OB_FRAME_CONTEXT_BLCORNER;
1537     if (win == self->innerbrr)          return OB_FRAME_CONTEXT_BRCORNER;
1538     if (win == self->innerbrb)          return OB_FRAME_CONTEXT_BRCORNER;
1539     if (win == self->max)               return OB_FRAME_CONTEXT_MAXIMIZE;
1540     if (win == self->iconify)           return OB_FRAME_CONTEXT_ICONIFY;
1541     if (win == self->close)             return OB_FRAME_CONTEXT_CLOSE;
1542     if (win == self->icon)              return OB_FRAME_CONTEXT_ICON;
1543     if (win == self->desk)              return OB_FRAME_CONTEXT_ALLDESKTOPS;
1544     if (win == self->shade)             return OB_FRAME_CONTEXT_SHADE;
1545
1546     return OB_FRAME_CONTEXT_NONE;
1547 }
1548
1549 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
1550 {
1551     /* horizontal */
1552     switch (self->client->gravity) {
1553     default:
1554     case NorthWestGravity:
1555     case SouthWestGravity:
1556     case WestGravity:
1557         break;
1558
1559     case NorthGravity:
1560     case SouthGravity:
1561     case CenterGravity:
1562         /* the middle of the client will be the middle of the frame */
1563         *x -= (self->size.right - self->size.left) / 2;
1564         break;
1565
1566     case NorthEastGravity:
1567     case SouthEastGravity:
1568     case EastGravity:
1569         /* the right side of the client will be the right side of the frame */
1570         *x -= self->size.right + self->size.left -
1571             self->client->border_width * 2;
1572         break;
1573
1574     case ForgetGravity:
1575     case StaticGravity:
1576         /* the client's position won't move */
1577         *x -= self->size.left - self->client->border_width;
1578         break;
1579     }
1580
1581     /* vertical */
1582     switch (self->client->gravity) {
1583     default:
1584     case NorthWestGravity:
1585     case NorthEastGravity:
1586     case NorthGravity:
1587         break;
1588
1589     case CenterGravity:
1590     case EastGravity:
1591     case WestGravity:
1592         /* the middle of the client will be the middle of the frame */
1593         *y -= (self->size.bottom - self->size.top) / 2;
1594         break;
1595
1596     case SouthWestGravity:
1597     case SouthEastGravity:
1598     case SouthGravity:
1599         /* the bottom of the client will be the bottom of the frame */
1600         *y -= self->size.bottom + self->size.top -
1601             self->client->border_width * 2;
1602         break;
1603
1604     case ForgetGravity:
1605     case StaticGravity:
1606         /* the client's position won't move */
1607         *y -= self->size.top - self->client->border_width;
1608         break;
1609     }
1610 }
1611
1612 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
1613 {
1614     /* horizontal */
1615     switch (self->client->gravity) {
1616     default:
1617     case NorthWestGravity:
1618     case WestGravity:
1619     case SouthWestGravity:
1620         break;
1621     case NorthGravity:
1622     case CenterGravity:
1623     case SouthGravity:
1624         /* the middle of the client will be the middle of the frame */
1625         *x += (self->size.right - self->size.left) / 2;
1626         break;
1627     case NorthEastGravity:
1628     case EastGravity:
1629     case SouthEastGravity:
1630         /* the right side of the client will be the right side of the frame */
1631         *x += self->size.right + self->size.left -
1632             self->client->border_width * 2;
1633         break;
1634     case StaticGravity:
1635     case ForgetGravity:
1636         /* the client's position won't move */
1637         *x += self->size.left - self->client->border_width;
1638         break;
1639     }
1640
1641     /* vertical */
1642     switch (self->client->gravity) {
1643     default:
1644     case NorthWestGravity:
1645     case NorthGravity:
1646     case NorthEastGravity:
1647         break;
1648     case WestGravity:
1649     case CenterGravity:
1650     case EastGravity:
1651         /* the middle of the client will be the middle of the frame */
1652         *y += (self->size.bottom - self->size.top) / 2;
1653         break;
1654     case SouthWestGravity:
1655     case SouthGravity:
1656     case SouthEastGravity:
1657         /* the bottom of the client will be the bottom of the frame */
1658         *y += self->size.bottom + self->size.top -
1659             self->client->border_width * 2;
1660         break;
1661     case StaticGravity:
1662     case ForgetGravity:
1663         /* the client's position won't move */
1664         *y += self->size.top - self->client->border_width;
1665         break;
1666     }
1667 }
1668
1669 void frame_rect_to_frame(ObFrame *self, Rect *r)
1670 {
1671     r->width += self->size.left + self->size.right;
1672     r->height += self->size.top + self->size.bottom;
1673     frame_client_gravity(self, &r->x, &r->y);
1674 }
1675
1676 void frame_rect_to_client(ObFrame *self, Rect *r)
1677 {
1678     r->width -= self->size.left + self->size.right;
1679     r->height -= self->size.top + self->size.bottom;
1680     frame_frame_gravity(self, &r->x, &r->y);
1681 }
1682
1683 static void flash_done(gpointer data)
1684 {
1685     ObFrame *self = data;
1686
1687     if (self->focused != self->flash_on)
1688         frame_adjust_focus(self, self->focused);
1689 }
1690
1691 static gboolean flash_timeout(gpointer data)
1692 {
1693     ObFrame *self = data;
1694     GTimeVal now;
1695
1696     g_get_current_time(&now);
1697     if (now.tv_sec > self->flash_end.tv_sec ||
1698         (now.tv_sec == self->flash_end.tv_sec &&
1699          now.tv_usec >= self->flash_end.tv_usec))
1700         self->flashing = FALSE;
1701
1702     if (!self->flashing)
1703         return FALSE; /* we are done */
1704
1705     self->flash_on = !self->flash_on;
1706     if (!self->focused) {
1707         frame_adjust_focus(self, self->flash_on);
1708         self->focused = FALSE;
1709     }
1710
1711     return TRUE; /* go again */
1712 }
1713
1714 void frame_flash_start(ObFrame *self)
1715 {
1716     self->flash_on = self->focused;
1717
1718     if (!self->flashing)
1719         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
1720                                                600, flash_timeout, self,
1721                                                flash_done);
1722     g_get_current_time(&self->flash_end);
1723     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1724
1725     self->flashing = TRUE;
1726 }
1727
1728 void frame_flash_stop(ObFrame *self)
1729 {
1730     self->flashing = FALSE;
1731 }
1732
1733 static gulong frame_animate_iconify_time_left(ObFrame *self,
1734                                               const GTimeVal *now)
1735 {
1736     glong sec, usec;
1737     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1738     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1739     if (usec < 0) {
1740         usec += G_USEC_PER_SEC;
1741         sec--;
1742     }
1743     /* no negative values */
1744     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1745 }
1746
1747 static gboolean frame_animate_iconify(gpointer p)
1748 {
1749     ObFrame *self = p;
1750     gint x, y, w, h;
1751     gint iconx, icony, iconw;
1752     GTimeVal now;
1753     gulong time;
1754     gboolean iconifying;
1755
1756     if (self->client->icon_geometry.width == 0) {
1757         /* there is no icon geometry set so just go straight down */
1758         const Rect *a;
1759
1760         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
1761         iconx = self->area.x + self->area.width / 2 + 32;
1762         icony = a->y + a->width;
1763         iconw = 64;
1764     } else {
1765         iconx = self->client->icon_geometry.x;
1766         icony = self->client->icon_geometry.y;
1767         iconw = self->client->icon_geometry.width;
1768     }
1769
1770     iconifying = self->iconify_animation_going > 0;
1771
1772     /* how far do we have left to go ? */
1773     g_get_current_time(&now);
1774     time = frame_animate_iconify_time_left(self, &now);
1775
1776     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
1777         /* start where the frame is supposed to be */
1778         x = self->area.x;
1779         y = self->area.y;
1780         w = self->area.width;
1781         h = self->area.height;
1782     } else {
1783         /* start at the icon */
1784         x = iconx;
1785         y = icony;
1786         w = iconw;
1787         h = self->size.top; /* just the titlebar */
1788     }
1789
1790     if (time > 0) {
1791         glong dx, dy, dw;
1792         glong elapsed;
1793
1794         dx = self->area.x - iconx;
1795         dy = self->area.y - icony;
1796         dw = self->area.width - self->bwidth * 2 - iconw;
1797          /* if restoring, we move in the opposite direction */
1798         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1799
1800         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1801         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1802         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1803         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1804         h = self->size.top; /* just the titlebar */
1805     }
1806
1807     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
1808     XFlush(obt_display);
1809
1810     if (time == 0)
1811         frame_end_iconify_animation(self);
1812
1813     return time > 0; /* repeat until we're out of time */
1814 }
1815
1816 void frame_end_iconify_animation(ObFrame *self)
1817 {
1818     /* see if there is an animation going */
1819     if (self->iconify_animation_going == 0) return;
1820
1821     if (!self->visible)
1822         XUnmapWindow(obt_display, self->window);
1823     else {
1824         /* Send a ConfigureNotify when the animation is done, this fixes
1825            KDE's pager showing the window in the wrong place.  since the
1826            window is mapped at a different location and is then moved, we
1827            need to send the synthetic configurenotify, since apps may have
1828            read the position when the client mapped, apparently. */
1829         client_reconfigure(self->client, TRUE);
1830     }
1831
1832     /* we're not animating any more ! */
1833     self->iconify_animation_going = 0;
1834
1835     XMoveResizeWindow(obt_display, self->window,
1836                       self->area.x, self->area.y,
1837                       self->area.width, self->area.height);
1838     /* we delay re-rendering until after we're done animating */
1839     framerender_frame(self);
1840     XFlush(obt_display);
1841 }
1842
1843 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1844 {
1845     gulong time;
1846     gboolean new_anim = FALSE;
1847     gboolean set_end = TRUE;
1848     GTimeVal now;
1849
1850     /* if there is no titlebar, just don't animate for now
1851        XXX it would be nice tho.. */
1852     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1853         return;
1854
1855     /* get the current time */
1856     g_get_current_time(&now);
1857
1858     /* get how long until the end */
1859     time = FRAME_ANIMATE_ICONIFY_TIME;
1860     if (self->iconify_animation_going) {
1861         if (!!iconifying != (self->iconify_animation_going > 0)) {
1862             /* animation was already going on in the opposite direction */
1863             time = time - frame_animate_iconify_time_left(self, &now);
1864         } else
1865             /* animation was already going in the same direction */
1866             set_end = FALSE;
1867     } else
1868         new_anim = TRUE;
1869     self->iconify_animation_going = iconifying ? 1 : -1;
1870
1871     /* set the ending time */
1872     if (set_end) {
1873         self->iconify_animation_end.tv_sec = now.tv_sec;
1874         self->iconify_animation_end.tv_usec = now.tv_usec;
1875         g_time_val_add(&self->iconify_animation_end, time);
1876     }
1877
1878     if (new_anim) {
1879         if (self->iconify_animation_timer)
1880             g_source_remove(self->iconify_animation_timer);
1881         self->iconify_animation_timer =
1882             g_timeout_add_full(G_PRIORITY_DEFAULT,
1883                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1884                                frame_animate_iconify, self, NULL);
1885                                
1886
1887         /* do the first step */
1888         frame_animate_iconify(self);
1889
1890         /* show it during the animation even if it is not "visible" */
1891         if (!self->visible)
1892             XMapWindow(obt_display, self->window);
1893     }
1894 }