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