5b695f7733a294c71f7d3a4afa02477c99207c43
[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                                 kind, 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 border on the
392                top of the window to let the user still undecorate/unmaximize the
393                window via the client menu. */
394             self->size.top += self->bwidth;
395         }
396
397         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
398             ob_rr_theme->handle_height > 0)
399         {
400             self->size.bottom += ob_rr_theme->handle_height + self->bwidth;
401         }
402
403         /* position/size and map/unmap all the windows */
404
405         if (!fake) {
406             gint innercornerheight =
407                 ob_rr_theme->grip_width - self->size.bottom;
408
409             if (self->cbwidth_l) {
410                 XMoveResizeWindow(obt_display, self->innerleft,
411                                   self->size.left - self->cbwidth_l,
412                                   self->size.top,
413                                   self->cbwidth_l, self->client->area.height);
414
415                 XMapWindow(obt_display, self->innerleft);
416             } else
417                 XUnmapWindow(obt_display, self->innerleft);
418
419             if (self->cbwidth_l && innercornerheight > 0) {
420                 XMoveResizeWindow(obt_display, self->innerbll,
421                                   0,
422                                   self->client->area.height - 
423                                   (ob_rr_theme->grip_width -
424                                    self->size.bottom),
425                                   self->cbwidth_l,
426                                   ob_rr_theme->grip_width - self->size.bottom);
427
428                 XMapWindow(obt_display, self->innerbll);
429             } else
430                 XUnmapWindow(obt_display, self->innerbll);
431
432             if (self->cbwidth_r) {
433                 XMoveResizeWindow(obt_display, self->innerright,
434                                   self->size.left + self->client->area.width,
435                                   self->size.top,
436                                   self->cbwidth_r, self->client->area.height);
437
438                 XMapWindow(obt_display, self->innerright);
439             } else
440                 XUnmapWindow(obt_display, self->innerright);
441
442             if (self->cbwidth_r && innercornerheight > 0) {
443                 XMoveResizeWindow(obt_display, self->innerbrr,
444                                   0,
445                                   self->client->area.height - 
446                                   (ob_rr_theme->grip_width -
447                                    self->size.bottom),
448                                   self->cbwidth_r,
449                                   ob_rr_theme->grip_width - self->size.bottom);
450
451                 XMapWindow(obt_display, self->innerbrr);
452             } else
453                 XUnmapWindow(obt_display, self->innerbrr);
454
455             if (self->cbwidth_t) {
456                 XMoveResizeWindow(obt_display, self->innertop,
457                                   self->size.left - self->cbwidth_l,
458                                   self->size.top - self->cbwidth_t,
459                                   self->client->area.width +
460                                   self->cbwidth_l + self->cbwidth_r,
461                                   self->cbwidth_t);
462
463                 XMapWindow(obt_display, self->innertop);
464             } else
465                 XUnmapWindow(obt_display, self->innertop);
466
467             if (self->cbwidth_b) {
468                 XMoveResizeWindow(obt_display, self->innerbottom,
469                                   self->size.left - self->cbwidth_l,
470                                   self->size.top + self->client->area.height,
471                                   self->client->area.width +
472                                   self->cbwidth_l + self->cbwidth_r,
473                                   self->cbwidth_b);
474
475                 XMoveResizeWindow(obt_display, self->innerblb,
476                                   0, 0,
477                                   ob_rr_theme->grip_width + self->bwidth,
478                                   self->cbwidth_b);
479                 XMoveResizeWindow(obt_display, self->innerbrb,
480                                   self->client->area.width +
481                                   self->cbwidth_l + self->cbwidth_r -
482                                   (ob_rr_theme->grip_width + self->bwidth),
483                                   0,
484                                   ob_rr_theme->grip_width + self->bwidth,
485                                   self->cbwidth_b);
486
487                 XMapWindow(obt_display, self->innerbottom);
488                 XMapWindow(obt_display, self->innerblb);
489                 XMapWindow(obt_display, self->innerbrb);
490             } else {
491                 XUnmapWindow(obt_display, self->innerbottom);
492                 XUnmapWindow(obt_display, self->innerblb);
493                 XUnmapWindow(obt_display, self->innerbrb);
494             }
495
496             if (self->bwidth) {
497                 gint titlesides;
498
499                 /* height of titleleft and titleright */
500                 titlesides = (!self->max_horz ? ob_rr_theme->grip_width : 0);
501
502                 XMoveResizeWindow(obt_display, self->titletop,
503                                   ob_rr_theme->grip_width + self->bwidth, 0,
504                                   /* width + bwidth*2 - bwidth*2 - grips*2 */
505                                   self->width - ob_rr_theme->grip_width * 2,
506                                   self->bwidth);
507                 XMoveResizeWindow(obt_display, self->titletopleft,
508                                   0, 0,
509                                   ob_rr_theme->grip_width + self->bwidth,
510                                   self->bwidth);
511                 XMoveResizeWindow(obt_display, self->titletopright,
512                                   self->client->area.width +
513                                   self->size.left + self->size.right -
514                                   ob_rr_theme->grip_width - self->bwidth,
515                                   0,
516                                   ob_rr_theme->grip_width + self->bwidth,
517                                   self->bwidth);
518
519                 if (titlesides > 0) {
520                     XMoveResizeWindow(obt_display, self->titleleft,
521                                       0, self->bwidth,
522                                       self->bwidth,
523                                       titlesides);
524                     XMoveResizeWindow(obt_display, self->titleright,
525                                       self->client->area.width +
526                                       self->size.left + self->size.right -
527                                       self->bwidth,
528                                       self->bwidth,
529                                       self->bwidth,
530                                       titlesides);
531
532                     XMapWindow(obt_display, self->titleleft);
533                     XMapWindow(obt_display, self->titleright);
534                 } else {
535                     XUnmapWindow(obt_display, self->titleleft);
536                     XUnmapWindow(obt_display, self->titleright);
537                 }
538
539                 XMapWindow(obt_display, self->titletop);
540                 XMapWindow(obt_display, self->titletopleft);
541                 XMapWindow(obt_display, self->titletopright);
542
543                 if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
544                     XMoveResizeWindow(obt_display, self->titlebottom,
545                                       (self->max_horz ? 0 : self->bwidth),
546                                       ob_rr_theme->title_height + self->bwidth,
547                                       self->width,
548                                       self->bwidth);
549
550                     XMapWindow(obt_display, self->titlebottom);
551                 } else
552                     XUnmapWindow(obt_display, self->titlebottom);
553             } else {
554                 XUnmapWindow(obt_display, self->titlebottom);
555
556                 XUnmapWindow(obt_display, self->titletop);
557                 XUnmapWindow(obt_display, self->titletopleft);
558                 XUnmapWindow(obt_display, self->titletopright);
559                 XUnmapWindow(obt_display, self->titleleft);
560                 XUnmapWindow(obt_display, self->titleright);
561             }
562
563             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
564                 XMoveResizeWindow(obt_display, self->title,
565                                   (self->max_horz ? 0 : self->bwidth),
566                                   self->bwidth,
567                                   self->width, ob_rr_theme->title_height);
568
569                 XMapWindow(obt_display, self->title);
570
571                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
572                     XMoveResizeWindow(obt_display, self->topresize,
573                                       ob_rr_theme->grip_width,
574                                       0,
575                                       self->width - ob_rr_theme->grip_width *2,
576                                       ob_rr_theme->paddingy + 1);
577
578                     XMoveWindow(obt_display, self->tltresize, 0, 0);
579                     XMoveWindow(obt_display, self->tllresize, 0, 0);
580                     XMoveWindow(obt_display, self->trtresize,
581                                 self->width - ob_rr_theme->grip_width, 0);
582                     XMoveWindow(obt_display, self->trrresize,
583                                 self->width - ob_rr_theme->paddingx - 1, 0);
584
585                     XMapWindow(obt_display, self->topresize);
586                     XMapWindow(obt_display, self->tltresize);
587                     XMapWindow(obt_display, self->tllresize);
588                     XMapWindow(obt_display, self->trtresize);
589                     XMapWindow(obt_display, self->trrresize);
590                 } else {
591                     XUnmapWindow(obt_display, self->topresize);
592                     XUnmapWindow(obt_display, self->tltresize);
593                     XUnmapWindow(obt_display, self->tllresize);
594                     XUnmapWindow(obt_display, self->trtresize);
595                     XUnmapWindow(obt_display, self->trrresize);
596                 }
597             } else
598                 XUnmapWindow(obt_display, self->title);
599         }
600
601         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
602             /* layout the title bar elements */
603             layout_title(self);
604
605         if (!fake) {
606             gint sidebwidth = self->max_horz ? 0 : self->bwidth;
607
608             if (self->bwidth && self->size.bottom) {
609                 XMoveResizeWindow(obt_display, self->handlebottom,
610                                   ob_rr_theme->grip_width +
611                                   self->bwidth + sidebwidth,
612                                   self->size.top + self->client->area.height +
613                                   self->size.bottom - self->bwidth,
614                                   self->width - (ob_rr_theme->grip_width +
615                                                  sidebwidth) * 2,
616                                   self->bwidth);
617
618
619                 if (sidebwidth) {
620                     XMoveResizeWindow(obt_display, self->lgripleft,
621                                       0,
622                                       self->size.top +
623                                       self->client->area.height +
624                                       self->size.bottom -
625                                       (!self->max_horz ?
626                                        ob_rr_theme->grip_width :
627                                        self->size.bottom - self->cbwidth_b),
628                                       self->bwidth,
629                                       (!self->max_horz ?
630                                        ob_rr_theme->grip_width :
631                                        self->size.bottom - self->cbwidth_b));
632                     XMoveResizeWindow(obt_display, self->rgripright,
633                                   self->size.left +
634                                       self->client->area.width +
635                                       self->size.right - self->bwidth,
636                                       self->size.top +
637                                       self->client->area.height +
638                                       self->size.bottom -
639                                       (!self->max_horz ?
640                                        ob_rr_theme->grip_width :
641                                        self->size.bottom - self->cbwidth_b),
642                                       self->bwidth,
643                                       (!self->max_horz ?
644                                        ob_rr_theme->grip_width :
645                                        self->size.bottom - self->cbwidth_b));
646
647                     XMapWindow(obt_display, self->lgripleft);
648                     XMapWindow(obt_display, self->rgripright);
649                 } else {
650                     XUnmapWindow(obt_display, self->lgripleft);
651                     XUnmapWindow(obt_display, self->rgripright);
652                 }
653
654                 XMoveResizeWindow(obt_display, self->lgripbottom,
655                                   sidebwidth,
656                                   self->size.top + self->client->area.height +
657                                   self->size.bottom - self->bwidth,
658                                   ob_rr_theme->grip_width + self->bwidth,
659                                   self->bwidth);
660                 XMoveResizeWindow(obt_display, self->rgripbottom,
661                                   self->size.left + self->client->area.width +
662                                   self->size.right - self->bwidth - sidebwidth-
663                                   ob_rr_theme->grip_width,
664                                   self->size.top + self->client->area.height +
665                                   self->size.bottom - self->bwidth,
666                                   ob_rr_theme->grip_width + self->bwidth,
667                                   self->bwidth);
668
669                 XMapWindow(obt_display, self->handlebottom);
670                 XMapWindow(obt_display, self->lgripbottom);
671                 XMapWindow(obt_display, self->rgripbottom);
672
673                 if (self->decorations & OB_FRAME_DECOR_HANDLE &&
674                     ob_rr_theme->handle_height > 0)
675                 {
676                     XMoveResizeWindow(obt_display, self->handletop,
677                                       ob_rr_theme->grip_width +
678                                       self->bwidth + sidebwidth,
679                                       FRAME_HANDLE_Y(self),
680                                       self->width - (ob_rr_theme->grip_width +
681                                                      sidebwidth) * 2,
682                                       self->bwidth);
683                     XMapWindow(obt_display, self->handletop);
684
685                     if (self->decorations & OB_FRAME_DECOR_GRIPS) {
686                         XMoveResizeWindow(obt_display, self->handleleft,
687                                           ob_rr_theme->grip_width,
688                                           0,
689                                           self->bwidth,
690                                           ob_rr_theme->handle_height);
691                         XMoveResizeWindow(obt_display, self->handleright,
692                                           self->width -
693                                           ob_rr_theme->grip_width -
694                                           self->bwidth,
695                                           0,
696                                           self->bwidth,
697                                           ob_rr_theme->handle_height);
698
699                         XMoveResizeWindow(obt_display, self->lgriptop,
700                                           sidebwidth,
701                                           FRAME_HANDLE_Y(self),
702                                           ob_rr_theme->grip_width +
703                                           self->bwidth,
704                                           self->bwidth);
705                         XMoveResizeWindow(obt_display, self->rgriptop,
706                                           self->size.left +
707                                           self->client->area.width +
708                                           self->size.right - self->bwidth -
709                                           sidebwidth - ob_rr_theme->grip_width,
710                                           FRAME_HANDLE_Y(self),
711                                           ob_rr_theme->grip_width +
712                                           self->bwidth,
713                                           self->bwidth);
714
715                         XMapWindow(obt_display, self->handleleft);
716                         XMapWindow(obt_display, self->handleright);
717                         XMapWindow(obt_display, self->lgriptop);
718                         XMapWindow(obt_display, self->rgriptop);
719                     } else {
720                         XUnmapWindow(obt_display, self->handleleft);
721                         XUnmapWindow(obt_display, self->handleright);
722                         XUnmapWindow(obt_display, self->lgriptop);
723                         XUnmapWindow(obt_display, self->rgriptop);
724                     }
725                 } else {
726                     XUnmapWindow(obt_display, self->handleleft);
727                     XUnmapWindow(obt_display, self->handleright);
728                     XUnmapWindow(obt_display, self->lgriptop);
729                     XUnmapWindow(obt_display, self->rgriptop);
730
731                     XUnmapWindow(obt_display, self->handletop);
732                 }
733             } else {
734                 XUnmapWindow(obt_display, self->handleleft);
735                 XUnmapWindow(obt_display, self->handleright);
736                 XUnmapWindow(obt_display, self->lgriptop);
737                 XUnmapWindow(obt_display, self->rgriptop);
738
739                 XUnmapWindow(obt_display, self->handletop);
740
741                 XUnmapWindow(obt_display, self->handlebottom);
742                 XUnmapWindow(obt_display, self->lgripleft);
743                 XUnmapWindow(obt_display, self->rgripright);
744                 XUnmapWindow(obt_display, self->lgripbottom);
745                 XUnmapWindow(obt_display, self->rgripbottom);
746             }
747
748             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
749                 ob_rr_theme->handle_height > 0)
750             {
751                 XMoveResizeWindow(obt_display, self->handle,
752                                   sidebwidth,
753                                   FRAME_HANDLE_Y(self) + self->bwidth,
754                                   self->width, ob_rr_theme->handle_height);
755                 XMapWindow(obt_display, self->handle);
756
757                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
758                     XMoveResizeWindow(obt_display, self->lgrip,
759                                       0, 0,
760                                       ob_rr_theme->grip_width,
761                                       ob_rr_theme->handle_height);
762                     XMoveResizeWindow(obt_display, self->rgrip,
763                                       self->width - ob_rr_theme->grip_width,
764                                       0,
765                                       ob_rr_theme->grip_width,
766                                       ob_rr_theme->handle_height);
767
768                     XMapWindow(obt_display, self->lgrip);
769                     XMapWindow(obt_display, self->rgrip);
770                 } else {
771                     XUnmapWindow(obt_display, self->lgrip);
772                     XUnmapWindow(obt_display, self->rgrip);
773                 }
774             } else {
775                 XUnmapWindow(obt_display, self->lgrip);
776                 XUnmapWindow(obt_display, self->rgrip);
777
778                 XUnmapWindow(obt_display, self->handle);
779             }
780
781             if (self->bwidth && !self->max_horz &&
782                 (self->client->area.height + self->size.top +
783                  self->size.bottom) > ob_rr_theme->grip_width * 2)
784             {
785                 XMoveResizeWindow(obt_display, self->left,
786                                   0,
787                                   self->bwidth + ob_rr_theme->grip_width,
788                                   self->bwidth,
789                                   self->client->area.height +
790                                   self->size.top + self->size.bottom -
791                                   ob_rr_theme->grip_width * 2);
792
793                 XMapWindow(obt_display, self->left);
794             } else
795                 XUnmapWindow(obt_display, self->left);
796
797             if (self->bwidth && !self->max_horz &&
798                 (self->client->area.height + self->size.top +
799                  self->size.bottom) > ob_rr_theme->grip_width * 2)
800             {
801                 XMoveResizeWindow(obt_display, self->right,
802                                   self->client->area.width + self->cbwidth_l +
803                                   self->cbwidth_r + self->bwidth,
804                                   self->bwidth + ob_rr_theme->grip_width,
805                                   self->bwidth,
806                                   self->client->area.height +
807                                   self->size.top + self->size.bottom -
808                                   ob_rr_theme->grip_width * 2);
809
810                 XMapWindow(obt_display, self->right);
811             } else
812                 XUnmapWindow(obt_display, self->right);
813
814             XMoveResizeWindow(obt_display, self->backback,
815                               self->size.left, self->size.top,
816                               self->client->area.width,
817                               self->client->area.height);
818         }
819     }
820
821     /* shading can change without being moved or resized */
822     RECT_SET_SIZE(self->area,
823                   self->client->area.width +
824                   self->size.left + self->size.right,
825                   (self->client->shaded ?
826                    ob_rr_theme->title_height + self->bwidth * 2:
827                    self->client->area.height +
828                    self->size.top + self->size.bottom));
829
830     if ((moved || resized) && !fake) {
831         /* find the new coordinates, done after setting the frame.size, for
832            frame_client_gravity. */
833         self->area.x = self->client->area.x;
834         self->area.y = self->client->area.y;
835         frame_client_gravity(self, &self->area.x, &self->area.y);
836     }
837
838     if (!fake) {
839         if (!frame_iconify_animating(self))
840             /* move and resize the top level frame.
841                shading can change without being moved or resized.
842
843                but don't do this during an iconify animation. it will be
844                reflected afterwards.
845             */
846             XMoveResizeWindow(obt_display, self->window,
847                               self->area.x,
848                               self->area.y,
849                               self->area.width,
850                               self->area.height);
851
852         /* when the client has StaticGravity, it likes to move around.
853            also this correctly positions the client when it maps.
854            this also needs to be run when the frame's decorations sizes change!
855         */
856         XMoveWindow(obt_display, self->client->window,
857                     self->size.left, self->size.top);
858
859         if (resized) {
860             self->need_render = TRUE;
861             framerender_frame(self);
862             frame_adjust_shape(self);
863         }
864
865         if (!STRUT_EQUAL(self->size, self->oldsize)) {
866             gulong vals[4];
867             vals[0] = self->size.left;
868             vals[1] = self->size.right;
869             vals[2] = self->size.top;
870             vals[3] = self->size.bottom;
871             OBT_PROP_SETA32(self->client->window, NET_FRAME_EXTENTS,
872                             CARDINAL, vals, 4);
873             OBT_PROP_SETA32(self->client->window, KDE_NET_WM_FRAME_STRUT,
874                             CARDINAL, vals, 4);
875             self->oldsize = self->size;
876         }
877
878         /* if this occurs while we are focus cycling, the indicator needs to
879            match the changes */
880         if (focus_cycle_target == self->client)
881             focus_cycle_update_indicator(self->client);
882     }
883     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR) &&
884         self->label_width)
885     {
886         XResizeWindow(obt_display, self->label, self->label_width,
887                       ob_rr_theme->label_height);
888     }
889 }
890
891 static void frame_adjust_cursors(ObFrame *self)
892 {
893     if ((self->functions & OB_CLIENT_FUNC_RESIZE) !=
894         (self->client->functions & OB_CLIENT_FUNC_RESIZE) ||
895         self->max_horz != self->client->max_horz ||
896         self->max_vert != self->client->max_vert ||
897         self->shaded != self->client->shaded)
898     {
899         gboolean r = (self->client->functions & OB_CLIENT_FUNC_RESIZE) &&
900             !(self->client->max_horz && self->client->max_vert);
901         gboolean topbot = !self->client->max_vert;
902         gboolean sh = self->client->shaded;
903         XSetWindowAttributes a;
904
905         /* these ones turn off when max vert, and some when shaded */
906         a.cursor = ob_cursor(r && topbot && !sh ?
907                              OB_CURSOR_NORTH : OB_CURSOR_NONE);
908         XChangeWindowAttributes(obt_display, self->topresize, CWCursor, &a);
909         XChangeWindowAttributes(obt_display, self->titletop, CWCursor, &a);
910         a.cursor = ob_cursor(r && topbot ? OB_CURSOR_SOUTH : OB_CURSOR_NONE);
911         XChangeWindowAttributes(obt_display, self->handle, CWCursor, &a);
912         XChangeWindowAttributes(obt_display, self->handletop, CWCursor, &a);
913         XChangeWindowAttributes(obt_display, self->handlebottom, CWCursor, &a);
914         XChangeWindowAttributes(obt_display, self->innerbottom, CWCursor, &a);
915
916         /* these ones change when shaded */
917         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_WEST : OB_CURSOR_NORTHWEST) :
918                              OB_CURSOR_NONE);
919         XChangeWindowAttributes(obt_display, self->titleleft, CWCursor, &a);
920         XChangeWindowAttributes(obt_display, self->tltresize, CWCursor, &a);
921         XChangeWindowAttributes(obt_display, self->tllresize, CWCursor, &a);
922         XChangeWindowAttributes(obt_display, self->titletopleft, CWCursor, &a);
923         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_EAST : OB_CURSOR_NORTHEAST) :
924                              OB_CURSOR_NONE);
925         XChangeWindowAttributes(obt_display, self->titleright, CWCursor, &a);
926         XChangeWindowAttributes(obt_display, self->trtresize, CWCursor, &a);
927         XChangeWindowAttributes(obt_display, self->trrresize, CWCursor, &a);
928         XChangeWindowAttributes(obt_display, self->titletopright, CWCursor,&a);
929
930         /* these ones are pretty static */
931         a.cursor = ob_cursor(r ? OB_CURSOR_WEST : OB_CURSOR_NONE);
932         XChangeWindowAttributes(obt_display, self->left, CWCursor, &a);
933         XChangeWindowAttributes(obt_display, self->innerleft, CWCursor, &a);
934         a.cursor = ob_cursor(r ? OB_CURSOR_EAST : OB_CURSOR_NONE);
935         XChangeWindowAttributes(obt_display, self->right, CWCursor, &a);
936         XChangeWindowAttributes(obt_display, self->innerright, CWCursor, &a);
937         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHWEST : OB_CURSOR_NONE);
938         XChangeWindowAttributes(obt_display, self->lgrip, CWCursor, &a);
939         XChangeWindowAttributes(obt_display, self->handleleft, CWCursor, &a);
940         XChangeWindowAttributes(obt_display, self->lgripleft, CWCursor, &a);
941         XChangeWindowAttributes(obt_display, self->lgriptop, CWCursor, &a);
942         XChangeWindowAttributes(obt_display, self->lgripbottom, CWCursor, &a);
943         XChangeWindowAttributes(obt_display, self->innerbll, CWCursor, &a);
944         XChangeWindowAttributes(obt_display, self->innerblb, CWCursor, &a);
945         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHEAST : OB_CURSOR_NONE);
946         XChangeWindowAttributes(obt_display, self->rgrip, CWCursor, &a);
947         XChangeWindowAttributes(obt_display, self->handleright, CWCursor, &a);
948         XChangeWindowAttributes(obt_display, self->rgripright, CWCursor, &a);
949         XChangeWindowAttributes(obt_display, self->rgriptop, CWCursor, &a);
950         XChangeWindowAttributes(obt_display, self->rgripbottom, CWCursor, &a);
951         XChangeWindowAttributes(obt_display, self->innerbrr, CWCursor, &a);
952         XChangeWindowAttributes(obt_display, self->innerbrb, CWCursor, &a);
953     }
954 }
955
956 void frame_adjust_client_area(ObFrame *self)
957 {
958     /* adjust the window which is there to prevent flashing on unmap */
959     XMoveResizeWindow(obt_display, self->backfront, 0, 0,
960                       self->client->area.width,
961                       self->client->area.height);
962 }
963
964 void frame_adjust_state(ObFrame *self)
965 {
966     self->need_render = TRUE;
967     framerender_frame(self);
968 }
969
970 void frame_adjust_focus(ObFrame *self, gboolean hilite)
971 {
972     ob_debug_type(OB_DEBUG_FOCUS,
973                   "Frame for 0x%x has focus: %d",
974                   self->client->window, hilite);
975     self->focused = hilite;
976     self->need_render = TRUE;
977     framerender_frame(self);
978     XFlush(obt_display);
979 }
980
981 void frame_adjust_title(ObFrame *self)
982 {
983     self->need_render = TRUE;
984     framerender_frame(self);
985 }
986
987 void frame_adjust_icon(ObFrame *self)
988 {
989     self->need_render = TRUE;
990     framerender_frame(self);
991 }
992
993 void frame_grab_client(ObFrame *self)
994 {
995     /* DO NOT map the client window here. we used to do that, but it is bogus.
996        we need to set up the client's dimensions and everything before we
997        send a mapnotify or we create race conditions.
998     */
999
1000     /* reparent the client to the frame */
1001     XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
1002
1003     /*
1004       When reparenting the client window, it is usually not mapped yet, since
1005       this occurs from a MapRequest. However, in the case where Openbox is
1006       starting up, the window is already mapped, so we'll see an unmap event
1007       for it.
1008     */
1009     if (ob_state() == OB_STATE_STARTING)
1010         ++self->client->ignore_unmaps;
1011
1012     /* select the event mask on the client's parent (to receive config/map
1013        req's) the ButtonPress is to catch clicks on the client border */
1014     XSelectInput(obt_display, self->window, FRAME_EVENTMASK);
1015
1016     /* set all the windows for the frame in the window_map */
1017     window_add(&self->window, CLIENT_AS_WINDOW(self->client));
1018     window_add(&self->backback, CLIENT_AS_WINDOW(self->client));
1019     window_add(&self->backfront, CLIENT_AS_WINDOW(self->client));
1020     window_add(&self->innerleft, CLIENT_AS_WINDOW(self->client));
1021     window_add(&self->innertop, CLIENT_AS_WINDOW(self->client));
1022     window_add(&self->innerright, CLIENT_AS_WINDOW(self->client));
1023     window_add(&self->innerbottom, CLIENT_AS_WINDOW(self->client));
1024     window_add(&self->innerblb, CLIENT_AS_WINDOW(self->client));
1025     window_add(&self->innerbll, CLIENT_AS_WINDOW(self->client));
1026     window_add(&self->innerbrb, CLIENT_AS_WINDOW(self->client));
1027     window_add(&self->innerbrr, CLIENT_AS_WINDOW(self->client));
1028     window_add(&self->title, CLIENT_AS_WINDOW(self->client));
1029     window_add(&self->label, CLIENT_AS_WINDOW(self->client));
1030     window_add(&self->max, CLIENT_AS_WINDOW(self->client));
1031     window_add(&self->close, CLIENT_AS_WINDOW(self->client));
1032     window_add(&self->desk, CLIENT_AS_WINDOW(self->client));
1033     window_add(&self->shade, CLIENT_AS_WINDOW(self->client));
1034     window_add(&self->icon, CLIENT_AS_WINDOW(self->client));
1035     window_add(&self->iconify, CLIENT_AS_WINDOW(self->client));
1036     window_add(&self->handle, CLIENT_AS_WINDOW(self->client));
1037     window_add(&self->lgrip, CLIENT_AS_WINDOW(self->client));
1038     window_add(&self->rgrip, CLIENT_AS_WINDOW(self->client));
1039     window_add(&self->topresize, CLIENT_AS_WINDOW(self->client));
1040     window_add(&self->tltresize, CLIENT_AS_WINDOW(self->client));
1041     window_add(&self->tllresize, CLIENT_AS_WINDOW(self->client));
1042     window_add(&self->trtresize, CLIENT_AS_WINDOW(self->client));
1043     window_add(&self->trrresize, CLIENT_AS_WINDOW(self->client));
1044     window_add(&self->left, CLIENT_AS_WINDOW(self->client));
1045     window_add(&self->right, CLIENT_AS_WINDOW(self->client));
1046     window_add(&self->titleleft, CLIENT_AS_WINDOW(self->client));
1047     window_add(&self->titletop, CLIENT_AS_WINDOW(self->client));
1048     window_add(&self->titletopleft, CLIENT_AS_WINDOW(self->client));
1049     window_add(&self->titletopright, CLIENT_AS_WINDOW(self->client));
1050     window_add(&self->titleright, CLIENT_AS_WINDOW(self->client));
1051     window_add(&self->titlebottom, CLIENT_AS_WINDOW(self->client));
1052     window_add(&self->handleleft, CLIENT_AS_WINDOW(self->client));
1053     window_add(&self->handletop, CLIENT_AS_WINDOW(self->client));
1054     window_add(&self->handleright, CLIENT_AS_WINDOW(self->client));
1055     window_add(&self->handlebottom, CLIENT_AS_WINDOW(self->client));
1056     window_add(&self->lgripleft, CLIENT_AS_WINDOW(self->client));
1057     window_add(&self->lgriptop, CLIENT_AS_WINDOW(self->client));
1058     window_add(&self->lgripbottom, CLIENT_AS_WINDOW(self->client));
1059     window_add(&self->rgripright, CLIENT_AS_WINDOW(self->client));
1060     window_add(&self->rgriptop, CLIENT_AS_WINDOW(self->client));
1061     window_add(&self->rgripbottom, CLIENT_AS_WINDOW(self->client));
1062 }
1063
1064 static gboolean find_reparent(XEvent *e, gpointer data)
1065 {
1066     const ObFrame *self = data;
1067
1068     /* Find ReparentNotify events for the window that aren't being reparented into the
1069        frame, thus the client reparenting itself off the frame. */
1070     return e->type == ReparentNotify && e->xreparent.window == self->client->window &&
1071         e->xreparent.parent != self->window;
1072 }
1073
1074 void frame_release_client(ObFrame *self)
1075 {
1076     /* if there was any animation going on, kill it */
1077     if (self->iconify_animation_timer)
1078         g_source_remove(self->iconify_animation_timer);
1079
1080     /* check if the app has already reparented its window away */
1081     if (!xqueue_exists_local(find_reparent, self)) {
1082         /* according to the ICCCM - if the client doesn't reparent itself,
1083            then we will reparent the window to root for them */
1084         XReparentWindow(obt_display, self->client->window, obt_root(ob_screen),
1085                         self->client->area.x, self->client->area.y);
1086     }
1087
1088     /* remove all the windows for the frame from the window_map */
1089     window_remove(self->window);
1090     window_remove(self->backback);
1091     window_remove(self->backfront);
1092     window_remove(self->innerleft);
1093     window_remove(self->innertop);
1094     window_remove(self->innerright);
1095     window_remove(self->innerbottom);
1096     window_remove(self->innerblb);
1097     window_remove(self->innerbll);
1098     window_remove(self->innerbrb);
1099     window_remove(self->innerbrr);
1100     window_remove(self->title);
1101     window_remove(self->label);
1102     window_remove(self->max);
1103     window_remove(self->close);
1104     window_remove(self->desk);
1105     window_remove(self->shade);
1106     window_remove(self->icon);
1107     window_remove(self->iconify);
1108     window_remove(self->handle);
1109     window_remove(self->lgrip);
1110     window_remove(self->rgrip);
1111     window_remove(self->topresize);
1112     window_remove(self->tltresize);
1113     window_remove(self->tllresize);
1114     window_remove(self->trtresize);
1115     window_remove(self->trrresize);
1116     window_remove(self->left);
1117     window_remove(self->right);
1118     window_remove(self->titleleft);
1119     window_remove(self->titletop);
1120     window_remove(self->titletopleft);
1121     window_remove(self->titletopright);
1122     window_remove(self->titleright);
1123     window_remove(self->titlebottom);
1124     window_remove(self->handleleft);
1125     window_remove(self->handletop);
1126     window_remove(self->handleright);
1127     window_remove(self->handlebottom);
1128     window_remove(self->lgripleft);
1129     window_remove(self->lgriptop);
1130     window_remove(self->lgripbottom);
1131     window_remove(self->rgripright);
1132     window_remove(self->rgriptop);
1133     window_remove(self->rgripbottom);
1134
1135     if (self->flash_timer) g_source_remove(self->flash_timer);
1136 }
1137
1138 /* is there anything present between us and the label? */
1139 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
1140     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
1141         if (*lc == ' ') continue; /* it was invalid */
1142         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
1143             return TRUE;
1144         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
1145             return TRUE;
1146         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
1147             return TRUE;
1148         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
1149             return TRUE;
1150         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
1151             return TRUE;
1152         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
1153             return TRUE;
1154         if (*lc == 'L') return FALSE;
1155     }
1156     return FALSE;
1157 }
1158
1159 static void place_button(ObFrame *self, const char *lc, gint bwidth,
1160                          gint left, gint i,
1161                          gint *x, gint *button_on, gint *button_x)
1162 {
1163   if (!(*button_on = is_button_present(self, lc, i)))
1164     return;
1165
1166   self->label_width -= bwidth;
1167   if (i > 0)
1168     *button_x = *x;
1169   *x += i * bwidth;
1170   if (i < 0) {
1171     if (self->label_x <= left || *x > self->label_x) {
1172       *button_x = *x;
1173     } else {
1174       /* the button would have been drawn on top of another button */
1175       *button_on = FALSE;
1176       self->label_width += bwidth;
1177     }
1178   }
1179 }
1180
1181 static void layout_title(ObFrame *self)
1182 {
1183     gchar *lc;
1184     gint i;
1185
1186     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
1187     /* position of the leftmost button */
1188     const gint left = ob_rr_theme->paddingx + 1;
1189     /* position of the rightmost button */
1190     const gint right = self->width;
1191
1192     /* turn them all off */
1193     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
1194         self->max_on = self->close_on = self->label_on = FALSE;
1195     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
1196     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
1197
1198     /* figure out what's being shown, find each element's position, and the
1199        width of the label
1200
1201        do the ones before the label, then after the label,
1202        i will be +1 the first time through when working to the left,
1203        and -1 the second time through when working to the right */
1204     for (i = 1; i >= -1; i-=2) {
1205         gint x;
1206         ObFrameContext *firstcon;
1207
1208         if (i > 0) {
1209             x = left;
1210             lc = config_title_layout;
1211             firstcon = &self->leftmost;
1212         } else {
1213             x = right;
1214             lc = config_title_layout + strlen(config_title_layout)-1;
1215             firstcon = &self->rightmost;
1216         }
1217
1218         /* stop at the end of the string (or the label, which calls break) */
1219         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
1220             if (*lc == 'L') {
1221                 if (i > 0) {
1222                     self->label_on = TRUE;
1223                     self->label_x = x;
1224                 }
1225                 break; /* break the for loop, do other side of label */
1226             } else if (*lc == 'N') {
1227                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
1228                 /* icon is bigger than buttons */
1229                 place_button(self, lc, bwidth + 2, left, i, &x, &self->icon_on, &self->icon_x);
1230             } else if (*lc == 'D') {
1231                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
1232                 place_button(self, lc, bwidth, left, i, &x, &self->desk_on, &self->desk_x);
1233             } else if (*lc == 'S') {
1234                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
1235                 place_button(self, lc, bwidth, left, i, &x, &self->shade_on, &self->shade_x);
1236             } else if (*lc == 'I') {
1237                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
1238                 place_button(self, lc, bwidth, left, i, &x, &self->iconify_on, &self->iconify_x);
1239             } else if (*lc == 'M') {
1240                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
1241                 place_button(self, lc, bwidth, left, i, &x, &self->max_on, &self->max_x);
1242             } else if (*lc == 'C') {
1243                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
1244                 place_button(self, lc, bwidth, left, i, &x, &self->close_on, &self->close_x);
1245             } else
1246                 continue; /* don't set firstcon */
1247             firstcon = NULL;
1248         }
1249     }
1250
1251     /* position and map the elements */
1252     if (self->icon_on) {
1253         XMapWindow(obt_display, self->icon);
1254         XMoveWindow(obt_display, self->icon, self->icon_x,
1255                     ob_rr_theme->paddingy);
1256     } else
1257         XUnmapWindow(obt_display, self->icon);
1258
1259     if (self->desk_on) {
1260         XMapWindow(obt_display, self->desk);
1261         XMoveWindow(obt_display, self->desk, self->desk_x,
1262                     ob_rr_theme->paddingy + 1);
1263     } else
1264         XUnmapWindow(obt_display, self->desk);
1265
1266     if (self->shade_on) {
1267         XMapWindow(obt_display, self->shade);
1268         XMoveWindow(obt_display, self->shade, self->shade_x,
1269                     ob_rr_theme->paddingy + 1);
1270     } else
1271         XUnmapWindow(obt_display, self->shade);
1272
1273     if (self->iconify_on) {
1274         XMapWindow(obt_display, self->iconify);
1275         XMoveWindow(obt_display, self->iconify, self->iconify_x,
1276                     ob_rr_theme->paddingy + 1);
1277     } else
1278         XUnmapWindow(obt_display, self->iconify);
1279
1280     if (self->max_on) {
1281         XMapWindow(obt_display, self->max);
1282         XMoveWindow(obt_display, self->max, self->max_x,
1283                     ob_rr_theme->paddingy + 1);
1284     } else
1285         XUnmapWindow(obt_display, self->max);
1286
1287     if (self->close_on) {
1288         XMapWindow(obt_display, self->close);
1289         XMoveWindow(obt_display, self->close, self->close_x,
1290                     ob_rr_theme->paddingy + 1);
1291     } else
1292         XUnmapWindow(obt_display, self->close);
1293
1294     if (self->label_on && self->label_width > 0) {
1295         XMapWindow(obt_display, self->label);
1296         XMoveWindow(obt_display, self->label, self->label_x,
1297                     ob_rr_theme->paddingy);
1298     } else
1299         XUnmapWindow(obt_display, self->label);
1300 }
1301
1302 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
1303 {
1304     gchar *p, *n;
1305
1306     if (!*names) /* empty string */
1307         return FALSE;
1308
1309     /* find the first space */
1310     for (p = names; *p; p = g_utf8_next_char(p)) {
1311         const gunichar c = g_utf8_get_char(p);
1312         if (g_unichar_isspace(c)) break;
1313     }
1314
1315     if (p == names) {
1316         /* leading spaces in the string */
1317         n = g_utf8_next_char(names);
1318         if (!frame_next_context_from_string(n, cx))
1319             return FALSE;
1320     } else {
1321         n = p;
1322         if (*p) {
1323             /* delete the space with null zero(s) */
1324             while (n < g_utf8_next_char(p))
1325                 *(n++) = '\0';
1326         }
1327
1328         *cx = frame_context_from_string(names);
1329
1330         /* find the next non-space */
1331         for (; *n; n = g_utf8_next_char(n)) {
1332             const gunichar c = g_utf8_get_char(n);
1333             if (!g_unichar_isspace(c)) break;
1334         }
1335     }
1336
1337     /* delete everything we just read (copy everything at n to the start of
1338        the string */
1339     for (p = names; *n; ++p, ++n)
1340         *p = *n;
1341     *p = *n;
1342
1343     return TRUE;
1344 }
1345
1346 ObFrameContext frame_context_from_string(const gchar *name)
1347 {
1348     if (!g_ascii_strcasecmp("Desktop", name))
1349         return OB_FRAME_CONTEXT_DESKTOP;
1350     else if (!g_ascii_strcasecmp("Root", name))
1351         return OB_FRAME_CONTEXT_ROOT;
1352     else if (!g_ascii_strcasecmp("Client", name))
1353         return OB_FRAME_CONTEXT_CLIENT;
1354     else if (!g_ascii_strcasecmp("Titlebar", name))
1355         return OB_FRAME_CONTEXT_TITLEBAR;
1356     else if (!g_ascii_strcasecmp("Frame", name))
1357         return OB_FRAME_CONTEXT_FRAME;
1358     else if (!g_ascii_strcasecmp("TLCorner", name))
1359         return OB_FRAME_CONTEXT_TLCORNER;
1360     else if (!g_ascii_strcasecmp("TRCorner", name))
1361         return OB_FRAME_CONTEXT_TRCORNER;
1362     else if (!g_ascii_strcasecmp("BLCorner", name))
1363         return OB_FRAME_CONTEXT_BLCORNER;
1364     else if (!g_ascii_strcasecmp("BRCorner", name))
1365         return OB_FRAME_CONTEXT_BRCORNER;
1366     else if (!g_ascii_strcasecmp("Top", name))
1367         return OB_FRAME_CONTEXT_TOP;
1368     else if (!g_ascii_strcasecmp("Bottom", name))
1369         return OB_FRAME_CONTEXT_BOTTOM;
1370     else if (!g_ascii_strcasecmp("Left", name))
1371         return OB_FRAME_CONTEXT_LEFT;
1372     else if (!g_ascii_strcasecmp("Right", name))
1373         return OB_FRAME_CONTEXT_RIGHT;
1374     else if (!g_ascii_strcasecmp("Maximize", name))
1375         return OB_FRAME_CONTEXT_MAXIMIZE;
1376     else if (!g_ascii_strcasecmp("AllDesktops", name))
1377         return OB_FRAME_CONTEXT_ALLDESKTOPS;
1378     else if (!g_ascii_strcasecmp("Shade", name))
1379         return OB_FRAME_CONTEXT_SHADE;
1380     else if (!g_ascii_strcasecmp("Iconify", name))
1381         return OB_FRAME_CONTEXT_ICONIFY;
1382     else if (!g_ascii_strcasecmp("Icon", name))
1383         return OB_FRAME_CONTEXT_ICON;
1384     else if (!g_ascii_strcasecmp("Close", name))
1385         return OB_FRAME_CONTEXT_CLOSE;
1386     else if (!g_ascii_strcasecmp("MoveResize", name))
1387         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1388     else if (!g_ascii_strcasecmp("Dock", name))
1389         return OB_FRAME_CONTEXT_DOCK;
1390     else if (!g_ascii_strcasecmp("ScreenTop", name))
1391         return OB_FRAME_CONTEXT_EDGE_TOP;
1392     else if (!g_ascii_strcasecmp("ScreenTopRight", name))
1393         return OB_FRAME_CONTEXT_EDGE_TOPRIGHT;
1394     else if (!g_ascii_strcasecmp("ScreenRight", name))
1395         return OB_FRAME_CONTEXT_EDGE_RIGHT;
1396     else if (!g_ascii_strcasecmp("ScreenBottomRight", name))
1397         return OB_FRAME_CONTEXT_EDGE_BOTTOMRIGHT;
1398     else if (!g_ascii_strcasecmp("ScreenBottom", name))
1399         return OB_FRAME_CONTEXT_EDGE_BOTTOM;
1400     else if (!g_ascii_strcasecmp("ScreenBottomLeft", name))
1401         return OB_FRAME_CONTEXT_EDGE_BOTTOMLEFT;
1402     else if (!g_ascii_strcasecmp("ScreenLeft", name))
1403         return OB_FRAME_CONTEXT_EDGE_LEFT;
1404     else if (!g_ascii_strcasecmp("ScreenTopLeft", name))
1405         return OB_FRAME_CONTEXT_EDGE_TOPLEFT;
1406
1407     return OB_FRAME_CONTEXT_NONE;
1408 }
1409
1410 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
1411 {
1412     ObFrame *self;
1413     ObWindow *obwin;
1414
1415     if (moveresize_in_progress)
1416         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1417     if (win == obt_root(ob_screen))
1418         return OB_FRAME_CONTEXT_ROOT;
1419     if ((obwin = window_find(win))) {
1420         if (WINDOW_IS_DOCK(obwin)) {
1421           return OB_FRAME_CONTEXT_DOCK;
1422         } else if (WINDOW_IS_EDGE(obwin)) {
1423           ObEdge *edge = (ObEdge *)obwin;
1424           return OB_FRAME_CONTEXT_EDGE_TOP + edge->location;
1425         }
1426     }
1427     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
1428     if (win == client->window) {
1429         /* conceptually, this is the desktop, as far as users are
1430            concerned */
1431         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1432             return OB_FRAME_CONTEXT_DESKTOP;
1433         return OB_FRAME_CONTEXT_CLIENT;
1434     }
1435
1436     self = client->frame;
1437
1438     /* when the user clicks in the corners of the titlebar and the client
1439        is fully maximized, then treat it like they clicked in the
1440        button that is there */
1441     if (self->max_horz && self->max_vert &&
1442         (win == self->title || win == self->titletop ||
1443          win == self->titleleft || win == self->titletopleft ||
1444          win == self->titleright || win == self->titletopright))
1445     {
1446         /* get the mouse coords in reference to the whole frame */
1447         gint fx = x;
1448         gint fy = y;
1449
1450         /* these windows are down a border width from the top of the frame */
1451         if (win == self->title ||
1452             win == self->titleleft || win == self->titleright)
1453             fy += self->bwidth;
1454
1455         /* title is a border width in from the edge */
1456         if (win == self->title)
1457             fx += self->bwidth;
1458         /* titletop is a bit to the right */
1459         else if (win == self->titletop)
1460             fx += ob_rr_theme->grip_width + self->bwidth;
1461         /* titletopright is way to the right edge */
1462         else if (win == self->titletopright)
1463             fx += self->area.width - (ob_rr_theme->grip_width + self->bwidth);
1464         /* titleright is even more way to the right edge */
1465         else if (win == self->titleright)
1466             fx += self->area.width - self->bwidth;
1467
1468         /* figure out if we're over the area that should be considered a
1469            button */
1470         if (fy < self->bwidth + ob_rr_theme->paddingy + 1 +
1471             ob_rr_theme->button_size)
1472         {
1473             if (fx < (self->bwidth + ob_rr_theme->paddingx + 1 +
1474                       ob_rr_theme->button_size))
1475             {
1476                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
1477                     return self->leftmost;
1478             }
1479             else if (fx >= (self->area.width -
1480                             (self->bwidth + ob_rr_theme->paddingx + 1 +
1481                              ob_rr_theme->button_size)))
1482             {
1483                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
1484                     return self->rightmost;
1485             }
1486         }
1487
1488         /* there is no resizing maximized windows so make them the titlebar
1489            context */
1490         return OB_FRAME_CONTEXT_TITLEBAR;
1491     }
1492     else if (self->max_vert &&
1493              (win == self->titletop || win == self->topresize))
1494         /* can't resize vertically when max vert */
1495         return OB_FRAME_CONTEXT_TITLEBAR;
1496     else if (self->shaded &&
1497              (win == self->titletop || win == self->topresize))
1498         /* can't resize vertically when shaded */
1499         return OB_FRAME_CONTEXT_TITLEBAR;
1500
1501     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
1502     if (win == self->label)             return OB_FRAME_CONTEXT_TITLEBAR;
1503     if (win == self->handle)            return OB_FRAME_CONTEXT_BOTTOM;
1504     if (win == self->handletop)         return OB_FRAME_CONTEXT_BOTTOM;
1505     if (win == self->handlebottom)      return OB_FRAME_CONTEXT_BOTTOM;
1506     if (win == self->handleleft)        return OB_FRAME_CONTEXT_BLCORNER;
1507     if (win == self->lgrip)             return OB_FRAME_CONTEXT_BLCORNER;
1508     if (win == self->lgripleft)         return OB_FRAME_CONTEXT_BLCORNER;
1509     if (win == self->lgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1510     if (win == self->lgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1511     if (win == self->handleright)       return OB_FRAME_CONTEXT_BRCORNER;
1512     if (win == self->rgrip)             return OB_FRAME_CONTEXT_BRCORNER;
1513     if (win == self->rgripright)        return OB_FRAME_CONTEXT_BRCORNER;
1514     if (win == self->rgriptop)          return OB_FRAME_CONTEXT_BRCORNER;
1515     if (win == self->rgripbottom)       return OB_FRAME_CONTEXT_BRCORNER;
1516     if (win == self->title)             return OB_FRAME_CONTEXT_TITLEBAR;
1517     if (win == self->titlebottom)       return OB_FRAME_CONTEXT_TITLEBAR;
1518     if (win == self->titleleft)         return OB_FRAME_CONTEXT_TLCORNER;
1519     if (win == self->titletopleft)      return OB_FRAME_CONTEXT_TLCORNER;
1520     if (win == self->titleright)        return OB_FRAME_CONTEXT_TRCORNER;
1521     if (win == self->titletopright)     return OB_FRAME_CONTEXT_TRCORNER;
1522     if (win == self->titletop)          return OB_FRAME_CONTEXT_TOP;
1523     if (win == self->topresize)         return OB_FRAME_CONTEXT_TOP;
1524     if (win == self->tltresize)         return OB_FRAME_CONTEXT_TLCORNER;
1525     if (win == self->tllresize)         return OB_FRAME_CONTEXT_TLCORNER;
1526     if (win == self->trtresize)         return OB_FRAME_CONTEXT_TRCORNER;
1527     if (win == self->trrresize)         return OB_FRAME_CONTEXT_TRCORNER;
1528     if (win == self->left)              return OB_FRAME_CONTEXT_LEFT;
1529     if (win == self->right)             return OB_FRAME_CONTEXT_RIGHT;
1530     if (win == self->innertop)          return OB_FRAME_CONTEXT_TITLEBAR;
1531     if (win == self->innerleft)         return OB_FRAME_CONTEXT_LEFT;
1532     if (win == self->innerbottom)       return OB_FRAME_CONTEXT_BOTTOM;
1533     if (win == self->innerright)        return OB_FRAME_CONTEXT_RIGHT;
1534     if (win == self->innerbll)          return OB_FRAME_CONTEXT_BLCORNER;
1535     if (win == self->innerblb)          return OB_FRAME_CONTEXT_BLCORNER;
1536     if (win == self->innerbrr)          return OB_FRAME_CONTEXT_BRCORNER;
1537     if (win == self->innerbrb)          return OB_FRAME_CONTEXT_BRCORNER;
1538     if (win == self->max)               return OB_FRAME_CONTEXT_MAXIMIZE;
1539     if (win == self->iconify)           return OB_FRAME_CONTEXT_ICONIFY;
1540     if (win == self->close)             return OB_FRAME_CONTEXT_CLOSE;
1541     if (win == self->icon)              return OB_FRAME_CONTEXT_ICON;
1542     if (win == self->desk)              return OB_FRAME_CONTEXT_ALLDESKTOPS;
1543     if (win == self->shade)             return OB_FRAME_CONTEXT_SHADE;
1544
1545     return OB_FRAME_CONTEXT_NONE;
1546 }
1547
1548 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
1549 {
1550     /* horizontal */
1551     switch (self->client->gravity) {
1552     default:
1553     case NorthWestGravity:
1554     case SouthWestGravity:
1555     case WestGravity:
1556         break;
1557
1558     case NorthGravity:
1559     case SouthGravity:
1560     case CenterGravity:
1561         /* the middle of the client will be the middle of the frame */
1562         *x -= (self->size.right - self->size.left) / 2;
1563         break;
1564
1565     case NorthEastGravity:
1566     case SouthEastGravity:
1567     case EastGravity:
1568         /* the right side of the client will be the right side of the frame */
1569         *x -= self->size.right + self->size.left -
1570             self->client->border_width * 2;
1571         break;
1572
1573     case ForgetGravity:
1574     case StaticGravity:
1575         /* the client's position won't move */
1576         *x -= self->size.left - self->client->border_width;
1577         break;
1578     }
1579
1580     /* vertical */
1581     switch (self->client->gravity) {
1582     default:
1583     case NorthWestGravity:
1584     case NorthEastGravity:
1585     case NorthGravity:
1586         break;
1587
1588     case CenterGravity:
1589     case EastGravity:
1590     case WestGravity:
1591         /* the middle of the client will be the middle of the frame */
1592         *y -= (self->size.bottom - self->size.top) / 2;
1593         break;
1594
1595     case SouthWestGravity:
1596     case SouthEastGravity:
1597     case SouthGravity:
1598         /* the bottom of the client will be the bottom of the frame */
1599         *y -= self->size.bottom + self->size.top -
1600             self->client->border_width * 2;
1601         break;
1602
1603     case ForgetGravity:
1604     case StaticGravity:
1605         /* the client's position won't move */
1606         *y -= self->size.top - self->client->border_width;
1607         break;
1608     }
1609 }
1610
1611 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
1612 {
1613     /* horizontal */
1614     switch (self->client->gravity) {
1615     default:
1616     case NorthWestGravity:
1617     case WestGravity:
1618     case SouthWestGravity:
1619         break;
1620     case NorthGravity:
1621     case CenterGravity:
1622     case SouthGravity:
1623         /* the middle of the client will be the middle of the frame */
1624         *x += (self->size.right - self->size.left) / 2;
1625         break;
1626     case NorthEastGravity:
1627     case EastGravity:
1628     case SouthEastGravity:
1629         /* the right side of the client will be the right side of the frame */
1630         *x += self->size.right + self->size.left -
1631             self->client->border_width * 2;
1632         break;
1633     case StaticGravity:
1634     case ForgetGravity:
1635         /* the client's position won't move */
1636         *x += self->size.left - self->client->border_width;
1637         break;
1638     }
1639
1640     /* vertical */
1641     switch (self->client->gravity) {
1642     default:
1643     case NorthWestGravity:
1644     case NorthGravity:
1645     case NorthEastGravity:
1646         break;
1647     case WestGravity:
1648     case CenterGravity:
1649     case EastGravity:
1650         /* the middle of the client will be the middle of the frame */
1651         *y += (self->size.bottom - self->size.top) / 2;
1652         break;
1653     case SouthWestGravity:
1654     case SouthGravity:
1655     case SouthEastGravity:
1656         /* the bottom of the client will be the bottom of the frame */
1657         *y += self->size.bottom + self->size.top -
1658             self->client->border_width * 2;
1659         break;
1660     case StaticGravity:
1661     case ForgetGravity:
1662         /* the client's position won't move */
1663         *y += self->size.top - self->client->border_width;
1664         break;
1665     }
1666 }
1667
1668 void frame_rect_to_frame(ObFrame *self, Rect *r)
1669 {
1670     r->width += self->size.left + self->size.right;
1671     r->height += self->size.top + self->size.bottom;
1672     frame_client_gravity(self, &r->x, &r->y);
1673 }
1674
1675 void frame_rect_to_client(ObFrame *self, Rect *r)
1676 {
1677     r->width -= self->size.left + self->size.right;
1678     r->height -= self->size.top + self->size.bottom;
1679     frame_frame_gravity(self, &r->x, &r->y);
1680 }
1681
1682 static void flash_done(gpointer data)
1683 {
1684     ObFrame *self = data;
1685
1686     self->flash_timer = 0;
1687 }
1688
1689 static gboolean flash_timeout(gpointer data)
1690 {
1691     ObFrame *self = data;
1692     GTimeVal now;
1693
1694     g_get_current_time(&now);
1695     if (now.tv_sec > self->flash_end.tv_sec ||
1696         (now.tv_sec == self->flash_end.tv_sec &&
1697          now.tv_usec >= self->flash_end.tv_usec))
1698         self->flashing = FALSE;
1699
1700     if (!self->flashing) {
1701         if (self->focused != self->flash_on)
1702             frame_adjust_focus(self, self->focused);
1703
1704         return FALSE; /* we are done */
1705     }
1706
1707     self->flash_on = !self->flash_on;
1708     if (!self->focused) {
1709         frame_adjust_focus(self, self->flash_on);
1710         self->focused = FALSE;
1711     }
1712
1713     return TRUE; /* go again */
1714 }
1715
1716 void frame_flash_start(ObFrame *self)
1717 {
1718     self->flash_on = self->focused;
1719
1720     if (!self->flashing)
1721         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
1722                                                600, flash_timeout, self,
1723                                                flash_done);
1724     g_get_current_time(&self->flash_end);
1725     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1726
1727     self->flashing = TRUE;
1728 }
1729
1730 void frame_flash_stop(ObFrame *self)
1731 {
1732     self->flashing = FALSE;
1733 }
1734
1735 static gulong frame_animate_iconify_time_left(ObFrame *self,
1736                                               const GTimeVal *now)
1737 {
1738     glong sec, usec;
1739     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1740     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1741     if (usec < 0) {
1742         usec += G_USEC_PER_SEC;
1743         sec--;
1744     }
1745     /* no negative values */
1746     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1747 }
1748
1749 static gboolean frame_animate_iconify(gpointer p)
1750 {
1751     ObFrame *self = p;
1752     gint x, y, w, h;
1753     gint iconx, icony, iconw;
1754     GTimeVal now;
1755     gulong time;
1756     gboolean iconifying;
1757
1758     if (self->client->icon_geometry.width == 0) {
1759         /* there is no icon geometry set so just go straight down */
1760         const Rect *a;
1761
1762         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
1763         iconx = self->area.x + self->area.width / 2 + 32;
1764         icony = a->y + a->width;
1765         iconw = 64;
1766     } else {
1767         iconx = self->client->icon_geometry.x;
1768         icony = self->client->icon_geometry.y;
1769         iconw = self->client->icon_geometry.width;
1770     }
1771
1772     iconifying = self->iconify_animation_going > 0;
1773
1774     /* how far do we have left to go ? */
1775     g_get_current_time(&now);
1776     time = frame_animate_iconify_time_left(self, &now);
1777
1778     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
1779         /* start where the frame is supposed to be */
1780         x = self->area.x;
1781         y = self->area.y;
1782         w = self->area.width;
1783         h = self->area.height;
1784     } else {
1785         /* start at the icon */
1786         x = iconx;
1787         y = icony;
1788         w = iconw;
1789         h = self->size.top; /* just the titlebar */
1790     }
1791
1792     if (time > 0) {
1793         glong dx, dy, dw;
1794         glong elapsed;
1795
1796         dx = self->area.x - iconx;
1797         dy = self->area.y - icony;
1798         dw = self->area.width - self->bwidth * 2 - iconw;
1799          /* if restoring, we move in the opposite direction */
1800         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1801
1802         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1803         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1804         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1805         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1806         h = self->size.top; /* just the titlebar */
1807     }
1808
1809     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
1810     XFlush(obt_display);
1811
1812     return time > 0; /* repeat until we're out of time */
1813 }
1814
1815 void frame_end_iconify_animation(gpointer data)
1816 {
1817     ObFrame *self = data;
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     self->iconify_animation_timer = 0;
1835
1836     XMoveResizeWindow(obt_display, self->window,
1837                       self->area.x, self->area.y,
1838                       self->area.width, self->area.height);
1839     /* we delay re-rendering until after we're done animating */
1840     framerender_frame(self);
1841     XFlush(obt_display);
1842 }
1843
1844 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1845 {
1846     gulong time;
1847     gboolean new_anim = FALSE;
1848     gboolean set_end = TRUE;
1849     GTimeVal now;
1850
1851     /* if there is no titlebar, just don't animate for now
1852        XXX it would be nice tho.. */
1853     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1854         return;
1855
1856     /* get the current time */
1857     g_get_current_time(&now);
1858
1859     /* get how long until the end */
1860     time = FRAME_ANIMATE_ICONIFY_TIME;
1861     if (self->iconify_animation_going) {
1862         if (!!iconifying != (self->iconify_animation_going > 0)) {
1863             /* animation was already going on in the opposite direction */
1864             time = time - frame_animate_iconify_time_left(self, &now);
1865         } else
1866             /* animation was already going in the same direction */
1867             set_end = FALSE;
1868     } else
1869         new_anim = TRUE;
1870     self->iconify_animation_going = iconifying ? 1 : -1;
1871
1872     /* set the ending time */
1873     if (set_end) {
1874         self->iconify_animation_end.tv_sec = now.tv_sec;
1875         self->iconify_animation_end.tv_usec = now.tv_usec;
1876         g_time_val_add(&self->iconify_animation_end, time);
1877     }
1878
1879     if (new_anim) {
1880         if (self->iconify_animation_timer)
1881             g_source_remove(self->iconify_animation_timer);
1882         self->iconify_animation_timer =
1883             g_timeout_add_full(G_PRIORITY_DEFAULT,
1884                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1885                                frame_animate_iconify, self,
1886                                frame_end_iconify_animation);
1887                                
1888
1889         /* do the first step */
1890         frame_animate_iconify(self);
1891
1892         /* show it during the animation even if it is not "visible" */
1893         if (!self->visible)
1894             XMapWindow(obt_display, self->window);
1895     }
1896 }