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