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