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