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