Refactor the per-app parsing code into a helper function and reduce nesting by 2...
[dana/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "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 "obrender/theme.h"
32 #include "obt/display.h"
33 #include "obt/xqueue.h"
34 #include "obt/prop.h"
35
36 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
37                          ButtonPressMask | ButtonReleaseMask | \
38                          SubstructureRedirectMask | FocusChangeMask)
39 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
40                            ButtonMotionMask | PointerMotionMask | \
41                            EnterWindowMask | LeaveWindowMask)
42
43 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
44 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (1000 / 60) /* 60 Hz */
45
46 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_b)
47
48 static void flash_done(gpointer data);
49 static gboolean flash_timeout(gpointer data);
50
51 static void layout_title(ObFrame *self);
52 static void set_theme_statics(ObFrame *self);
53 static void free_theme_statics(ObFrame *self);
54 static gboolean frame_animate_iconify(gpointer self);
55 static void frame_adjust_cursors(ObFrame *self);
56
57 static Window createWindow(Window parent, Visual *visual,
58                            gulong mask, XSetWindowAttributes *attrib)
59 {
60     return XCreateWindow(obt_display, parent, 0, 0, 1, 1, 0,
61                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
62                          (visual ? visual : RrVisual(ob_rr_inst)),
63                          mask, attrib);
64
65 }
66
67 static Visual *check_32bit_client(ObClient *c)
68 {
69     XWindowAttributes wattrib;
70     Status ret;
71
72     /* we're already running at 32 bit depth, yay. we don't need to use their
73        visual */
74     if (RrDepth(ob_rr_inst) == 32)
75         return NULL;
76
77     ret = XGetWindowAttributes(obt_display, c->window, &wattrib);
78     g_assert(ret != BadDrawable);
79     g_assert(ret != BadWindow);
80
81     if (wattrib.depth == 32)
82         return wattrib.visual;
83     return NULL;
84 }
85
86 ObFrame *frame_new(ObClient *client)
87 {
88     XSetWindowAttributes attrib;
89     gulong mask;
90     ObFrame *self;
91     Visual *visual;
92
93     self = g_slice_new0(ObFrame);
94     self->client = client;
95
96     visual = check_32bit_client(client);
97
98     /* create the non-visible decor windows */
99
100     mask = 0;
101     if (visual) {
102         /* client has a 32-bit visual */
103         mask = CWColormap | CWBackPixel | CWBorderPixel;
104         /* create a colormap with the visual */
105         self->colormap = attrib.colormap =
106             XCreateColormap(obt_display, obt_root(ob_screen),
107                             visual, AllocNone);
108         attrib.background_pixel = BlackPixel(obt_display, ob_screen);
109         attrib.border_pixel = BlackPixel(obt_display, ob_screen);
110     }
111     self->window = createWindow(obt_root(ob_screen), visual,
112                                 mask, &attrib);
113
114     /* create the visible decor windows */
115
116     mask = 0;
117     if (visual) {
118         /* client has a 32-bit visual */
119         mask = CWColormap | CWBackPixel | CWBorderPixel;
120         attrib.colormap = RrColormap(ob_rr_inst);
121     }
122
123     self->backback = createWindow(self->window, NULL, mask, &attrib);
124     self->backfront = createWindow(self->backback, NULL, mask, &attrib);
125
126     mask |= CWEventMask;
127     attrib.event_mask = ELEMENT_EVENTMASK;
128     self->innerleft = createWindow(self->window, NULL, mask, &attrib);
129     self->innertop = createWindow(self->window, NULL, mask, &attrib);
130     self->innerright = createWindow(self->window, NULL, mask, &attrib);
131     self->innerbottom = createWindow(self->window, NULL, mask, &attrib);
132
133     self->innerblb = createWindow(self->innerbottom, NULL, mask, &attrib);
134     self->innerbrb = createWindow(self->innerbottom, NULL, mask, &attrib);
135     self->innerbll = createWindow(self->innerleft, NULL, mask, &attrib);
136     self->innerbrr = createWindow(self->innerright, NULL, mask, &attrib);
137
138     self->title = createWindow(self->window, NULL, mask, &attrib);
139     self->titleleft = createWindow(self->window, NULL, mask, &attrib);
140     self->titletop = createWindow(self->window, NULL, mask, &attrib);
141     self->titletopleft = createWindow(self->window, NULL, mask, &attrib);
142     self->titletopright = createWindow(self->window, NULL, mask, &attrib);
143     self->titleright = createWindow(self->window, NULL, mask, &attrib);
144     self->titlebottom = createWindow(self->window, NULL, mask, &attrib);
145
146     self->topresize = createWindow(self->title, NULL, mask, &attrib);
147     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
148     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
149     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
150     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
151
152     self->left = createWindow(self->window, NULL, mask, &attrib);
153     self->right = createWindow(self->window, NULL, mask, &attrib);
154
155     self->label = createWindow(self->title, NULL, mask, &attrib);
156     self->max = createWindow(self->title, NULL, mask, &attrib);
157     self->close = createWindow(self->title, NULL, mask, &attrib);
158     self->desk = createWindow(self->title, NULL, mask, &attrib);
159     self->shade = createWindow(self->title, NULL, mask, &attrib);
160     self->icon = createWindow(self->title, NULL, mask, &attrib);
161     self->iconify = createWindow(self->title, NULL, mask, &attrib);
162
163     self->handle = createWindow(self->window, NULL, mask, &attrib);
164     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
165     self->rgrip = createWindow(self->handle, NULL, mask, &attrib);
166
167     self->handleleft = createWindow(self->handle, NULL, mask, &attrib);
168     self->handleright = createWindow(self->handle, NULL, mask, &attrib);
169
170     self->handletop = createWindow(self->window, NULL, mask, &attrib);
171     self->handlebottom = createWindow(self->window, NULL, mask, &attrib);
172     self->lgripleft = createWindow(self->window, NULL, mask, &attrib);
173     self->lgriptop = createWindow(self->window, NULL, mask, &attrib);
174     self->lgripbottom = createWindow(self->window, NULL, mask, &attrib);
175     self->rgripright = createWindow(self->window, NULL, mask, &attrib);
176     self->rgriptop = createWindow(self->window, NULL, mask, &attrib);
177     self->rgripbottom = createWindow(self->window, NULL, mask, &attrib);
178
179     self->focused = FALSE;
180
181     /* the other stuff is shown based on decor settings */
182     XMapWindow(obt_display, self->label);
183     XMapWindow(obt_display, self->backback);
184     XMapWindow(obt_display, self->backfront);
185
186     self->max_press = self->close_press = self->desk_press =
187         self->iconify_press = self->shade_press = FALSE;
188     self->max_hover = self->close_hover = self->desk_hover =
189         self->iconify_hover = self->shade_hover = FALSE;
190
191     /* make sure the size will be different the first time, so the extent hints
192        will be set */
193     STRUT_SET(self->oldsize, -1, -1, -1, -1);
194
195     set_theme_statics(self);
196
197     return self;
198 }
199
200 static void set_theme_statics(ObFrame *self)
201 {
202     /* set colors/appearance/sizes for stuff that doesn't change */
203     XResizeWindow(obt_display, self->max,
204                   ob_rr_theme->button_size, ob_rr_theme->button_size);
205     XResizeWindow(obt_display, self->iconify,
206                   ob_rr_theme->button_size, ob_rr_theme->button_size);
207     XResizeWindow(obt_display, self->icon,
208                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
209     XResizeWindow(obt_display, self->close,
210                   ob_rr_theme->button_size, ob_rr_theme->button_size);
211     XResizeWindow(obt_display, self->desk,
212                   ob_rr_theme->button_size, ob_rr_theme->button_size);
213     XResizeWindow(obt_display, self->shade,
214                   ob_rr_theme->button_size, ob_rr_theme->button_size);
215     XResizeWindow(obt_display, self->tltresize,
216                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
217     XResizeWindow(obt_display, self->trtresize,
218                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
219     XResizeWindow(obt_display, self->tllresize,
220                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
221     XResizeWindow(obt_display, self->trrresize,
222                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
223 }
224
225 static void free_theme_statics(ObFrame *self)
226 {
227 }
228
229 void frame_free(ObFrame *self)
230 {
231     free_theme_statics(self);
232
233     XDestroyWindow(obt_display, self->window);
234     if (self->colormap)
235         XFreeColormap(obt_display, self->colormap);
236
237     g_slice_free(ObFrame, self);
238 }
239
240 void frame_show(ObFrame *self)
241 {
242     if (!self->visible) {
243         self->visible = TRUE;
244         framerender_frame(self);
245         /* Grab the server to make sure that the frame window is mapped before
246            the client gets its MapNotify, i.e. to make sure the client is
247            _visible_ when it gets MapNotify. */
248         grab_server(TRUE);
249         XMapWindow(obt_display, self->client->window);
250         XMapWindow(obt_display, self->window);
251         grab_server(FALSE);
252     }
253 }
254
255 void frame_hide(ObFrame *self)
256 {
257     if (self->visible) {
258         self->visible = FALSE;
259         if (!frame_iconify_animating(self))
260             XUnmapWindow(obt_display, self->window);
261         /* we unmap the client itself so that we can get MapRequest
262            events, and because the ICCCM tells us to! */
263         XUnmapWindow(obt_display, self->client->window);
264         self->client->ignore_unmaps += 1;
265     }
266 }
267
268 void frame_adjust_theme(ObFrame *self)
269 {
270     free_theme_statics(self);
271     set_theme_statics(self);
272 }
273
274 #ifdef SHAPE
275 void frame_adjust_shape_kind(ObFrame *self, int kind)
276 {
277     gint num;
278     XRectangle xrect[2];
279     gboolean shaped;
280
281     shaped = (kind == ShapeBounding && self->client->shaped);
282 #ifdef ShapeInput
283     shaped |= (kind == ShapeInput && self->client->shaped_input);
284 #endif
285
286     if (!shaped) {
287         /* clear the shape on the frame window */
288         XShapeCombineMask(obt_display, self->window, kind,
289                           self->size.left,
290                           self->size.top,
291                           None, ShapeSet);
292     } else {
293         /* make the frame's shape match the clients */
294         XShapeCombineShape(obt_display, self->window, kind,
295                            self->size.left,
296                            self->size.top,
297                            self->client->window,
298                            kind, ShapeSet);
299
300         num = 0;
301         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
302             xrect[0].x = 0;
303             xrect[0].y = 0;
304             xrect[0].width = self->area.width;
305             xrect[0].height = self->size.top;
306             ++num;
307         }
308
309         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
310             ob_rr_theme->handle_height > 0)
311         {
312             xrect[1].x = 0;
313             xrect[1].y = FRAME_HANDLE_Y(self);
314             xrect[1].width = self->area.width;
315             xrect[1].height = ob_rr_theme->handle_height +
316                 self->bwidth * 2;
317             ++num;
318         }
319
320         XShapeCombineRectangles(obt_display, self->window,
321                                 ShapeBounding, 0, 0, xrect, num,
322                                 ShapeUnion, Unsorted);
323     }
324 }
325 #endif
326
327 void frame_adjust_shape(ObFrame *self)
328 {
329 #ifdef SHAPE
330   frame_adjust_shape_kind(self, ShapeBounding);
331 #ifdef ShapeInput
332   frame_adjust_shape_kind(self, ShapeInput);
333 #endif
334 #endif
335 }
336
337 void frame_adjust_area(ObFrame *self, gboolean moved,
338                        gboolean resized, gboolean fake)
339 {
340     if (resized) {
341         /* do this before changing the frame's status like max_horz max_vert */
342         frame_adjust_cursors(self);
343
344         self->functions = self->client->functions;
345         self->decorations = self->client->decorations;
346         self->max_horz = self->client->max_horz;
347         self->max_vert = self->client->max_vert;
348         self->shaded = self->client->shaded;
349
350         if (self->decorations & OB_FRAME_DECOR_BORDER)
351             self->bwidth = self->client->undecorated ?
352                 ob_rr_theme->ubwidth : ob_rr_theme->fbwidth;
353         else
354             self->bwidth = 0;
355
356         if (self->decorations & OB_FRAME_DECOR_BORDER &&
357             !self->client->undecorated)
358         {
359             self->cbwidth_l = self->cbwidth_r = ob_rr_theme->cbwidthx;
360             self->cbwidth_t = self->cbwidth_b = ob_rr_theme->cbwidthy;
361         } else
362             self->cbwidth_l = self->cbwidth_t =
363                 self->cbwidth_r = self->cbwidth_b = 0;
364
365         if (self->max_horz) {
366             self->cbwidth_l = self->cbwidth_r = 0;
367             self->width = self->client->area.width;
368             if (self->max_vert)
369                 self->cbwidth_b = 0;
370         } else
371             self->width = self->client->area.width +
372                 self->cbwidth_l + self->cbwidth_r;
373
374         /* some elements are sized based of the width, so don't let them have
375            negative values */
376         self->width = MAX(self->width,
377                           (ob_rr_theme->grip_width + self->bwidth) * 2 + 1);
378
379         STRUT_SET(self->size,
380                   self->cbwidth_l + (!self->max_horz ? self->bwidth : 0),
381                   self->cbwidth_t +
382                   (!self->max_horz || !self->max_vert ? self->bwidth : 0),
383                   self->cbwidth_r + (!self->max_horz ? self->bwidth : 0),
384                   self->cbwidth_b +
385                   (!self->max_horz || !self->max_vert ? self->bwidth : 0));
386
387         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
388             self->size.top += ob_rr_theme->title_height + self->bwidth;
389         else if (self->max_horz && self->max_vert) {
390             /* A maximized and undecorated window needs a small border on the
391                top of the window to let the user still undecorate/unmaximize the
392                window via the client menu. */
393             /* XXX This size should probably be a theme option. */
394             self->size.top += 1;
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
1391     return OB_FRAME_CONTEXT_NONE;
1392 }
1393
1394 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
1395 {
1396     ObFrame *self;
1397     ObWindow *obwin;
1398
1399     if (moveresize_in_progress)
1400         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1401
1402     if (win == obt_root(ob_screen))
1403         return OB_FRAME_CONTEXT_ROOT;
1404     if ((obwin = window_find(win))) {
1405         if (WINDOW_IS_DOCK(obwin)) {
1406           return OB_FRAME_CONTEXT_DOCK;
1407         }
1408     }
1409     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
1410     if (win == client->window) {
1411         /* conceptually, this is the desktop, as far as users are
1412            concerned */
1413         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1414             return OB_FRAME_CONTEXT_DESKTOP;
1415         return OB_FRAME_CONTEXT_CLIENT;
1416     }
1417
1418     self = client->frame;
1419
1420     /* when the user clicks in the corners of the titlebar and the client
1421        is fully maximized, then treat it like they clicked in the
1422        button that is there */
1423     if (self->max_horz && self->max_vert &&
1424         (win == self->title || win == self->titletop ||
1425          win == self->titleleft || win == self->titletopleft ||
1426          win == self->titleright || win == self->titletopright))
1427     {
1428         /* get the mouse coords in reference to the whole frame */
1429         gint fx = x;
1430         gint fy = y;
1431
1432         /* these windows are down a border width from the top of the frame */
1433         if (win == self->title ||
1434             win == self->titleleft || win == self->titleright)
1435             fy += self->bwidth;
1436
1437         /* title is a border width in from the edge */
1438         if (win == self->title)
1439             fx += self->bwidth;
1440         /* titletop is a bit to the right */
1441         else if (win == self->titletop)
1442             fx += ob_rr_theme->grip_width + self->bwidth;
1443         /* titletopright is way to the right edge */
1444         else if (win == self->titletopright)
1445             fx += self->area.width - (ob_rr_theme->grip_width + self->bwidth);
1446         /* titleright is even more way to the right edge */
1447         else if (win == self->titleright)
1448             fx += self->area.width - self->bwidth;
1449
1450         /* figure out if we're over the area that should be considered a
1451            button */
1452         if (fy < self->bwidth + ob_rr_theme->paddingy + 1 +
1453             ob_rr_theme->button_size)
1454         {
1455             if (fx < (self->bwidth + ob_rr_theme->paddingx + 1 +
1456                       ob_rr_theme->button_size))
1457             {
1458                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
1459                     return self->leftmost;
1460             }
1461             else if (fx >= (self->area.width -
1462                             (self->bwidth + ob_rr_theme->paddingx + 1 +
1463                              ob_rr_theme->button_size)))
1464             {
1465                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
1466                     return self->rightmost;
1467             }
1468         }
1469
1470         /* there is no resizing maximized windows so make them the titlebar
1471            context */
1472         return OB_FRAME_CONTEXT_TITLEBAR;
1473     }
1474     else if (self->max_vert &&
1475              (win == self->titletop || win == self->topresize))
1476         /* can't resize vertically when max vert */
1477         return OB_FRAME_CONTEXT_TITLEBAR;
1478     else if (self->shaded &&
1479              (win == self->titletop || win == self->topresize))
1480         /* can't resize vertically when shaded */
1481         return OB_FRAME_CONTEXT_TITLEBAR;
1482
1483     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
1484     if (win == self->label)             return OB_FRAME_CONTEXT_TITLEBAR;
1485     if (win == self->handle)            return OB_FRAME_CONTEXT_BOTTOM;
1486     if (win == self->handletop)         return OB_FRAME_CONTEXT_BOTTOM;
1487     if (win == self->handlebottom)      return OB_FRAME_CONTEXT_BOTTOM;
1488     if (win == self->handleleft)        return OB_FRAME_CONTEXT_BLCORNER;
1489     if (win == self->lgrip)             return OB_FRAME_CONTEXT_BLCORNER;
1490     if (win == self->lgripleft)         return OB_FRAME_CONTEXT_BLCORNER;
1491     if (win == self->lgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1492     if (win == self->lgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1493     if (win == self->handleright)       return OB_FRAME_CONTEXT_BRCORNER;
1494     if (win == self->rgrip)             return OB_FRAME_CONTEXT_BRCORNER;
1495     if (win == self->rgripright)        return OB_FRAME_CONTEXT_BRCORNER;
1496     if (win == self->rgriptop)          return OB_FRAME_CONTEXT_BRCORNER;
1497     if (win == self->rgripbottom)       return OB_FRAME_CONTEXT_BRCORNER;
1498     if (win == self->title)             return OB_FRAME_CONTEXT_TITLEBAR;
1499     if (win == self->titlebottom)       return OB_FRAME_CONTEXT_TITLEBAR;
1500     if (win == self->titleleft)         return OB_FRAME_CONTEXT_TLCORNER;
1501     if (win == self->titletopleft)      return OB_FRAME_CONTEXT_TLCORNER;
1502     if (win == self->titleright)        return OB_FRAME_CONTEXT_TRCORNER;
1503     if (win == self->titletopright)     return OB_FRAME_CONTEXT_TRCORNER;
1504     if (win == self->titletop)          return OB_FRAME_CONTEXT_TOP;
1505     if (win == self->topresize)         return OB_FRAME_CONTEXT_TOP;
1506     if (win == self->tltresize)         return OB_FRAME_CONTEXT_TLCORNER;
1507     if (win == self->tllresize)         return OB_FRAME_CONTEXT_TLCORNER;
1508     if (win == self->trtresize)         return OB_FRAME_CONTEXT_TRCORNER;
1509     if (win == self->trrresize)         return OB_FRAME_CONTEXT_TRCORNER;
1510     if (win == self->left)              return OB_FRAME_CONTEXT_LEFT;
1511     if (win == self->right)             return OB_FRAME_CONTEXT_RIGHT;
1512     if (win == self->innertop)          return OB_FRAME_CONTEXT_TITLEBAR;
1513     if (win == self->innerleft)         return OB_FRAME_CONTEXT_LEFT;
1514     if (win == self->innerbottom)       return OB_FRAME_CONTEXT_BOTTOM;
1515     if (win == self->innerright)        return OB_FRAME_CONTEXT_RIGHT;
1516     if (win == self->innerbll)          return OB_FRAME_CONTEXT_BLCORNER;
1517     if (win == self->innerblb)          return OB_FRAME_CONTEXT_BLCORNER;
1518     if (win == self->innerbrr)          return OB_FRAME_CONTEXT_BRCORNER;
1519     if (win == self->innerbrb)          return OB_FRAME_CONTEXT_BRCORNER;
1520     if (win == self->max)               return OB_FRAME_CONTEXT_MAXIMIZE;
1521     if (win == self->iconify)           return OB_FRAME_CONTEXT_ICONIFY;
1522     if (win == self->close)             return OB_FRAME_CONTEXT_CLOSE;
1523     if (win == self->icon)              return OB_FRAME_CONTEXT_ICON;
1524     if (win == self->desk)              return OB_FRAME_CONTEXT_ALLDESKTOPS;
1525     if (win == self->shade)             return OB_FRAME_CONTEXT_SHADE;
1526
1527     return OB_FRAME_CONTEXT_NONE;
1528 }
1529
1530 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
1531 {
1532     /* horizontal */
1533     switch (self->client->gravity) {
1534     default:
1535     case NorthWestGravity:
1536     case SouthWestGravity:
1537     case WestGravity:
1538         break;
1539
1540     case NorthGravity:
1541     case SouthGravity:
1542     case CenterGravity:
1543         /* the middle of the client will be the middle of the frame */
1544         *x -= (self->size.right - self->size.left) / 2;
1545         break;
1546
1547     case NorthEastGravity:
1548     case SouthEastGravity:
1549     case EastGravity:
1550         /* the right side of the client will be the right side of the frame */
1551         *x -= self->size.right + self->size.left -
1552             self->client->border_width * 2;
1553         break;
1554
1555     case ForgetGravity:
1556     case StaticGravity:
1557         /* the client's position won't move */
1558         *x -= self->size.left - self->client->border_width;
1559         break;
1560     }
1561
1562     /* vertical */
1563     switch (self->client->gravity) {
1564     default:
1565     case NorthWestGravity:
1566     case NorthEastGravity:
1567     case NorthGravity:
1568         break;
1569
1570     case CenterGravity:
1571     case EastGravity:
1572     case WestGravity:
1573         /* the middle of the client will be the middle of the frame */
1574         *y -= (self->size.bottom - self->size.top) / 2;
1575         break;
1576
1577     case SouthWestGravity:
1578     case SouthEastGravity:
1579     case SouthGravity:
1580         /* the bottom of the client will be the bottom of the frame */
1581         *y -= self->size.bottom + self->size.top -
1582             self->client->border_width * 2;
1583         break;
1584
1585     case ForgetGravity:
1586     case StaticGravity:
1587         /* the client's position won't move */
1588         *y -= self->size.top - self->client->border_width;
1589         break;
1590     }
1591 }
1592
1593 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
1594 {
1595     /* horizontal */
1596     switch (self->client->gravity) {
1597     default:
1598     case NorthWestGravity:
1599     case WestGravity:
1600     case SouthWestGravity:
1601         break;
1602     case NorthGravity:
1603     case CenterGravity:
1604     case SouthGravity:
1605         /* the middle of the client will be the middle of the frame */
1606         *x += (self->size.right - self->size.left) / 2;
1607         break;
1608     case NorthEastGravity:
1609     case EastGravity:
1610     case SouthEastGravity:
1611         /* the right side of the client will be the right side of the frame */
1612         *x += self->size.right + self->size.left -
1613             self->client->border_width * 2;
1614         break;
1615     case StaticGravity:
1616     case ForgetGravity:
1617         /* the client's position won't move */
1618         *x += self->size.left - self->client->border_width;
1619         break;
1620     }
1621
1622     /* vertical */
1623     switch (self->client->gravity) {
1624     default:
1625     case NorthWestGravity:
1626     case NorthGravity:
1627     case NorthEastGravity:
1628         break;
1629     case WestGravity:
1630     case CenterGravity:
1631     case EastGravity:
1632         /* the middle of the client will be the middle of the frame */
1633         *y += (self->size.bottom - self->size.top) / 2;
1634         break;
1635     case SouthWestGravity:
1636     case SouthGravity:
1637     case SouthEastGravity:
1638         /* the bottom of the client will be the bottom of the frame */
1639         *y += self->size.bottom + self->size.top -
1640             self->client->border_width * 2;
1641         break;
1642     case StaticGravity:
1643     case ForgetGravity:
1644         /* the client's position won't move */
1645         *y += self->size.top - self->client->border_width;
1646         break;
1647     }
1648 }
1649
1650 void frame_rect_to_frame(ObFrame *self, Rect *r)
1651 {
1652     r->width += self->size.left + self->size.right;
1653     r->height += self->size.top + self->size.bottom;
1654     frame_client_gravity(self, &r->x, &r->y);
1655 }
1656
1657 void frame_rect_to_client(ObFrame *self, Rect *r)
1658 {
1659     r->width -= self->size.left + self->size.right;
1660     r->height -= self->size.top + self->size.bottom;
1661     frame_frame_gravity(self, &r->x, &r->y);
1662 }
1663
1664 static void flash_done(gpointer data)
1665 {
1666     ObFrame *self = data;
1667
1668     if (self->focused != self->flash_on)
1669         frame_adjust_focus(self, self->focused);
1670 }
1671
1672 static gboolean flash_timeout(gpointer data)
1673 {
1674     ObFrame *self = data;
1675     GTimeVal now;
1676
1677     g_get_current_time(&now);
1678     if (now.tv_sec > self->flash_end.tv_sec ||
1679         (now.tv_sec == self->flash_end.tv_sec &&
1680          now.tv_usec >= self->flash_end.tv_usec))
1681         self->flashing = FALSE;
1682
1683     if (!self->flashing)
1684         return FALSE; /* we are done */
1685
1686     self->flash_on = !self->flash_on;
1687     if (!self->focused) {
1688         frame_adjust_focus(self, self->flash_on);
1689         self->focused = FALSE;
1690     }
1691
1692     return TRUE; /* go again */
1693 }
1694
1695 void frame_flash_start(ObFrame *self)
1696 {
1697     self->flash_on = self->focused;
1698
1699     if (!self->flashing)
1700         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
1701                                                600, flash_timeout, self,
1702                                                flash_done);
1703     g_get_current_time(&self->flash_end);
1704     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1705
1706     self->flashing = TRUE;
1707 }
1708
1709 void frame_flash_stop(ObFrame *self)
1710 {
1711     self->flashing = FALSE;
1712 }
1713
1714 static gulong frame_animate_iconify_time_left(ObFrame *self,
1715                                               const GTimeVal *now)
1716 {
1717     glong sec, usec;
1718     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1719     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1720     if (usec < 0) {
1721         usec += G_USEC_PER_SEC;
1722         sec--;
1723     }
1724     /* no negative values */
1725     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1726 }
1727
1728 static gboolean frame_animate_iconify(gpointer p)
1729 {
1730     ObFrame *self = p;
1731     gint x, y, w, h;
1732     gint iconx, icony, iconw;
1733     GTimeVal now;
1734     gulong time;
1735     gboolean iconifying;
1736
1737     if (self->client->icon_geometry.width == 0) {
1738         /* there is no icon geometry set so just go straight down */
1739         const Rect *a;
1740
1741         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
1742         iconx = self->area.x + self->area.width / 2 + 32;
1743         icony = a->y + a->width;
1744         iconw = 64;
1745     } else {
1746         iconx = self->client->icon_geometry.x;
1747         icony = self->client->icon_geometry.y;
1748         iconw = self->client->icon_geometry.width;
1749     }
1750
1751     iconifying = self->iconify_animation_going > 0;
1752
1753     /* how far do we have left to go ? */
1754     g_get_current_time(&now);
1755     time = frame_animate_iconify_time_left(self, &now);
1756
1757     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
1758         /* start where the frame is supposed to be */
1759         x = self->area.x;
1760         y = self->area.y;
1761         w = self->area.width;
1762         h = self->area.height;
1763     } else {
1764         /* start at the icon */
1765         x = iconx;
1766         y = icony;
1767         w = iconw;
1768         h = self->size.top; /* just the titlebar */
1769     }
1770
1771     if (time > 0) {
1772         glong dx, dy, dw;
1773         glong elapsed;
1774
1775         dx = self->area.x - iconx;
1776         dy = self->area.y - icony;
1777         dw = self->area.width - self->bwidth * 2 - iconw;
1778          /* if restoring, we move in the opposite direction */
1779         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1780
1781         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1782         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1783         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1784         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1785         h = self->size.top; /* just the titlebar */
1786     }
1787
1788     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
1789     XFlush(obt_display);
1790
1791     if (time == 0)
1792         frame_end_iconify_animation(self);
1793
1794     return time > 0; /* repeat until we're out of time */
1795 }
1796
1797 void frame_end_iconify_animation(ObFrame *self)
1798 {
1799     /* see if there is an animation going */
1800     if (self->iconify_animation_going == 0) return;
1801
1802     if (!self->visible)
1803         XUnmapWindow(obt_display, self->window);
1804     else {
1805         /* Send a ConfigureNotify when the animation is done, this fixes
1806            KDE's pager showing the window in the wrong place.  since the
1807            window is mapped at a different location and is then moved, we
1808            need to send the synthetic configurenotify, since apps may have
1809            read the position when the client mapped, apparently. */
1810         client_reconfigure(self->client, TRUE);
1811     }
1812
1813     /* we're not animating any more ! */
1814     self->iconify_animation_going = 0;
1815
1816     XMoveResizeWindow(obt_display, self->window,
1817                       self->area.x, self->area.y,
1818                       self->area.width, self->area.height);
1819     /* we delay re-rendering until after we're done animating */
1820     framerender_frame(self);
1821     XFlush(obt_display);
1822 }
1823
1824 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1825 {
1826     gulong time;
1827     gboolean new_anim = FALSE;
1828     gboolean set_end = TRUE;
1829     GTimeVal now;
1830
1831     /* if there is no titlebar, just don't animate for now
1832        XXX it would be nice tho.. */
1833     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1834         return;
1835
1836     /* get the current time */
1837     g_get_current_time(&now);
1838
1839     /* get how long until the end */
1840     time = FRAME_ANIMATE_ICONIFY_TIME;
1841     if (self->iconify_animation_going) {
1842         if (!!iconifying != (self->iconify_animation_going > 0)) {
1843             /* animation was already going on in the opposite direction */
1844             time = time - frame_animate_iconify_time_left(self, &now);
1845         } else
1846             /* animation was already going in the same direction */
1847             set_end = FALSE;
1848     } else
1849         new_anim = TRUE;
1850     self->iconify_animation_going = iconifying ? 1 : -1;
1851
1852     /* set the ending time */
1853     if (set_end) {
1854         self->iconify_animation_end.tv_sec = now.tv_sec;
1855         self->iconify_animation_end.tv_usec = now.tv_usec;
1856         g_time_val_add(&self->iconify_animation_end, time);
1857     }
1858
1859     if (new_anim) {
1860         if (self->iconify_animation_timer)
1861             g_source_remove(self->iconify_animation_timer);
1862         self->iconify_animation_timer =
1863             g_timeout_add_full(G_PRIORITY_DEFAULT,
1864                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1865                                frame_animate_iconify, self, NULL);
1866                                
1867
1868         /* do the first step */
1869         frame_animate_iconify(self);
1870
1871         /* show it during the animation even if it is not "visible" */
1872         if (!self->visible)
1873             XMapWindow(obt_display, self->window);
1874     }
1875 }