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