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