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