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