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