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