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