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