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