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