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