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