xflush after changing the focus decorations so it gets shown faster
[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 "render/theme.h"
31
32 #define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask | \
33                          FocusChangeMask)
34 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
35                          ButtonPressMask | ButtonReleaseMask | \
36                          VisibilityChangeMask)
37 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
38                            ButtonMotionMask | ExposureMask | \
39                            EnterWindowMask | LeaveWindowMask)
40
41 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
42                            f->cbwidth_y)
43
44 static void layout_title(ObFrame *self);
45 static void flash_done(gpointer data);
46 static gboolean flash_timeout(gpointer data);
47
48 static void set_theme_statics(ObFrame *self);
49 static void free_theme_statics(ObFrame *self);
50
51 static Window createWindow(Window parent, Visual *visual,
52                            gulong mask, XSetWindowAttributes *attrib)
53 {
54     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
55                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
56                          (visual ? visual : RrVisual(ob_rr_inst)),
57                          mask, attrib);
58                        
59 }
60
61 static Visual *check_32bit_client(ObClient *c)
62 {
63     XWindowAttributes wattrib;
64     Status ret;
65
66     ret = XGetWindowAttributes(ob_display, c->window, &wattrib);
67     g_assert(ret != BadDrawable);
68     g_assert(ret != BadWindow);
69
70     if (wattrib.depth == 32)
71         return wattrib.visual;
72     return NULL;
73 }
74
75 ObFrame *frame_new(ObClient *client)
76 {
77     XSetWindowAttributes attrib;
78     gulong mask;
79     ObFrame *self;
80     Visual *visual;
81
82     self = g_new0(ObFrame, 1);
83
84     self->obscured = TRUE;
85
86     visual = check_32bit_client(client);
87
88     /* create the non-visible decor windows */
89
90     mask = CWEventMask;
91     if (visual) {
92         /* client has a 32-bit visual */
93         mask |= CWColormap | CWBackPixel | CWBorderPixel;
94         /* create a colormap with the visual */
95         self->colormap = attrib.colormap =
96             XCreateColormap(ob_display,
97                             RootWindow(ob_display, ob_screen),
98                             visual, AllocNone);
99         attrib.background_pixel = BlackPixel(ob_display, 0);
100         attrib.border_pixel = BlackPixel(ob_display, 0);
101     }
102     attrib.event_mask = FRAME_EVENTMASK;
103     self->window = createWindow(RootWindow(ob_display, ob_screen), visual,
104                                 mask, &attrib);
105     mask &= ~CWEventMask;
106     self->plate = createWindow(self->window, visual, mask, &attrib);
107
108     /* create the visible decor windows */
109
110     mask = CWEventMask;
111     if (visual) {
112         /* client has a 32-bit visual */
113         mask |= CWColormap | CWBackPixel | CWBorderPixel;
114         attrib.colormap = RrColormap(ob_rr_inst);
115     }
116     attrib.event_mask = ELEMENT_EVENTMASK;
117     self->title = createWindow(self->window, NULL, mask, &attrib);
118
119     mask |= CWCursor;
120     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
121     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
122     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
123     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
124     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
125     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
126
127     mask &= ~CWCursor;
128     self->label = createWindow(self->title, NULL, mask, &attrib);
129     self->max = createWindow(self->title, NULL, mask, &attrib);
130     self->close = createWindow(self->title, NULL, mask, &attrib);
131     self->desk = createWindow(self->title, NULL, mask, &attrib);
132     self->shade = createWindow(self->title, NULL, mask, &attrib);
133     self->icon = createWindow(self->title, NULL, mask, &attrib);
134     self->iconify = createWindow(self->title, NULL, mask, &attrib);
135     self->handle = createWindow(self->window, NULL, mask, &attrib);
136
137     mask |= CWCursor;
138     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
139     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
140     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
141     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
142
143     self->focused = FALSE;
144
145     /* the other stuff is shown based on decor settings */
146     XMapWindow(ob_display, self->plate);
147     XMapWindow(ob_display, self->lgrip);
148     XMapWindow(ob_display, self->rgrip);
149     XMapWindow(ob_display, self->label);
150
151     self->max_press = self->close_press = self->desk_press = 
152         self->iconify_press = self->shade_press = FALSE;
153     self->max_hover = self->close_hover = self->desk_hover = 
154         self->iconify_hover = self->shade_hover = FALSE;
155
156     set_theme_statics(self);
157
158     return (ObFrame*)self;
159 }
160
161 static void set_theme_statics(ObFrame *self)
162 {
163     /* set colors/appearance/sizes for stuff that doesn't change */
164     XSetWindowBorder(ob_display, self->window,
165                      RrColorPixel(ob_rr_theme->frame_b_color));
166     XSetWindowBorder(ob_display, self->title,
167                      RrColorPixel(ob_rr_theme->frame_b_color));
168     XSetWindowBorder(ob_display, self->handle,
169                      RrColorPixel(ob_rr_theme->frame_b_color));
170     XSetWindowBorder(ob_display, self->rgrip,
171                      RrColorPixel(ob_rr_theme->frame_b_color));
172     XSetWindowBorder(ob_display, self->lgrip,
173                      RrColorPixel(ob_rr_theme->frame_b_color));
174
175     XResizeWindow(ob_display, self->max,
176                   ob_rr_theme->button_size, ob_rr_theme->button_size);
177     XResizeWindow(ob_display, self->iconify,
178                   ob_rr_theme->button_size, ob_rr_theme->button_size);
179     XResizeWindow(ob_display, self->icon,
180                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
181     XResizeWindow(ob_display, self->close,
182                   ob_rr_theme->button_size, ob_rr_theme->button_size);
183     XResizeWindow(ob_display, self->desk,
184                   ob_rr_theme->button_size, ob_rr_theme->button_size);
185     XResizeWindow(ob_display, self->shade,
186                   ob_rr_theme->button_size, ob_rr_theme->button_size);
187     if (ob_rr_theme->handle_height > 0) {
188         XResizeWindow(ob_display, self->lgrip,
189                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
190         XResizeWindow(ob_display, self->rgrip,
191                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
192     }
193     XResizeWindow(ob_display, self->tltresize,
194                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
195     XResizeWindow(ob_display, self->trtresize,
196                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
197     XResizeWindow(ob_display, self->tllresize,
198                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
199     XResizeWindow(ob_display, self->trrresize,
200                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
201
202     /* set up the dynamic appearances */
203     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
204     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
205     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
206     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
207     self->a_unfocused_handle =
208         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
209     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
210     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
211 }
212
213 static void free_theme_statics(ObFrame *self)
214 {
215     RrAppearanceFree(self->a_unfocused_title); 
216     RrAppearanceFree(self->a_focused_title);
217     RrAppearanceFree(self->a_unfocused_label);
218     RrAppearanceFree(self->a_focused_label);
219     RrAppearanceFree(self->a_unfocused_handle);
220     RrAppearanceFree(self->a_focused_handle);
221     RrAppearanceFree(self->a_icon);
222 }
223
224 static void frame_free(ObFrame *self)
225 {
226     free_theme_statics(self);
227
228     XDestroyWindow(ob_display, self->window);
229     if (self->colormap)
230         XFreeColormap(ob_display, self->colormap);
231
232     g_free(self);
233 }
234
235 void frame_show(ObFrame *self)
236 {
237     if (!self->visible) {
238         self->visible = TRUE;
239         XMapWindow(ob_display, self->client->window);
240         XMapWindow(ob_display, self->window);
241     }
242 }
243
244 void frame_hide(ObFrame *self)
245 {
246     if (self->visible) {
247         self->visible = FALSE;
248         self->client->ignore_unmaps += 1;
249         /* we unmap the client itself so that we can get MapRequest
250            events, and because the ICCCM tells us to! */
251         XUnmapWindow(ob_display, self->window);
252         XUnmapWindow(ob_display, self->client->window);
253     }
254 }
255
256 void frame_adjust_theme(ObFrame *self)
257 {
258     free_theme_statics(self);
259     set_theme_statics(self);
260 }
261
262 void frame_adjust_shape(ObFrame *self)
263 {
264 #ifdef SHAPE
265     gint num;
266     XRectangle xrect[2];
267
268     if (!self->client->shaped) {
269         /* clear the shape on the frame window */
270         XShapeCombineMask(ob_display, self->window, ShapeBounding,
271                           self->innersize.left,
272                           self->innersize.top,
273                           None, ShapeSet);
274     } else {
275         /* make the frame's shape match the clients */
276         XShapeCombineShape(ob_display, self->window, ShapeBounding,
277                            self->innersize.left,
278                            self->innersize.top,
279                            self->client->window,
280                            ShapeBounding, ShapeSet);
281
282         num = 0;
283         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
284             xrect[0].x = -ob_rr_theme->fbwidth;
285             xrect[0].y = -ob_rr_theme->fbwidth;
286             xrect[0].width = self->width + self->rbwidth * 2;
287             xrect[0].height = ob_rr_theme->title_height +
288                 self->bwidth * 2;
289             ++num;
290         }
291
292         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
293             xrect[1].x = -ob_rr_theme->fbwidth;
294             xrect[1].y = FRAME_HANDLE_Y(self);
295             xrect[1].width = self->width + self->rbwidth * 2;
296             xrect[1].height = ob_rr_theme->handle_height +
297                 self->bwidth * 2;
298             ++num;
299         }
300
301         XShapeCombineRectangles(ob_display, self->window,
302                                 ShapeBounding, 0, 0, xrect, num,
303                                 ShapeUnion, Unsorted);
304     }
305 #endif
306 }
307
308 void frame_adjust_area(ObFrame *self, gboolean moved,
309                        gboolean resized, gboolean fake)
310 {
311     Strut oldsize;
312
313     oldsize = self->size;
314
315     if (resized) {
316         self->decorations = self->client->decorations;
317         self->max_horz = self->client->max_horz;
318
319         if (self->decorations & OB_FRAME_DECOR_BORDER) {
320             self->bwidth = ob_rr_theme->fbwidth;
321             self->cbwidth_x = ob_rr_theme->cbwidthx;
322             self->cbwidth_y = ob_rr_theme->cbwidthy;
323         } else {
324             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
325         }
326         self->rbwidth = self->bwidth;
327
328         if (self->max_horz)
329             self->bwidth = self->cbwidth_x = 0;
330
331         STRUT_SET(self->innersize,
332                   self->cbwidth_x,
333                   self->cbwidth_y,
334                   self->cbwidth_x,
335                   self->cbwidth_y);
336         self->width = self->client->area.width + self->cbwidth_x * 2 -
337             (self->max_horz ? self->rbwidth * 2 : 0);
338         self->width = MAX(self->width, 1); /* no lower than 1 */
339
340         /* set border widths */
341         if (!fake) {
342             XSetWindowBorderWidth(ob_display, self->window, self->bwidth);
343             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
344             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
345             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
346             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
347         }
348
349         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
350             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
351                 (self->rbwidth - self->bwidth);
352         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
353             ob_rr_theme->handle_height > 0)
354             self->innersize.bottom += ob_rr_theme->handle_height +
355                 self->rbwidth + (self->rbwidth - self->bwidth);
356   
357         /* they all default off, they're turned on in layout_title */
358         self->icon_x = -1;
359         self->desk_x = -1;
360         self->shade_x = -1;
361         self->iconify_x = -1;
362         self->label_x = -1;
363         self->max_x = -1;
364         self->close_x = -1;
365
366         /* position/size and map/unmap all the windows */
367
368         if (!fake) {
369             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
370                 XMoveResizeWindow(ob_display, self->title,
371                                   -self->bwidth, -self->bwidth,
372                                   self->width, ob_rr_theme->title_height);
373                 XMapWindow(ob_display, self->title);
374
375                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
376                     XMoveWindow(ob_display, self->tltresize, 0, 0);
377                     XMoveWindow(ob_display, self->tllresize, 0, 0);
378                     XMoveWindow(ob_display, self->trtresize,
379                                 self->width - ob_rr_theme->grip_width, 0);
380                     XMoveWindow(ob_display, self->trrresize,
381                                 self->width - ob_rr_theme->paddingx - 1, 0);
382                     XMapWindow(ob_display, self->tltresize);
383                     XMapWindow(ob_display, self->tllresize);
384                     XMapWindow(ob_display, self->trtresize);
385                     XMapWindow(ob_display, self->trrresize);
386                 } else {
387                     XUnmapWindow(ob_display, self->tltresize);
388                     XUnmapWindow(ob_display, self->tllresize);
389                     XUnmapWindow(ob_display, self->trtresize);
390                     XUnmapWindow(ob_display, self->trrresize);
391                 }
392             } else
393                 XUnmapWindow(ob_display, self->title);
394         }
395
396         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
397             /* layout the title bar elements */
398             layout_title(self);
399
400         if (!fake) {
401             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
402                 ob_rr_theme->handle_height > 0)
403             {
404                 XMoveResizeWindow(ob_display, self->handle,
405                                   -self->bwidth, FRAME_HANDLE_Y(self),
406                                   self->width, ob_rr_theme->handle_height);
407                 XMapWindow(ob_display, self->handle);
408
409                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
410                     XMoveWindow(ob_display, self->lgrip,
411                                 -self->rbwidth, -self->rbwidth);
412                     XMoveWindow(ob_display, self->rgrip,
413                                 -self->rbwidth + self->width -
414                                 ob_rr_theme->grip_width, -self->rbwidth);
415                     XMapWindow(ob_display, self->lgrip);
416                     XMapWindow(ob_display, self->rgrip);
417                 } else {
418                     XUnmapWindow(ob_display, self->lgrip);
419                     XUnmapWindow(ob_display, self->rgrip);
420                 }
421             } else
422                 XUnmapWindow(ob_display, self->handle);
423
424             /* move and resize the plate */
425             XMoveResizeWindow(ob_display, self->plate,
426                               self->innersize.left - self->cbwidth_x,
427                               self->innersize.top - self->cbwidth_y,
428                               self->client->area.width + self->cbwidth_x * 2,
429                               self->client->area.height + self->cbwidth_y * 2);
430             /* when the client has StaticGravity, it likes to move around. */
431             XMoveWindow(ob_display, self->client->window,
432                         self->cbwidth_x, self->cbwidth_y);
433         }
434
435         STRUT_SET(self->size,
436                   self->innersize.left + self->bwidth,
437                   self->innersize.top + self->bwidth,
438                   self->innersize.right + self->bwidth,
439                   self->innersize.bottom + self->bwidth);
440     }
441
442     /* shading can change without being moved or resized */
443     RECT_SET_SIZE(self->area,
444                   self->client->area.width +
445                   self->size.left + self->size.right,
446                   (self->client->shaded ?
447                    ob_rr_theme->title_height + self->rbwidth * 2:
448                    self->client->area.height +
449                    self->size.top + self->size.bottom));
450
451     if (moved) {
452         /* find the new coordinates, done after setting the frame.size, for
453            frame_client_gravity. */
454         self->area.x = self->client->area.x;
455         self->area.y = self->client->area.y;
456         frame_client_gravity(self, &self->area.x, &self->area.y);
457     }
458
459     if (!fake) {
460         /* move and resize the top level frame.
461            shading can change without being moved or resized */
462         XMoveResizeWindow(ob_display, self->window,
463                           self->area.x, self->area.y,
464                           self->area.width - self->bwidth * 2,
465                           self->area.height - self->bwidth * 2);
466
467         if (resized) {
468             framerender_frame(self);
469             frame_adjust_shape(self);
470         }
471
472         if (!STRUT_EQUAL(self->size, oldsize)) {
473             gulong vals[4];
474             vals[0] = self->size.left;
475             vals[1] = self->size.right;
476             vals[2] = self->size.top;
477             vals[3] = self->size.bottom;
478             PROP_SETA32(self->client->window, net_frame_extents,
479                         cardinal, vals, 4);
480         }
481
482         /* if this occurs while we are focus cycling, the indicator needs to
483            match the changes */
484         if (focus_cycle_target == self->client)
485             focus_cycle_draw_indicator();
486     }
487     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
488         XResizeWindow(ob_display, self->label, self->label_width,
489                       ob_rr_theme->label_height);
490 }
491
492 void frame_adjust_state(ObFrame *self)
493 {
494     framerender_frame(self);
495 }
496
497 void frame_adjust_focus(ObFrame *self, gboolean hilite)
498 {
499     self->focused = hilite;
500     framerender_frame(self);
501     XFlush(ob_display);
502 }
503
504 void frame_adjust_title(ObFrame *self)
505 {
506     framerender_frame(self);
507 }
508
509 void frame_adjust_icon(ObFrame *self)
510 {
511     framerender_frame(self);
512 }
513
514 void frame_grab_client(ObFrame *self, ObClient *client)
515 {
516     self->client = client;
517
518     /* reparent the client to the frame */
519     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
520     /*
521       When reparenting the client window, it is usually not mapped yet, since
522       this occurs from a MapRequest. However, in the case where Openbox is
523       starting up, the window is already mapped, so we'll see unmap events for
524       it. There are 2 unmap events generated that we see, one with the 'event'
525       member set the root window, and one set to the client, but both get
526       handled and need to be ignored.
527     */
528     if (ob_state() == OB_STATE_STARTING)
529         client->ignore_unmaps += 2;
530
531     /* select the event mask on the client's parent (to receive config/map
532        req's) the ButtonPress is to catch clicks on the client border */
533     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
534
535     frame_adjust_area(self, TRUE, TRUE, FALSE);
536
537     /* map the client so it maps when the frame does */
538     XMapWindow(ob_display, client->window);
539
540     /* set all the windows for the frame in the window_map */
541     g_hash_table_insert(window_map, &self->window, client);
542     g_hash_table_insert(window_map, &self->plate, client);
543     g_hash_table_insert(window_map, &self->title, client);
544     g_hash_table_insert(window_map, &self->label, client);
545     g_hash_table_insert(window_map, &self->max, client);
546     g_hash_table_insert(window_map, &self->close, client);
547     g_hash_table_insert(window_map, &self->desk, client);
548     g_hash_table_insert(window_map, &self->shade, client);
549     g_hash_table_insert(window_map, &self->icon, client);
550     g_hash_table_insert(window_map, &self->iconify, client);
551     g_hash_table_insert(window_map, &self->handle, client);
552     g_hash_table_insert(window_map, &self->lgrip, client);
553     g_hash_table_insert(window_map, &self->rgrip, client);
554     g_hash_table_insert(window_map, &self->tltresize, client);
555     g_hash_table_insert(window_map, &self->tllresize, client);
556     g_hash_table_insert(window_map, &self->trtresize, client);
557     g_hash_table_insert(window_map, &self->trrresize, client);
558 }
559
560 void frame_release_client(ObFrame *self, ObClient *client)
561 {
562     XEvent ev;
563     gboolean reparent = TRUE;
564
565     g_assert(self->client == client);
566
567     /* check if the app has already reparented its window away */
568     while (XCheckTypedWindowEvent(ob_display, client->window,
569                                   ReparentNotify, &ev))
570     {
571         /* This check makes sure we don't catch our own reparent action to
572            our frame window. This doesn't count as the app reparenting itself
573            away of course.
574
575            Reparent events that are generated by us are just discarded here.
576            They are of no consequence to us anyhow.
577         */
578         if (ev.xreparent.parent != self->plate) {
579             reparent = FALSE;
580             XPutBackEvent(ob_display, &ev);
581             break;
582         }
583     }
584
585     if (reparent) {
586         /* according to the ICCCM - if the client doesn't reparent itself,
587            then we will reparent the window to root for them */
588         XReparentWindow(ob_display, client->window,
589                         RootWindow(ob_display, ob_screen),
590                         client->area.x,
591                         client->area.y);
592     }
593
594     /* remove all the windows for the frame from the window_map */
595     g_hash_table_remove(window_map, &self->window);
596     g_hash_table_remove(window_map, &self->plate);
597     g_hash_table_remove(window_map, &self->title);
598     g_hash_table_remove(window_map, &self->label);
599     g_hash_table_remove(window_map, &self->max);
600     g_hash_table_remove(window_map, &self->close);
601     g_hash_table_remove(window_map, &self->desk);
602     g_hash_table_remove(window_map, &self->shade);
603     g_hash_table_remove(window_map, &self->icon);
604     g_hash_table_remove(window_map, &self->iconify);
605     g_hash_table_remove(window_map, &self->handle);
606     g_hash_table_remove(window_map, &self->lgrip);
607     g_hash_table_remove(window_map, &self->rgrip);
608     g_hash_table_remove(window_map, &self->tltresize);
609     g_hash_table_remove(window_map, &self->tllresize);
610     g_hash_table_remove(window_map, &self->trtresize);
611     g_hash_table_remove(window_map, &self->trrresize);
612
613     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
614
615     frame_free(self);
616 }
617
618 static void layout_title(ObFrame *self)
619 {
620     gchar *lc;
621     gint x;
622     gboolean n, d, i, l, m, c, s;
623
624     n = d = i = l = m = c = s = FALSE;
625
626     /* figure out whats being shown, and the width of the label */
627     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
628     for (lc = config_title_layout; *lc != '\0'; ++lc) {
629         switch (*lc) {
630         case 'N':
631             if (n) { *lc = ' '; break; } /* rm duplicates */
632             n = TRUE;
633             self->label_width -= (ob_rr_theme->button_size + 2 +
634                                   ob_rr_theme->paddingx + 1);
635             break;
636         case 'D':
637             if (d) { *lc = ' '; break; }
638             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
639                 && config_theme_hidedisabled)
640                 break;
641             d = TRUE;
642             self->label_width -= (ob_rr_theme->button_size +
643                                   ob_rr_theme->paddingx + 1);
644             break;
645         case 'S':
646             if (s) { *lc = ' '; break; }
647             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
648                 && config_theme_hidedisabled)
649                 break;
650             s = TRUE;
651             self->label_width -= (ob_rr_theme->button_size +
652                                   ob_rr_theme->paddingx + 1);
653             break;
654         case 'I':
655             if (i) { *lc = ' '; break; }
656             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
657                 && config_theme_hidedisabled)
658                 break;
659             i = TRUE;
660             self->label_width -= (ob_rr_theme->button_size +
661                                   ob_rr_theme->paddingx + 1);
662             break;
663         case 'L':
664             if (l) { *lc = ' '; break; }
665             l = TRUE;
666             break;
667         case 'M':
668             if (m) { *lc = ' '; break; }
669             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
670                 && config_theme_hidedisabled)
671                 break;
672             m = TRUE;
673             self->label_width -= (ob_rr_theme->button_size +
674                                   ob_rr_theme->paddingx + 1);
675             break;
676         case 'C':
677             if (c) { *lc = ' '; break; }
678             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
679                 && config_theme_hidedisabled)
680                 break;
681             c = TRUE;
682             self->label_width -= (ob_rr_theme->button_size +
683                                   ob_rr_theme->paddingx + 1);
684             break;
685         }
686     }
687     if (self->label_width < 1) self->label_width = 1;
688
689     if (!n) XUnmapWindow(ob_display, self->icon);
690     if (!d) XUnmapWindow(ob_display, self->desk);
691     if (!s) XUnmapWindow(ob_display, self->shade);
692     if (!i) XUnmapWindow(ob_display, self->iconify);
693     if (!l) XUnmapWindow(ob_display, self->label);
694     if (!m) XUnmapWindow(ob_display, self->max);
695     if (!c) XUnmapWindow(ob_display, self->close);
696
697     x = ob_rr_theme->paddingx + 1;
698     for (lc = config_title_layout; *lc != '\0'; ++lc) {
699         switch (*lc) {
700         case 'N':
701             if (!n) break;
702             self->icon_x = x;
703             XMapWindow(ob_display, self->icon);
704             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->paddingy);
705             x += ob_rr_theme->button_size + 2 + ob_rr_theme->paddingx + 1;
706             break;
707         case 'D':
708             if (!d) break;
709             self->desk_x = x;
710             XMapWindow(ob_display, self->desk);
711             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->paddingy + 1);
712             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
713             break;
714         case 'S':
715             if (!s) break;
716             self->shade_x = x;
717             XMapWindow(ob_display, self->shade);
718             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->paddingy + 1);
719             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
720             break;
721         case 'I':
722             if (!i) break;
723             self->iconify_x = x;
724             XMapWindow(ob_display, self->iconify);
725             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->paddingy + 1);
726             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
727             break;
728         case 'L':
729             if (!l) break;
730             self->label_x = x;
731             XMapWindow(ob_display, self->label);
732             XMoveWindow(ob_display, self->label, x, ob_rr_theme->paddingy);
733             x += self->label_width + ob_rr_theme->paddingx + 1;
734             break;
735         case 'M':
736             if (!m) break;
737             self->max_x = x;
738             XMapWindow(ob_display, self->max);
739             XMoveWindow(ob_display, self->max, x, ob_rr_theme->paddingy + 1);
740             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
741             break;
742         case 'C':
743             if (!c) break;
744             self->close_x = x;
745             XMapWindow(ob_display, self->close);
746             XMoveWindow(ob_display, self->close, x, ob_rr_theme->paddingy + 1);
747             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
748             break;
749         }
750     }
751 }
752
753 ObFrameContext frame_context_from_string(const gchar *name)
754 {
755     if (!g_ascii_strcasecmp("Desktop", name))
756         return OB_FRAME_CONTEXT_DESKTOP;
757     else if (!g_ascii_strcasecmp("Client", name))
758         return OB_FRAME_CONTEXT_CLIENT;
759     else if (!g_ascii_strcasecmp("Titlebar", name))
760         return OB_FRAME_CONTEXT_TITLEBAR;
761     else if (!g_ascii_strcasecmp("Handle", name))
762         return OB_FRAME_CONTEXT_HANDLE;
763     else if (!g_ascii_strcasecmp("Frame", name))
764         return OB_FRAME_CONTEXT_FRAME;
765     else if (!g_ascii_strcasecmp("TLCorner", name))
766         return OB_FRAME_CONTEXT_TLCORNER;
767     else if (!g_ascii_strcasecmp("TRCorner", name))
768         return OB_FRAME_CONTEXT_TRCORNER;
769     else if (!g_ascii_strcasecmp("BLCorner", name))
770         return OB_FRAME_CONTEXT_BLCORNER;
771     else if (!g_ascii_strcasecmp("BRCorner", name))
772         return OB_FRAME_CONTEXT_BRCORNER;
773     else if (!g_ascii_strcasecmp("Maximize", name))
774         return OB_FRAME_CONTEXT_MAXIMIZE;
775     else if (!g_ascii_strcasecmp("AllDesktops", name))
776         return OB_FRAME_CONTEXT_ALLDESKTOPS;
777     else if (!g_ascii_strcasecmp("Shade", name))
778         return OB_FRAME_CONTEXT_SHADE;
779     else if (!g_ascii_strcasecmp("Iconify", name))
780         return OB_FRAME_CONTEXT_ICONIFY;
781     else if (!g_ascii_strcasecmp("Icon", name))
782         return OB_FRAME_CONTEXT_ICON;
783     else if (!g_ascii_strcasecmp("Close", name))
784         return OB_FRAME_CONTEXT_CLOSE;
785     else if (!g_ascii_strcasecmp("MoveResize", name))
786         return OB_FRAME_CONTEXT_MOVE_RESIZE;
787     return OB_FRAME_CONTEXT_NONE;
788 }
789
790 ObFrameContext frame_context(ObClient *client, Window win)
791 {
792     ObFrame *self;
793
794     if (moveresize_in_progress)
795         return OB_FRAME_CONTEXT_MOVE_RESIZE;
796
797     if (win == RootWindow(ob_display, ob_screen))
798         return OB_FRAME_CONTEXT_DESKTOP;
799     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
800     if (win == client->window) {
801         /* conceptually, this is the desktop, as far as users are
802            concerned */
803         if (client->type == OB_CLIENT_TYPE_DESKTOP)
804             return OB_FRAME_CONTEXT_DESKTOP;
805         return OB_FRAME_CONTEXT_CLIENT;
806     }
807
808     self = client->frame;
809     if (win == self->plate) {
810         /* conceptually, this is the desktop, as far as users are
811            concerned */
812         if (client->type == OB_CLIENT_TYPE_DESKTOP)
813             return OB_FRAME_CONTEXT_DESKTOP;
814         return OB_FRAME_CONTEXT_CLIENT;
815     }
816
817     if (win == self->window)   return OB_FRAME_CONTEXT_FRAME;
818     if (win == self->title)    return OB_FRAME_CONTEXT_TITLEBAR;
819     if (win == self->label)    return OB_FRAME_CONTEXT_TITLEBAR;
820     if (win == self->handle)   return OB_FRAME_CONTEXT_HANDLE;
821     if (win == self->lgrip)    return OB_FRAME_CONTEXT_BLCORNER;
822     if (win == self->rgrip)    return OB_FRAME_CONTEXT_BRCORNER;
823     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
824     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
825     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
826     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
827     if (win == self->max)      return OB_FRAME_CONTEXT_MAXIMIZE;
828     if (win == self->iconify)  return OB_FRAME_CONTEXT_ICONIFY;
829     if (win == self->close)    return OB_FRAME_CONTEXT_CLOSE;
830     if (win == self->icon)     return OB_FRAME_CONTEXT_ICON;
831     if (win == self->desk)     return OB_FRAME_CONTEXT_ALLDESKTOPS;
832     if (win == self->shade)    return OB_FRAME_CONTEXT_SHADE;
833
834     return OB_FRAME_CONTEXT_NONE;
835 }
836
837 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
838 {
839     /* horizontal */
840     switch (self->client->gravity) {
841     default:
842     case NorthWestGravity:
843     case SouthWestGravity:
844     case WestGravity:
845         break;
846
847     case NorthGravity:
848     case SouthGravity:
849     case CenterGravity:
850         *x -= (self->size.left + self->size.right) / 2;
851         break;
852
853     case NorthEastGravity:
854     case SouthEastGravity:
855     case EastGravity:
856         *x -= self->size.left + self->size.right;
857         break;
858
859     case ForgetGravity:
860     case StaticGravity:
861         *x -= self->size.left;
862         break;
863     }
864
865     /* vertical */
866     switch (self->client->gravity) {
867     default:
868     case NorthWestGravity:
869     case NorthEastGravity:
870     case NorthGravity:
871         break;
872
873     case CenterGravity:
874     case EastGravity:
875     case WestGravity:
876         *y -= (self->size.top + self->size.bottom) / 2;
877         break;
878
879     case SouthWestGravity:
880     case SouthEastGravity:
881     case SouthGravity:
882         *y -= self->size.top + self->size.bottom;
883         break;
884
885     case ForgetGravity:
886     case StaticGravity:
887         *y -= self->size.top;
888         break;
889     }
890 }
891
892 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
893 {
894     /* horizontal */
895     switch (self->client->gravity) {
896     default:
897     case NorthWestGravity:
898     case WestGravity:
899     case SouthWestGravity:
900         break;
901     case NorthGravity:
902     case CenterGravity:
903     case SouthGravity:
904         *x += (self->size.left + self->size.right) / 2;
905         break;
906     case NorthEastGravity:
907     case EastGravity:
908     case SouthEastGravity:
909         *x += self->size.left + self->size.right;
910         break;
911     case StaticGravity:
912     case ForgetGravity:
913         *x += self->size.left;
914         break;
915     }
916
917     /* vertical */
918     switch (self->client->gravity) {
919     default:
920     case NorthWestGravity:
921     case NorthGravity:
922     case NorthEastGravity:
923         break;
924     case WestGravity:
925     case CenterGravity:
926     case EastGravity:
927         *y += (self->size.top + self->size.bottom) / 2;
928         break;
929     case SouthWestGravity:
930     case SouthGravity:
931     case SouthEastGravity:
932         *y += self->size.top + self->size.bottom;
933         break;
934     case StaticGravity:
935     case ForgetGravity:
936         *y += self->size.top;
937         break;
938     }
939 }
940
941 static void flash_done(gpointer data)
942 {
943     ObFrame *self = data;
944
945     if (self->focused != self->flash_on)
946         frame_adjust_focus(self, self->focused);
947 }
948
949 static gboolean flash_timeout(gpointer data)
950 {
951     ObFrame *self = data;
952     GTimeVal now;
953
954     g_get_current_time(&now);
955     if (now.tv_sec > self->flash_end.tv_sec ||
956         (now.tv_sec == self->flash_end.tv_sec &&
957          now.tv_usec >= self->flash_end.tv_usec))
958         self->flashing = FALSE;
959
960     if (!self->flashing)
961         return FALSE; /* we are done */
962
963     self->flash_on = !self->flash_on;
964     if (!self->focused) {
965         frame_adjust_focus(self, self->flash_on);
966         self->focused = FALSE;
967     }
968
969     return TRUE; /* go again */
970 }
971
972 void frame_flash_start(ObFrame *self)
973 {
974     self->flash_on = self->focused;
975
976     if (!self->flashing)
977         ob_main_loop_timeout_add(ob_main_loop,
978                                  G_USEC_PER_SEC * 0.6,
979                                  flash_timeout,
980                                  self,
981                                  g_direct_equal,
982                                  flash_done);
983     g_get_current_time(&self->flash_end);
984     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
985     
986     self->flashing = TRUE;
987 }
988
989 void frame_flash_stop(ObFrame *self)
990 {
991     self->flashing = FALSE;
992 }