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