]> icculus.org git repositories - dana/openbox.git/blob - openbox/frame.c
add a window between the frame and the plate for drawing the client border. this...
[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 | 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
106     /* create the visible decor windows */
107
108     if (visual) {
109         /* client has a 32-bit visual */
110         mask |= CWColormap | CWBackPixel | CWBorderPixel;
111         attrib.colormap = RrColormap(ob_rr_inst);
112     }
113     attrib.event_mask = ELEMENT_EVENTMASK;
114     self->inner = createWindow(self->window, NULL, mask, &attrib);
115     self->title = createWindow(self->window, NULL, mask, &attrib);
116
117     mask |= CWCursor;
118     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
119     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
120     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
121     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
122     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
123     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
124
125     mask &= ~CWCursor;
126     self->label = createWindow(self->title, NULL, mask, &attrib);
127     self->max = createWindow(self->title, NULL, mask, &attrib);
128     self->close = createWindow(self->title, NULL, mask, &attrib);
129     self->desk = createWindow(self->title, NULL, mask, &attrib);
130     self->shade = createWindow(self->title, NULL, mask, &attrib);
131     self->icon = createWindow(self->title, NULL, mask, &attrib);
132     self->iconify = createWindow(self->title, NULL, mask, &attrib);
133     self->handle = createWindow(self->window, NULL, mask, &attrib);
134
135     mask |= CWCursor;
136     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
137     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
138     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
139     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
140
141     /* create the plate window which holds the client */
142
143     mask &= ~(CWEventMask | CWCursor);
144     self->plate = createWindow(self->inner, visual, mask, &attrib);
145
146     self->focused = FALSE;
147
148     /* the other stuff is shown based on decor settings */
149     XMapWindow(ob_display, self->plate);
150     XMapWindow(ob_display, self->inner);
151     XMapWindow(ob_display, self->lgrip);
152     XMapWindow(ob_display, self->rgrip);
153     XMapWindow(ob_display, self->label);
154
155     self->max_press = self->close_press = self->desk_press = 
156         self->iconify_press = self->shade_press = FALSE;
157     self->max_hover = self->close_hover = self->desk_hover = 
158         self->iconify_hover = self->shade_hover = FALSE;
159
160     set_theme_statics(self);
161
162     return (ObFrame*)self;
163 }
164
165 static void set_theme_statics(ObFrame *self)
166 {
167     /* set colors/appearance/sizes for stuff that doesn't change */
168     XSetWindowBorder(ob_display, self->window,
169                      RrColorPixel(ob_rr_theme->frame_b_color));
170     XSetWindowBorder(ob_display, self->title,
171                      RrColorPixel(ob_rr_theme->frame_b_color));
172     XSetWindowBorder(ob_display, self->handle,
173                      RrColorPixel(ob_rr_theme->frame_b_color));
174     XSetWindowBorder(ob_display, self->rgrip,
175                      RrColorPixel(ob_rr_theme->frame_b_color));
176     XSetWindowBorder(ob_display, self->lgrip,
177                      RrColorPixel(ob_rr_theme->frame_b_color));
178
179     XResizeWindow(ob_display, self->max,
180                   ob_rr_theme->button_size, ob_rr_theme->button_size);
181     XResizeWindow(ob_display, self->iconify,
182                   ob_rr_theme->button_size, ob_rr_theme->button_size);
183     XResizeWindow(ob_display, self->icon,
184                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
185     XResizeWindow(ob_display, self->close,
186                   ob_rr_theme->button_size, ob_rr_theme->button_size);
187     XResizeWindow(ob_display, self->desk,
188                   ob_rr_theme->button_size, ob_rr_theme->button_size);
189     XResizeWindow(ob_display, self->shade,
190                   ob_rr_theme->button_size, ob_rr_theme->button_size);
191     if (ob_rr_theme->handle_height > 0) {
192         XResizeWindow(ob_display, self->lgrip,
193                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
194         XResizeWindow(ob_display, self->rgrip,
195                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
196     }
197     XResizeWindow(ob_display, self->tltresize,
198                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
199     XResizeWindow(ob_display, self->trtresize,
200                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
201     XResizeWindow(ob_display, self->tllresize,
202                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
203     XResizeWindow(ob_display, self->trrresize,
204                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
205
206     /* set up the dynamic appearances */
207     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
208     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
209     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
210     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
211     self->a_unfocused_handle =
212         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
213     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
214     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
215 }
216
217 static void free_theme_statics(ObFrame *self)
218 {
219     RrAppearanceFree(self->a_unfocused_title); 
220     RrAppearanceFree(self->a_focused_title);
221     RrAppearanceFree(self->a_unfocused_label);
222     RrAppearanceFree(self->a_focused_label);
223     RrAppearanceFree(self->a_unfocused_handle);
224     RrAppearanceFree(self->a_focused_handle);
225     RrAppearanceFree(self->a_icon);
226 }
227
228 static void frame_free(ObFrame *self)
229 {
230     free_theme_statics(self);
231
232     XDestroyWindow(ob_display, self->window);
233     if (self->colormap)
234         XFreeColormap(ob_display, self->colormap);
235
236     g_free(self);
237 }
238
239 void frame_show(ObFrame *self)
240 {
241     if (!self->visible) {
242         self->visible = TRUE;
243         XMapWindow(ob_display, self->client->window);
244         XMapWindow(ob_display, self->window);
245     }
246 }
247
248 void frame_hide(ObFrame *self)
249 {
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 inner border window which contains the plate
429              */
430             XMoveResizeWindow(ob_display, self->inner,
431                               self->innersize.left - self->cbwidth_x,
432                               self->innersize.top - self->cbwidth_y,
433                               self->client->area.width +
434                               self->cbwidth_x * 2,
435                               self->client->area.height +
436                               self->cbwidth_y * 2);
437
438             /* move and resize the plate */
439             XMoveResizeWindow(ob_display, self->plate,
440                               self->cbwidth_x,
441                               self->cbwidth_y,
442                               self->client->area.width,
443                               self->client->area.height);
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_title(ObFrame *self)
518 {
519     framerender_frame(self);
520 }
521
522 void frame_adjust_icon(ObFrame *self)
523 {
524     framerender_frame(self);
525 }
526
527 void frame_grab_client(ObFrame *self, ObClient *client)
528 {
529     self->client = client;
530
531     /* reparent the client to the frame */
532     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
533     /*
534       When reparenting the client window, it is usually not mapped yet, since
535       this occurs from a MapRequest. However, in the case where Openbox is
536       starting up, the window is already mapped, so we'll see unmap events for
537       it. There are 2 unmap events generated that we see, one with the 'event'
538       member set the root window, and one set to the client, but both get
539       handled and need to be ignored.
540     */
541     if (ob_state() == OB_STATE_STARTING)
542         client->ignore_unmaps += 2;
543
544     /* select the event mask on the client's parent (to receive config/map
545        req's) the ButtonPress is to catch clicks on the client border */
546     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
547
548     frame_adjust_area(self, TRUE, TRUE, FALSE);
549
550     /* map the client so it maps when the frame does */
551     XMapWindow(ob_display, client->window);
552
553     /* set all the windows for the frame in the window_map */
554     g_hash_table_insert(window_map, &self->window, client);
555     g_hash_table_insert(window_map, &self->plate, client);
556     g_hash_table_insert(window_map, &self->inner, client);
557     g_hash_table_insert(window_map, &self->title, client);
558     g_hash_table_insert(window_map, &self->label, client);
559     g_hash_table_insert(window_map, &self->max, client);
560     g_hash_table_insert(window_map, &self->close, client);
561     g_hash_table_insert(window_map, &self->desk, client);
562     g_hash_table_insert(window_map, &self->shade, client);
563     g_hash_table_insert(window_map, &self->icon, client);
564     g_hash_table_insert(window_map, &self->iconify, client);
565     g_hash_table_insert(window_map, &self->handle, client);
566     g_hash_table_insert(window_map, &self->lgrip, client);
567     g_hash_table_insert(window_map, &self->rgrip, client);
568     g_hash_table_insert(window_map, &self->tltresize, client);
569     g_hash_table_insert(window_map, &self->tllresize, client);
570     g_hash_table_insert(window_map, &self->trtresize, client);
571     g_hash_table_insert(window_map, &self->trrresize, client);
572 }
573
574 void frame_release_client(ObFrame *self, ObClient *client)
575 {
576     XEvent ev;
577     gboolean reparent = TRUE;
578
579     g_assert(self->client == client);
580
581     /* check if the app has already reparented its window away */
582     while (XCheckTypedWindowEvent(ob_display, client->window,
583                                   ReparentNotify, &ev))
584     {
585         /* This check makes sure we don't catch our own reparent action to
586            our frame window. This doesn't count as the app reparenting itself
587            away of course.
588
589            Reparent events that are generated by us are just discarded here.
590            They are of no consequence to us anyhow.
591         */
592         if (ev.xreparent.parent != self->plate) {
593             reparent = FALSE;
594             XPutBackEvent(ob_display, &ev);
595             break;
596         }
597     }
598
599     if (reparent) {
600         /* according to the ICCCM - if the client doesn't reparent itself,
601            then we will reparent the window to root for them */
602         XReparentWindow(ob_display, client->window,
603                         RootWindow(ob_display, ob_screen),
604                         client->area.x,
605                         client->area.y);
606     }
607
608     /* remove all the windows for the frame from the window_map */
609     g_hash_table_remove(window_map, &self->window);
610     g_hash_table_remove(window_map, &self->plate);
611     g_hash_table_remove(window_map, &self->inner);
612     g_hash_table_remove(window_map, &self->title);
613     g_hash_table_remove(window_map, &self->label);
614     g_hash_table_remove(window_map, &self->max);
615     g_hash_table_remove(window_map, &self->close);
616     g_hash_table_remove(window_map, &self->desk);
617     g_hash_table_remove(window_map, &self->shade);
618     g_hash_table_remove(window_map, &self->icon);
619     g_hash_table_remove(window_map, &self->iconify);
620     g_hash_table_remove(window_map, &self->handle);
621     g_hash_table_remove(window_map, &self->lgrip);
622     g_hash_table_remove(window_map, &self->rgrip);
623     g_hash_table_remove(window_map, &self->tltresize);
624     g_hash_table_remove(window_map, &self->tllresize);
625     g_hash_table_remove(window_map, &self->trtresize);
626     g_hash_table_remove(window_map, &self->trrresize);
627
628     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
629
630     frame_free(self);
631 }
632
633 static void layout_title(ObFrame *self)
634 {
635     gchar *lc;
636     gint x;
637     gboolean n, d, i, l, m, c, s;
638
639     n = d = i = l = m = c = s = FALSE;
640
641     /* figure out whats being shown, and the width of the label */
642     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
643     for (lc = config_title_layout; *lc != '\0'; ++lc) {
644         switch (*lc) {
645         case 'N':
646             if (n) { *lc = ' '; break; } /* rm duplicates */
647             n = TRUE;
648             self->label_width -= (ob_rr_theme->button_size + 2 +
649                                   ob_rr_theme->paddingx + 1);
650             break;
651         case 'D':
652             if (d) { *lc = ' '; break; }
653             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
654                 && config_theme_hidedisabled)
655                 break;
656             d = TRUE;
657             self->label_width -= (ob_rr_theme->button_size +
658                                   ob_rr_theme->paddingx + 1);
659             break;
660         case 'S':
661             if (s) { *lc = ' '; break; }
662             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
663                 && config_theme_hidedisabled)
664                 break;
665             s = TRUE;
666             self->label_width -= (ob_rr_theme->button_size +
667                                   ob_rr_theme->paddingx + 1);
668             break;
669         case 'I':
670             if (i) { *lc = ' '; break; }
671             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
672                 && config_theme_hidedisabled)
673                 break;
674             i = TRUE;
675             self->label_width -= (ob_rr_theme->button_size +
676                                   ob_rr_theme->paddingx + 1);
677             break;
678         case 'L':
679             if (l) { *lc = ' '; break; }
680             l = TRUE;
681             break;
682         case 'M':
683             if (m) { *lc = ' '; break; }
684             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
685                 && config_theme_hidedisabled)
686                 break;
687             m = TRUE;
688             self->label_width -= (ob_rr_theme->button_size +
689                                   ob_rr_theme->paddingx + 1);
690             break;
691         case 'C':
692             if (c) { *lc = ' '; break; }
693             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
694                 && config_theme_hidedisabled)
695                 break;
696             c = TRUE;
697             self->label_width -= (ob_rr_theme->button_size +
698                                   ob_rr_theme->paddingx + 1);
699             break;
700         }
701     }
702     if (self->label_width < 1) self->label_width = 1;
703
704     if (!n) XUnmapWindow(ob_display, self->icon);
705     if (!d) XUnmapWindow(ob_display, self->desk);
706     if (!s) XUnmapWindow(ob_display, self->shade);
707     if (!i) XUnmapWindow(ob_display, self->iconify);
708     if (!l) XUnmapWindow(ob_display, self->label);
709     if (!m) XUnmapWindow(ob_display, self->max);
710     if (!c) XUnmapWindow(ob_display, self->close);
711
712     x = ob_rr_theme->paddingx + 1;
713     for (lc = config_title_layout; *lc != '\0'; ++lc) {
714         switch (*lc) {
715         case 'N':
716             if (!n) break;
717             self->icon_x = x;
718             XMapWindow(ob_display, self->icon);
719             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->paddingy);
720             x += ob_rr_theme->button_size + 2 + ob_rr_theme->paddingx + 1;
721             break;
722         case 'D':
723             if (!d) break;
724             self->desk_x = x;
725             XMapWindow(ob_display, self->desk);
726             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->paddingy + 1);
727             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
728             break;
729         case 'S':
730             if (!s) break;
731             self->shade_x = x;
732             XMapWindow(ob_display, self->shade);
733             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->paddingy + 1);
734             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
735             break;
736         case 'I':
737             if (!i) break;
738             self->iconify_x = x;
739             XMapWindow(ob_display, self->iconify);
740             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->paddingy + 1);
741             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
742             break;
743         case 'L':
744             if (!l) break;
745             self->label_x = x;
746             XMapWindow(ob_display, self->label);
747             XMoveWindow(ob_display, self->label, x, ob_rr_theme->paddingy);
748             x += self->label_width + ob_rr_theme->paddingx + 1;
749             break;
750         case 'M':
751             if (!m) break;
752             self->max_x = x;
753             XMapWindow(ob_display, self->max);
754             XMoveWindow(ob_display, self->max, x, ob_rr_theme->paddingy + 1);
755             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
756             break;
757         case 'C':
758             if (!c) break;
759             self->close_x = x;
760             XMapWindow(ob_display, self->close);
761             XMoveWindow(ob_display, self->close, x, ob_rr_theme->paddingy + 1);
762             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
763             break;
764         }
765     }
766 }
767
768 ObFrameContext frame_context_from_string(const gchar *name)
769 {
770     if (!g_ascii_strcasecmp("Desktop", name))
771         return OB_FRAME_CONTEXT_DESKTOP;
772     else if (!g_ascii_strcasecmp("Client", name))
773         return OB_FRAME_CONTEXT_CLIENT;
774     else if (!g_ascii_strcasecmp("Titlebar", name))
775         return OB_FRAME_CONTEXT_TITLEBAR;
776     else if (!g_ascii_strcasecmp("Handle", name))
777         return OB_FRAME_CONTEXT_HANDLE;
778     else if (!g_ascii_strcasecmp("Frame", name))
779         return OB_FRAME_CONTEXT_FRAME;
780     else if (!g_ascii_strcasecmp("TLCorner", name))
781         return OB_FRAME_CONTEXT_TLCORNER;
782     else if (!g_ascii_strcasecmp("TRCorner", name))
783         return OB_FRAME_CONTEXT_TRCORNER;
784     else if (!g_ascii_strcasecmp("BLCorner", name))
785         return OB_FRAME_CONTEXT_BLCORNER;
786     else if (!g_ascii_strcasecmp("BRCorner", name))
787         return OB_FRAME_CONTEXT_BRCORNER;
788     else if (!g_ascii_strcasecmp("Maximize", name))
789         return OB_FRAME_CONTEXT_MAXIMIZE;
790     else if (!g_ascii_strcasecmp("AllDesktops", name))
791         return OB_FRAME_CONTEXT_ALLDESKTOPS;
792     else if (!g_ascii_strcasecmp("Shade", name))
793         return OB_FRAME_CONTEXT_SHADE;
794     else if (!g_ascii_strcasecmp("Iconify", name))
795         return OB_FRAME_CONTEXT_ICONIFY;
796     else if (!g_ascii_strcasecmp("Icon", name))
797         return OB_FRAME_CONTEXT_ICON;
798     else if (!g_ascii_strcasecmp("Close", name))
799         return OB_FRAME_CONTEXT_CLOSE;
800     else if (!g_ascii_strcasecmp("MoveResize", name))
801         return OB_FRAME_CONTEXT_MOVE_RESIZE;
802     return OB_FRAME_CONTEXT_NONE;
803 }
804
805 ObFrameContext frame_context(ObClient *client, Window win)
806 {
807     ObFrame *self;
808
809     if (moveresize_in_progress)
810         return OB_FRAME_CONTEXT_MOVE_RESIZE;
811
812     if (win == RootWindow(ob_display, ob_screen))
813         return OB_FRAME_CONTEXT_DESKTOP;
814     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
815     if (win == client->window) {
816         /* conceptually, this is the desktop, as far as users are
817            concerned */
818         if (client->type == OB_CLIENT_TYPE_DESKTOP)
819             return OB_FRAME_CONTEXT_DESKTOP;
820         return OB_FRAME_CONTEXT_CLIENT;
821     }
822
823     self = client->frame;
824     if (win == self->plate) {
825         /* conceptually, this is the desktop, as far as users are
826            concerned */
827         if (client->type == OB_CLIENT_TYPE_DESKTOP)
828             return OB_FRAME_CONTEXT_DESKTOP;
829         return OB_FRAME_CONTEXT_CLIENT;
830     }
831
832     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
833     if (win == self->inner)     return OB_FRAME_CONTEXT_FRAME;
834     if (win == self->title)     return OB_FRAME_CONTEXT_TITLEBAR;
835     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
836     if (win == self->handle)    return OB_FRAME_CONTEXT_HANDLE;
837     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
838     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
839     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
840     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
841     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
842     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
843     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
844     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
845     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
846     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
847     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
848     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
849
850     return OB_FRAME_CONTEXT_NONE;
851 }
852
853 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
854 {
855     /* horizontal */
856     switch (self->client->gravity) {
857     default:
858     case NorthWestGravity:
859     case SouthWestGravity:
860     case WestGravity:
861         break;
862
863     case NorthGravity:
864     case SouthGravity:
865     case CenterGravity:
866         *x -= (self->size.left + self->size.right) / 2;
867         break;
868
869     case NorthEastGravity:
870     case SouthEastGravity:
871     case EastGravity:
872         *x -= self->size.left + self->size.right;
873         break;
874
875     case ForgetGravity:
876     case StaticGravity:
877         *x -= self->size.left;
878         break;
879     }
880
881     /* vertical */
882     switch (self->client->gravity) {
883     default:
884     case NorthWestGravity:
885     case NorthEastGravity:
886     case NorthGravity:
887         break;
888
889     case CenterGravity:
890     case EastGravity:
891     case WestGravity:
892         *y -= (self->size.top + self->size.bottom) / 2;
893         break;
894
895     case SouthWestGravity:
896     case SouthEastGravity:
897     case SouthGravity:
898         *y -= self->size.top + self->size.bottom;
899         break;
900
901     case ForgetGravity:
902     case StaticGravity:
903         *y -= self->size.top;
904         break;
905     }
906 }
907
908 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
909 {
910     /* horizontal */
911     switch (self->client->gravity) {
912     default:
913     case NorthWestGravity:
914     case WestGravity:
915     case SouthWestGravity:
916         break;
917     case NorthGravity:
918     case CenterGravity:
919     case SouthGravity:
920         *x += (self->size.left + self->size.right) / 2;
921         break;
922     case NorthEastGravity:
923     case EastGravity:
924     case SouthEastGravity:
925         *x += self->size.left + self->size.right;
926         break;
927     case StaticGravity:
928     case ForgetGravity:
929         *x += self->size.left;
930         break;
931     }
932
933     /* vertical */
934     switch (self->client->gravity) {
935     default:
936     case NorthWestGravity:
937     case NorthGravity:
938     case NorthEastGravity:
939         break;
940     case WestGravity:
941     case CenterGravity:
942     case EastGravity:
943         *y += (self->size.top + self->size.bottom) / 2;
944         break;
945     case SouthWestGravity:
946     case SouthGravity:
947     case SouthEastGravity:
948         *y += self->size.top + self->size.bottom;
949         break;
950     case StaticGravity:
951     case ForgetGravity:
952         *y += self->size.top;
953         break;
954     }
955 }
956
957 static void flash_done(gpointer data)
958 {
959     ObFrame *self = data;
960
961     if (self->focused != self->flash_on)
962         frame_adjust_focus(self, self->focused);
963 }
964
965 static gboolean flash_timeout(gpointer data)
966 {
967     ObFrame *self = data;
968     GTimeVal now;
969
970     g_get_current_time(&now);
971     if (now.tv_sec > self->flash_end.tv_sec ||
972         (now.tv_sec == self->flash_end.tv_sec &&
973          now.tv_usec >= self->flash_end.tv_usec))
974         self->flashing = FALSE;
975
976     if (!self->flashing)
977         return FALSE; /* we are done */
978
979     self->flash_on = !self->flash_on;
980     if (!self->focused) {
981         frame_adjust_focus(self, self->flash_on);
982         self->focused = FALSE;
983     }
984
985     return TRUE; /* go again */
986 }
987
988 void frame_flash_start(ObFrame *self)
989 {
990     self->flash_on = self->focused;
991
992     if (!self->flashing)
993         ob_main_loop_timeout_add(ob_main_loop,
994                                  G_USEC_PER_SEC * 0.6,
995                                  flash_timeout,
996                                  self,
997                                  g_direct_equal,
998                                  flash_done);
999     g_get_current_time(&self->flash_end);
1000     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1001     
1002     self->flashing = TRUE;
1003 }
1004
1005 void frame_flash_stop(ObFrame *self)
1006 {
1007     self->flashing = FALSE;
1008 }