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