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