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