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