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