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