the inner stuff was being placed wrong all this time.
[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-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->inner,
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->inner, self->bwidth);
350             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
351             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
352             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
353             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
354         }
355
356         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
357             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
358                 (self->rbwidth - self->bwidth);
359         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
360             ob_rr_theme->handle_height > 0)
361             self->innersize.bottom += ob_rr_theme->handle_height +
362                 self->rbwidth + (self->rbwidth - self->bwidth);
363   
364         /* they all default off, they're turned on in layout_title */
365         self->icon_x = -1;
366         self->desk_x = -1;
367         self->shade_x = -1;
368         self->iconify_x = -1;
369         self->label_x = -1;
370         self->max_x = -1;
371         self->close_x = -1;
372
373         /* position/size and map/unmap all the windows */
374
375         if (!fake) {
376             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
377                 XMoveResizeWindow(ob_display, self->title,
378                                   -self->bwidth, -self->bwidth,
379                                   self->width, ob_rr_theme->title_height);
380                 XMapWindow(ob_display, self->title);
381
382                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
383                     XMoveWindow(ob_display, self->tltresize, 0, 0);
384                     XMoveWindow(ob_display, self->tllresize, 0, 0);
385                     XMoveWindow(ob_display, self->trtresize,
386                                 self->width - ob_rr_theme->grip_width, 0);
387                     XMoveWindow(ob_display, self->trrresize,
388                                 self->width - ob_rr_theme->paddingx - 1, 0);
389                     XMapWindow(ob_display, self->tltresize);
390                     XMapWindow(ob_display, self->tllresize);
391                     XMapWindow(ob_display, self->trtresize);
392                     XMapWindow(ob_display, self->trrresize);
393                 } else {
394                     XUnmapWindow(ob_display, self->tltresize);
395                     XUnmapWindow(ob_display, self->tllresize);
396                     XUnmapWindow(ob_display, self->trtresize);
397                     XUnmapWindow(ob_display, self->trrresize);
398                 }
399             } else
400                 XUnmapWindow(ob_display, self->title);
401         }
402
403         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
404             /* layout the title bar elements */
405             layout_title(self);
406
407         if (!fake) {
408             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
409                 ob_rr_theme->handle_height > 0)
410             {
411                 XMoveResizeWindow(ob_display, self->handle,
412                                   -self->bwidth, FRAME_HANDLE_Y(self),
413                                   self->width, ob_rr_theme->handle_height);
414                 XMapWindow(ob_display, self->handle);
415
416                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
417                     XMoveWindow(ob_display, self->lgrip,
418                                 -self->rbwidth, -self->rbwidth);
419                     XMoveWindow(ob_display, self->rgrip,
420                                 -self->rbwidth + self->width -
421                                 ob_rr_theme->grip_width, -self->rbwidth);
422                     XMapWindow(ob_display, self->lgrip);
423                     XMapWindow(ob_display, self->rgrip);
424                 } else {
425                     XUnmapWindow(ob_display, self->lgrip);
426                     XUnmapWindow(ob_display, self->rgrip);
427                 }
428             } else
429                 XUnmapWindow(ob_display, self->handle);
430
431             /* move and resize the inner border window which contains the plate
432              */
433             XMoveResizeWindow(ob_display, self->inner,
434                               self->innersize.left - self->cbwidth_x -
435                               self->bwidth,
436                               self->innersize.top - self->cbwidth_y -
437                               self->bwidth,
438                               self->client->area.width +
439                               self->cbwidth_x * 2,
440                               self->client->area.height +
441                               self->cbwidth_y * 2);
442
443             /* move the plate */
444             XMoveWindow(ob_display, self->plate,
445                         self->cbwidth_x, self->cbwidth_y);
446
447             /* when the client has StaticGravity, it likes to move around. */
448             XMoveWindow(ob_display, self->client->window, 0, 0);
449         }
450
451         STRUT_SET(self->size,
452                   self->innersize.left + self->bwidth,
453                   self->innersize.top + self->bwidth,
454                   self->innersize.right + self->bwidth,
455                   self->innersize.bottom + self->bwidth);
456     }
457
458     /* shading can change without being moved or resized */
459     RECT_SET_SIZE(self->area,
460                   self->client->area.width +
461                   self->size.left + self->size.right,
462                   (self->client->shaded ?
463                    ob_rr_theme->title_height + self->rbwidth * 2:
464                    self->client->area.height +
465                    self->size.top + self->size.bottom));
466
467     if (moved) {
468         /* find the new coordinates, done after setting the frame.size, for
469            frame_client_gravity. */
470         self->area.x = self->client->area.x;
471         self->area.y = self->client->area.y;
472         frame_client_gravity(self, &self->area.x, &self->area.y);
473     }
474
475     if (!fake) {
476         /* move and resize the top level frame.
477            shading can change without being moved or resized */
478         XMoveResizeWindow(ob_display, self->window,
479                           self->area.x, self->area.y,
480                           self->area.width - self->bwidth * 2,
481                           self->area.height - self->bwidth * 2);
482
483         if (resized) {
484             framerender_frame(self);
485             frame_adjust_shape(self);
486         }
487
488         if (!STRUT_EQUAL(self->size, oldsize)) {
489             gulong vals[4];
490             vals[0] = self->size.left;
491             vals[1] = self->size.right;
492             vals[2] = self->size.top;
493             vals[3] = self->size.bottom;
494             PROP_SETA32(self->client->window, net_frame_extents,
495                         cardinal, vals, 4);
496         }
497
498         /* if this occurs while we are focus cycling, the indicator needs to
499            match the changes */
500         if (focus_cycle_target == self->client)
501             focus_cycle_draw_indicator();
502     }
503     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
504         XResizeWindow(ob_display, self->label, self->label_width,
505                       ob_rr_theme->label_height);
506 }
507
508 void frame_adjust_state(ObFrame *self)
509 {
510     framerender_frame(self);
511 }
512
513 void frame_adjust_focus(ObFrame *self, gboolean hilite)
514 {
515     self->focused = hilite;
516     framerender_frame(self);
517     XFlush(ob_display);
518 }
519
520 void frame_adjust_client_area(ObFrame *self)
521 {
522     /* resize the plate */
523     XResizeWindow(ob_display, self->plate,
524                   self->client->area.width, self->client->area.height);
525 }
526
527 void frame_adjust_title(ObFrame *self)
528 {
529     framerender_frame(self);
530 }
531
532 void frame_adjust_icon(ObFrame *self)
533 {
534     framerender_frame(self);
535 }
536
537 void frame_grab_client(ObFrame *self, ObClient *client)
538 {
539     self->client = client;
540
541     /* reparent the client to the frame */
542     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
543     /*
544       When reparenting the client window, it is usually not mapped yet, since
545       this occurs from a MapRequest. However, in the case where Openbox is
546       starting up, the window is already mapped, so we'll see unmap events for
547       it. There are 2 unmap events generated that we see, one with the 'event'
548       member set the root window, and one set to the client, but both get
549       handled and need to be ignored.
550     */
551     if (ob_state() == OB_STATE_STARTING)
552         client->ignore_unmaps += 2;
553
554     /* select the event mask on the client's parent (to receive config/map
555        req's) the ButtonPress is to catch clicks on the client border */
556     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
557
558     frame_adjust_area(self, TRUE, TRUE, FALSE);
559
560     /* map the client so it maps when the frame does */
561     XMapWindow(ob_display, client->window);
562
563     /* set all the windows for the frame in the window_map */
564     g_hash_table_insert(window_map, &self->window, client);
565     g_hash_table_insert(window_map, &self->plate, client);
566     g_hash_table_insert(window_map, &self->inner, client);
567     g_hash_table_insert(window_map, &self->title, client);
568     g_hash_table_insert(window_map, &self->label, client);
569     g_hash_table_insert(window_map, &self->max, client);
570     g_hash_table_insert(window_map, &self->close, client);
571     g_hash_table_insert(window_map, &self->desk, client);
572     g_hash_table_insert(window_map, &self->shade, client);
573     g_hash_table_insert(window_map, &self->icon, client);
574     g_hash_table_insert(window_map, &self->iconify, client);
575     g_hash_table_insert(window_map, &self->handle, client);
576     g_hash_table_insert(window_map, &self->lgrip, client);
577     g_hash_table_insert(window_map, &self->rgrip, client);
578     g_hash_table_insert(window_map, &self->tltresize, client);
579     g_hash_table_insert(window_map, &self->tllresize, client);
580     g_hash_table_insert(window_map, &self->trtresize, client);
581     g_hash_table_insert(window_map, &self->trrresize, client);
582 }
583
584 void frame_release_client(ObFrame *self, ObClient *client)
585 {
586     XEvent ev;
587     gboolean reparent = TRUE;
588
589     g_assert(self->client == client);
590
591     /* check if the app has already reparented its window away */
592     while (XCheckTypedWindowEvent(ob_display, client->window,
593                                   ReparentNotify, &ev))
594     {
595         /* This check makes sure we don't catch our own reparent action to
596            our frame window. This doesn't count as the app reparenting itself
597            away of course.
598
599            Reparent events that are generated by us are just discarded here.
600            They are of no consequence to us anyhow.
601         */
602         if (ev.xreparent.parent != self->plate) {
603             reparent = FALSE;
604             XPutBackEvent(ob_display, &ev);
605             break;
606         }
607     }
608
609     if (reparent) {
610         /* according to the ICCCM - if the client doesn't reparent itself,
611            then we will reparent the window to root for them */
612         XReparentWindow(ob_display, client->window,
613                         RootWindow(ob_display, ob_screen),
614                         client->area.x,
615                         client->area.y);
616     }
617
618     /* remove all the windows for the frame from the window_map */
619     g_hash_table_remove(window_map, &self->window);
620     g_hash_table_remove(window_map, &self->plate);
621     g_hash_table_remove(window_map, &self->inner);
622     g_hash_table_remove(window_map, &self->title);
623     g_hash_table_remove(window_map, &self->label);
624     g_hash_table_remove(window_map, &self->max);
625     g_hash_table_remove(window_map, &self->close);
626     g_hash_table_remove(window_map, &self->desk);
627     g_hash_table_remove(window_map, &self->shade);
628     g_hash_table_remove(window_map, &self->icon);
629     g_hash_table_remove(window_map, &self->iconify);
630     g_hash_table_remove(window_map, &self->handle);
631     g_hash_table_remove(window_map, &self->lgrip);
632     g_hash_table_remove(window_map, &self->rgrip);
633     g_hash_table_remove(window_map, &self->tltresize);
634     g_hash_table_remove(window_map, &self->tllresize);
635     g_hash_table_remove(window_map, &self->trtresize);
636     g_hash_table_remove(window_map, &self->trrresize);
637
638     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
639
640     frame_free(self);
641 }
642
643 static void layout_title(ObFrame *self)
644 {
645     gchar *lc;
646     gint x;
647     gboolean n, d, i, l, m, c, s;
648
649     n = d = i = l = m = c = s = FALSE;
650
651     /* figure out whats being shown, and the width of the label */
652     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
653     for (lc = config_title_layout; *lc != '\0'; ++lc) {
654         switch (*lc) {
655         case 'N':
656             if (n) { *lc = ' '; break; } /* rm duplicates */
657             n = TRUE;
658             self->label_width -= (ob_rr_theme->button_size + 2 +
659                                   ob_rr_theme->paddingx + 1);
660             break;
661         case 'D':
662             if (d) { *lc = ' '; break; }
663             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
664                 && config_theme_hidedisabled)
665                 break;
666             d = TRUE;
667             self->label_width -= (ob_rr_theme->button_size +
668                                   ob_rr_theme->paddingx + 1);
669             break;
670         case 'S':
671             if (s) { *lc = ' '; break; }
672             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
673                 && config_theme_hidedisabled)
674                 break;
675             s = TRUE;
676             self->label_width -= (ob_rr_theme->button_size +
677                                   ob_rr_theme->paddingx + 1);
678             break;
679         case 'I':
680             if (i) { *lc = ' '; break; }
681             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
682                 && config_theme_hidedisabled)
683                 break;
684             i = TRUE;
685             self->label_width -= (ob_rr_theme->button_size +
686                                   ob_rr_theme->paddingx + 1);
687             break;
688         case 'L':
689             if (l) { *lc = ' '; break; }
690             l = TRUE;
691             break;
692         case 'M':
693             if (m) { *lc = ' '; break; }
694             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
695                 && config_theme_hidedisabled)
696                 break;
697             m = TRUE;
698             self->label_width -= (ob_rr_theme->button_size +
699                                   ob_rr_theme->paddingx + 1);
700             break;
701         case 'C':
702             if (c) { *lc = ' '; break; }
703             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
704                 && config_theme_hidedisabled)
705                 break;
706             c = TRUE;
707             self->label_width -= (ob_rr_theme->button_size +
708                                   ob_rr_theme->paddingx + 1);
709             break;
710         }
711     }
712     if (self->label_width < 1) self->label_width = 1;
713
714     if (!n) XUnmapWindow(ob_display, self->icon);
715     if (!d) XUnmapWindow(ob_display, self->desk);
716     if (!s) XUnmapWindow(ob_display, self->shade);
717     if (!i) XUnmapWindow(ob_display, self->iconify);
718     if (!l) XUnmapWindow(ob_display, self->label);
719     if (!m) XUnmapWindow(ob_display, self->max);
720     if (!c) XUnmapWindow(ob_display, self->close);
721
722     x = ob_rr_theme->paddingx + 1;
723     for (lc = config_title_layout; *lc != '\0'; ++lc) {
724         switch (*lc) {
725         case 'N':
726             if (!n) break;
727             self->icon_x = x;
728             XMapWindow(ob_display, self->icon);
729             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->paddingy);
730             x += ob_rr_theme->button_size + 2 + ob_rr_theme->paddingx + 1;
731             break;
732         case 'D':
733             if (!d) break;
734             self->desk_x = x;
735             XMapWindow(ob_display, self->desk);
736             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->paddingy + 1);
737             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
738             break;
739         case 'S':
740             if (!s) break;
741             self->shade_x = x;
742             XMapWindow(ob_display, self->shade);
743             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->paddingy + 1);
744             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
745             break;
746         case 'I':
747             if (!i) break;
748             self->iconify_x = x;
749             XMapWindow(ob_display, self->iconify);
750             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->paddingy + 1);
751             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
752             break;
753         case 'L':
754             if (!l) break;
755             self->label_x = x;
756             XMapWindow(ob_display, self->label);
757             XMoveWindow(ob_display, self->label, x, ob_rr_theme->paddingy);
758             x += self->label_width + ob_rr_theme->paddingx + 1;
759             break;
760         case 'M':
761             if (!m) break;
762             self->max_x = x;
763             XMapWindow(ob_display, self->max);
764             XMoveWindow(ob_display, self->max, x, ob_rr_theme->paddingy + 1);
765             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
766             break;
767         case 'C':
768             if (!c) break;
769             self->close_x = x;
770             XMapWindow(ob_display, self->close);
771             XMoveWindow(ob_display, self->close, x, ob_rr_theme->paddingy + 1);
772             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
773             break;
774         }
775     }
776 }
777
778 ObFrameContext frame_context_from_string(const gchar *name)
779 {
780     if (!g_ascii_strcasecmp("Desktop", name))
781         return OB_FRAME_CONTEXT_DESKTOP;
782     else if (!g_ascii_strcasecmp("Client", name))
783         return OB_FRAME_CONTEXT_CLIENT;
784     else if (!g_ascii_strcasecmp("Titlebar", name))
785         return OB_FRAME_CONTEXT_TITLEBAR;
786     else if (!g_ascii_strcasecmp("Handle", name))
787         return OB_FRAME_CONTEXT_HANDLE;
788     else if (!g_ascii_strcasecmp("Frame", name))
789         return OB_FRAME_CONTEXT_FRAME;
790     else if (!g_ascii_strcasecmp("TLCorner", name))
791         return OB_FRAME_CONTEXT_TLCORNER;
792     else if (!g_ascii_strcasecmp("TRCorner", name))
793         return OB_FRAME_CONTEXT_TRCORNER;
794     else if (!g_ascii_strcasecmp("BLCorner", name))
795         return OB_FRAME_CONTEXT_BLCORNER;
796     else if (!g_ascii_strcasecmp("BRCorner", name))
797         return OB_FRAME_CONTEXT_BRCORNER;
798     else if (!g_ascii_strcasecmp("Maximize", name))
799         return OB_FRAME_CONTEXT_MAXIMIZE;
800     else if (!g_ascii_strcasecmp("AllDesktops", name))
801         return OB_FRAME_CONTEXT_ALLDESKTOPS;
802     else if (!g_ascii_strcasecmp("Shade", name))
803         return OB_FRAME_CONTEXT_SHADE;
804     else if (!g_ascii_strcasecmp("Iconify", name))
805         return OB_FRAME_CONTEXT_ICONIFY;
806     else if (!g_ascii_strcasecmp("Icon", name))
807         return OB_FRAME_CONTEXT_ICON;
808     else if (!g_ascii_strcasecmp("Close", name))
809         return OB_FRAME_CONTEXT_CLOSE;
810     else if (!g_ascii_strcasecmp("MoveResize", name))
811         return OB_FRAME_CONTEXT_MOVE_RESIZE;
812     return OB_FRAME_CONTEXT_NONE;
813 }
814
815 ObFrameContext frame_context(ObClient *client, Window win)
816 {
817     ObFrame *self;
818
819     if (moveresize_in_progress)
820         return OB_FRAME_CONTEXT_MOVE_RESIZE;
821
822     if (win == RootWindow(ob_display, ob_screen))
823         return OB_FRAME_CONTEXT_DESKTOP;
824     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
825     if (win == client->window) {
826         /* conceptually, this is the desktop, as far as users are
827            concerned */
828         if (client->type == OB_CLIENT_TYPE_DESKTOP)
829             return OB_FRAME_CONTEXT_DESKTOP;
830         return OB_FRAME_CONTEXT_CLIENT;
831     }
832
833     self = client->frame;
834     if (win == self->inner || win == self->plate) {
835         /* conceptually, this is the desktop, as far as users are
836            concerned */
837         if (client->type == OB_CLIENT_TYPE_DESKTOP)
838             return OB_FRAME_CONTEXT_DESKTOP;
839         return OB_FRAME_CONTEXT_CLIENT;
840     }
841
842     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
843     if (win == self->title)     return OB_FRAME_CONTEXT_TITLEBAR;
844     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
845     if (win == self->handle)    return OB_FRAME_CONTEXT_HANDLE;
846     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
847     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
848     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
849     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
850     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
851     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
852     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
853     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
854     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
855     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
856     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
857     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
858
859     return OB_FRAME_CONTEXT_NONE;
860 }
861
862 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
863 {
864     /* horizontal */
865     switch (self->client->gravity) {
866     default:
867     case NorthWestGravity:
868     case SouthWestGravity:
869     case WestGravity:
870         break;
871
872     case NorthGravity:
873     case SouthGravity:
874     case CenterGravity:
875         *x -= (self->size.left + self->size.right) / 2;
876         break;
877
878     case NorthEastGravity:
879     case SouthEastGravity:
880     case EastGravity:
881         *x -= self->size.left + self->size.right;
882         break;
883
884     case ForgetGravity:
885     case StaticGravity:
886         *x -= self->size.left;
887         break;
888     }
889
890     /* vertical */
891     switch (self->client->gravity) {
892     default:
893     case NorthWestGravity:
894     case NorthEastGravity:
895     case NorthGravity:
896         break;
897
898     case CenterGravity:
899     case EastGravity:
900     case WestGravity:
901         *y -= (self->size.top + self->size.bottom) / 2;
902         break;
903
904     case SouthWestGravity:
905     case SouthEastGravity:
906     case SouthGravity:
907         *y -= self->size.top + self->size.bottom;
908         break;
909
910     case ForgetGravity:
911     case StaticGravity:
912         *y -= self->size.top;
913         break;
914     }
915 }
916
917 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
918 {
919     /* horizontal */
920     switch (self->client->gravity) {
921     default:
922     case NorthWestGravity:
923     case WestGravity:
924     case SouthWestGravity:
925         break;
926     case NorthGravity:
927     case CenterGravity:
928     case SouthGravity:
929         *x += (self->size.left + self->size.right) / 2;
930         break;
931     case NorthEastGravity:
932     case EastGravity:
933     case SouthEastGravity:
934         *x += self->size.left + self->size.right;
935         break;
936     case StaticGravity:
937     case ForgetGravity:
938         *x += self->size.left;
939         break;
940     }
941
942     /* vertical */
943     switch (self->client->gravity) {
944     default:
945     case NorthWestGravity:
946     case NorthGravity:
947     case NorthEastGravity:
948         break;
949     case WestGravity:
950     case CenterGravity:
951     case EastGravity:
952         *y += (self->size.top + self->size.bottom) / 2;
953         break;
954     case SouthWestGravity:
955     case SouthGravity:
956     case SouthEastGravity:
957         *y += self->size.top + self->size.bottom;
958         break;
959     case StaticGravity:
960     case ForgetGravity:
961         *y += self->size.top;
962         break;
963     }
964 }
965
966 static void flash_done(gpointer data)
967 {
968     ObFrame *self = data;
969
970     if (self->focused != self->flash_on)
971         frame_adjust_focus(self, self->focused);
972 }
973
974 static gboolean flash_timeout(gpointer data)
975 {
976     ObFrame *self = data;
977     GTimeVal now;
978
979     g_get_current_time(&now);
980     if (now.tv_sec > self->flash_end.tv_sec ||
981         (now.tv_sec == self->flash_end.tv_sec &&
982          now.tv_usec >= self->flash_end.tv_usec))
983         self->flashing = FALSE;
984
985     if (!self->flashing)
986         return FALSE; /* we are done */
987
988     self->flash_on = !self->flash_on;
989     if (!self->focused) {
990         frame_adjust_focus(self, self->flash_on);
991         self->focused = FALSE;
992     }
993
994     return TRUE; /* go again */
995 }
996
997 void frame_flash_start(ObFrame *self)
998 {
999     self->flash_on = self->focused;
1000
1001     if (!self->flashing)
1002         ob_main_loop_timeout_add(ob_main_loop,
1003                                  G_USEC_PER_SEC * 0.6,
1004                                  flash_timeout,
1005                                  self,
1006                                  g_direct_equal,
1007                                  flash_done);
1008     g_get_current_time(&self->flash_end);
1009     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1010     
1011     self->flashing = TRUE;
1012 }
1013
1014 void frame_flash_stop(ObFrame *self)
1015 {
1016     self->flashing = FALSE;
1017 }