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