]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/frame.c
80 cols
[mikachu/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003        Ben Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "extensions.h"
24 #include "prop.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "mainloop.h"
28 #include "focus.h"
29 #include "moveresize.h"
30 #include "render/theme.h"
31
32 #define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask)
33 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
34                          ButtonPressMask | ButtonReleaseMask | \
35                          VisibilityChangeMask)
36 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
37                            ButtonMotionMask | ExposureMask | \
38                            EnterWindowMask | LeaveWindowMask)
39
40 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
41                            f->cbwidth_y)
42
43 static void layout_title(ObFrame *self);
44 static void flash_done(gpointer data);
45 static gboolean flash_timeout(gpointer data);
46
47 static void set_theme_statics(ObFrame *self);
48 static void free_theme_statics(ObFrame *self);
49
50 static Window createWindow(Window parent, gulong mask,
51                            XSetWindowAttributes *attrib)
52 {
53     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
54                          RrDepth(ob_rr_inst), InputOutput,
55                          RrVisual(ob_rr_inst), mask, attrib);
56                        
57 }
58
59 ObFrame *frame_new()
60 {
61     XSetWindowAttributes attrib;
62     gulong mask;
63     ObFrame *self;
64
65     self = g_new0(ObFrame, 1);
66
67     self->obscured = TRUE;
68
69     /* create all of the decor windows */
70     mask = CWEventMask;
71     attrib.event_mask = FRAME_EVENTMASK;
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             frame_adjust_shape(self);
421         }
422
423         if (!STRUT_EQUAL(self->size, oldsize)) {
424             gulong vals[4];
425             vals[0] = self->size.left;
426             vals[1] = self->size.right;
427             vals[2] = self->size.top;
428             vals[3] = self->size.bottom;
429             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
430                         cardinal, vals, 4);
431         }
432
433         /* if this occurs while we are focus cycling, the indicator needs to
434            match the changes */
435         if (focus_cycle_target == self->client)
436             focus_cycle_draw_indicator();
437     }
438     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
439         XResizeWindow(ob_display, self->label, self->label_width,
440                       ob_rr_theme->label_height);
441 }
442
443 void frame_adjust_state(ObFrame *self)
444 {
445     framerender_frame(self);
446 }
447
448 void frame_adjust_focus(ObFrame *self, gboolean hilite)
449 {
450     self->focused = hilite;
451     framerender_frame(self);
452 }
453
454 void frame_adjust_title(ObFrame *self)
455 {
456     framerender_frame(self);
457 }
458
459 void frame_adjust_icon(ObFrame *self)
460 {
461     framerender_frame(self);
462 }
463
464 void frame_grab_client(ObFrame *self, ObClient *client)
465 {
466     self->client = client;
467
468     /* reparent the client to the frame */
469     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
470     /*
471       When reparenting the client window, it is usually not mapped yet, since
472       this occurs from a MapRequest. However, in the case where Openbox is
473       starting up, the window is already mapped, so we'll see unmap events for
474       it. There are 2 unmap events generated that we see, one with the 'event'
475       member set the root window, and one set to the client, but both get
476       handled and need to be ignored.
477     */
478     if (ob_state() == OB_STATE_STARTING)
479         client->ignore_unmaps += 2;
480
481     /* select the event mask on the client's parent (to receive config/map
482        req's) the ButtonPress is to catch clicks on the client border */
483     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
484
485     /* map the client so it maps when the frame does */
486     XMapWindow(ob_display, client->window);
487
488     frame_adjust_area(self, TRUE, TRUE, FALSE);
489
490     /* set all the windows for the frame in the window_map */
491     g_hash_table_insert(window_map, &self->window, client);
492     g_hash_table_insert(window_map, &self->plate, client);
493     g_hash_table_insert(window_map, &self->title, client);
494     g_hash_table_insert(window_map, &self->label, client);
495     g_hash_table_insert(window_map, &self->max, client);
496     g_hash_table_insert(window_map, &self->close, client);
497     g_hash_table_insert(window_map, &self->desk, client);
498     g_hash_table_insert(window_map, &self->shade, client);
499     g_hash_table_insert(window_map, &self->icon, client);
500     g_hash_table_insert(window_map, &self->iconify, client);
501     g_hash_table_insert(window_map, &self->handle, client);
502     g_hash_table_insert(window_map, &self->lgrip, client);
503     g_hash_table_insert(window_map, &self->rgrip, client);
504     g_hash_table_insert(window_map, &self->tlresize, client);
505     g_hash_table_insert(window_map, &self->trresize, client);
506 }
507
508 void frame_release_client(ObFrame *self, ObClient *client)
509 {
510     XEvent ev;
511     gboolean reparent = TRUE;
512
513     g_assert(self->client == client);
514
515     /* check if the app has already reparented its window away */
516     while (XCheckTypedWindowEvent(ob_display, client->window,
517                                   ReparentNotify, &ev))
518     {
519         /* This check makes sure we don't catch our own reparent action to
520            our frame window. This doesn't count as the app reparenting itself
521            away of course.
522
523            Reparent events that are generated by us are just discarded here.
524            They are of no consequence to us anyhow.
525         */
526         if (ev.xreparent.parent != self->plate) {
527             reparent = FALSE;
528             XPutBackEvent(ob_display, &ev);
529             break;
530         }
531     }
532
533     if (reparent) {
534         /* according to the ICCCM - if the client doesn't reparent itself,
535            then we will reparent the window to root for them */
536         XReparentWindow(ob_display, client->window,
537                         RootWindow(ob_display, ob_screen),
538                         client->area.x,
539                         client->area.y);
540     }
541
542     /* remove all the windows for the frame from the window_map */
543     g_hash_table_remove(window_map, &self->window);
544     g_hash_table_remove(window_map, &self->plate);
545     g_hash_table_remove(window_map, &self->title);
546     g_hash_table_remove(window_map, &self->label);
547     g_hash_table_remove(window_map, &self->max);
548     g_hash_table_remove(window_map, &self->close);
549     g_hash_table_remove(window_map, &self->desk);
550     g_hash_table_remove(window_map, &self->shade);
551     g_hash_table_remove(window_map, &self->icon);
552     g_hash_table_remove(window_map, &self->iconify);
553     g_hash_table_remove(window_map, &self->handle);
554     g_hash_table_remove(window_map, &self->lgrip);
555     g_hash_table_remove(window_map, &self->rgrip);
556     g_hash_table_remove(window_map, &self->tlresize);
557     g_hash_table_remove(window_map, &self->trresize);
558
559     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
560
561     frame_free(self);
562 }
563
564 static void layout_title(ObFrame *self)
565 {
566     gchar *lc;
567     gint x;
568     gboolean n, d, i, l, m, c, s;
569
570     n = d = i = l = m = c = s = FALSE;
571
572     /* figure out whats being shown, and the width of the label */
573     self->label_width = self->width - (ob_rr_theme->padding + 1) * 2;
574     for (lc = config_title_layout; *lc != '\0'; ++lc) {
575         switch (*lc) {
576         case 'N':
577             if (n) { *lc = ' '; break; } /* rm duplicates */
578             n = TRUE;
579             self->label_width -= (ob_rr_theme->button_size + 2 +
580                                   ob_rr_theme->padding + 1);
581             break;
582         case 'D':
583             if (d) { *lc = ' '; break; }
584             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
585                 && config_theme_hidedisabled)
586                 break;
587             d = TRUE;
588             self->label_width -= (ob_rr_theme->button_size +
589                                   ob_rr_theme->padding + 1);
590             break;
591         case 'S':
592             if (s) { *lc = ' '; break; }
593             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
594                 && config_theme_hidedisabled)
595                 break;
596             s = TRUE;
597             self->label_width -= (ob_rr_theme->button_size +
598                                   ob_rr_theme->padding + 1);
599             break;
600         case 'I':
601             if (i) { *lc = ' '; break; }
602             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
603                 && config_theme_hidedisabled)
604                 break;
605             i = TRUE;
606             self->label_width -= (ob_rr_theme->button_size +
607                                   ob_rr_theme->padding + 1);
608             break;
609         case 'L':
610             if (l) { *lc = ' '; break; }
611             l = TRUE;
612             break;
613         case 'M':
614             if (m) { *lc = ' '; break; }
615             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
616                 && config_theme_hidedisabled)
617                 break;
618             m = TRUE;
619             self->label_width -= (ob_rr_theme->button_size +
620                                   ob_rr_theme->padding + 1);
621             break;
622         case 'C':
623             if (c) { *lc = ' '; break; }
624             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
625                 && config_theme_hidedisabled)
626                 break;
627             c = TRUE;
628             self->label_width -= (ob_rr_theme->button_size +
629                                   ob_rr_theme->padding + 1);
630             break;
631         }
632     }
633     if (self->label_width < 1) self->label_width = 1;
634
635     if (!n) XUnmapWindow(ob_display, self->icon);
636     if (!d) XUnmapWindow(ob_display, self->desk);
637     if (!s) XUnmapWindow(ob_display, self->shade);
638     if (!i) XUnmapWindow(ob_display, self->iconify);
639     if (!l) XUnmapWindow(ob_display, self->label);
640     if (!m) XUnmapWindow(ob_display, self->max);
641     if (!c) XUnmapWindow(ob_display, self->close);
642
643     x = ob_rr_theme->padding + 1;
644     for (lc = config_title_layout; *lc != '\0'; ++lc) {
645         switch (*lc) {
646         case 'N':
647             if (!n) break;
648             self->icon_x = x;
649             XMapWindow(ob_display, self->icon);
650             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->padding);
651             x += ob_rr_theme->button_size + 2 + ob_rr_theme->padding + 1;
652             break;
653         case 'D':
654             if (!d) break;
655             self->desk_x = x;
656             XMapWindow(ob_display, self->desk);
657             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->padding + 1);
658             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
659             break;
660         case 'S':
661             if (!s) break;
662             self->shade_x = x;
663             XMapWindow(ob_display, self->shade);
664             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->padding + 1);
665             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
666             break;
667         case 'I':
668             if (!i) break;
669             self->iconify_x = x;
670             XMapWindow(ob_display, self->iconify);
671             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->padding + 1);
672             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
673             break;
674         case 'L':
675             if (!l) break;
676             self->label_x = x;
677             XMapWindow(ob_display, self->label);
678             XMoveWindow(ob_display, self->label, x, ob_rr_theme->padding);
679             x += self->label_width + ob_rr_theme->padding + 1;
680             break;
681         case 'M':
682             if (!m) break;
683             self->max_x = x;
684             XMapWindow(ob_display, self->max);
685             XMoveWindow(ob_display, self->max, x, ob_rr_theme->padding + 1);
686             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
687             break;
688         case 'C':
689             if (!c) break;
690             self->close_x = x;
691             XMapWindow(ob_display, self->close);
692             XMoveWindow(ob_display, self->close, x, ob_rr_theme->padding + 1);
693             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
694             break;
695         }
696     }
697 }
698
699 ObFrameContext frame_context_from_string(const gchar *name)
700 {
701     if (!g_ascii_strcasecmp("Desktop", name))
702         return OB_FRAME_CONTEXT_DESKTOP;
703     else if (!g_ascii_strcasecmp("Client", name))
704         return OB_FRAME_CONTEXT_CLIENT;
705     else if (!g_ascii_strcasecmp("Titlebar", name))
706         return OB_FRAME_CONTEXT_TITLEBAR;
707     else if (!g_ascii_strcasecmp("Handle", name))
708         return OB_FRAME_CONTEXT_HANDLE;
709     else if (!g_ascii_strcasecmp("Frame", name))
710         return OB_FRAME_CONTEXT_FRAME;
711     else if (!g_ascii_strcasecmp("TLCorner", name))
712         return OB_FRAME_CONTEXT_TLCORNER;
713     else if (!g_ascii_strcasecmp("TRCorner", name))
714         return OB_FRAME_CONTEXT_TRCORNER;
715     else if (!g_ascii_strcasecmp("BLCorner", name))
716         return OB_FRAME_CONTEXT_BLCORNER;
717     else if (!g_ascii_strcasecmp("BRCorner", name))
718         return OB_FRAME_CONTEXT_BRCORNER;
719     else if (!g_ascii_strcasecmp("Maximize", name))
720         return OB_FRAME_CONTEXT_MAXIMIZE;
721     else if (!g_ascii_strcasecmp("AllDesktops", name))
722         return OB_FRAME_CONTEXT_ALLDESKTOPS;
723     else if (!g_ascii_strcasecmp("Shade", name))
724         return OB_FRAME_CONTEXT_SHADE;
725     else if (!g_ascii_strcasecmp("Iconify", name))
726         return OB_FRAME_CONTEXT_ICONIFY;
727     else if (!g_ascii_strcasecmp("Icon", name))
728         return OB_FRAME_CONTEXT_ICON;
729     else if (!g_ascii_strcasecmp("Close", name))
730         return OB_FRAME_CONTEXT_CLOSE;
731     else if (!g_ascii_strcasecmp("MoveResize", name))
732         return OB_FRAME_CONTEXT_MOVE_RESIZE;
733     return OB_FRAME_CONTEXT_NONE;
734 }
735
736 ObFrameContext frame_context(ObClient *client, Window win)
737 {
738     ObFrame *self;
739
740     if (moveresize_in_progress)
741         return OB_FRAME_CONTEXT_MOVE_RESIZE;
742
743     if (win == RootWindow(ob_display, ob_screen))
744         return OB_FRAME_CONTEXT_DESKTOP;
745     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
746     if (win == client->window) {
747         /* conceptually, this is the desktop, as far as users are
748            concerned */
749         if (client->type == OB_CLIENT_TYPE_DESKTOP)
750             return OB_FRAME_CONTEXT_DESKTOP;
751         return OB_FRAME_CONTEXT_CLIENT;
752     }
753
754     self = client->frame;
755     if (win == self->plate) {
756         /* conceptually, this is the desktop, as far as users are
757            concerned */
758         if (client->type == OB_CLIENT_TYPE_DESKTOP)
759             return OB_FRAME_CONTEXT_DESKTOP;
760         return OB_FRAME_CONTEXT_CLIENT;
761     }
762
763     if (win == self->window)   return OB_FRAME_CONTEXT_FRAME;
764     if (win == self->title)    return OB_FRAME_CONTEXT_TITLEBAR;
765     if (win == self->label)    return OB_FRAME_CONTEXT_TITLEBAR;
766     if (win == self->handle)   return OB_FRAME_CONTEXT_HANDLE;
767     if (win == self->lgrip)    return OB_FRAME_CONTEXT_BLCORNER;
768     if (win == self->rgrip)    return OB_FRAME_CONTEXT_BRCORNER;
769     if (win == self->tlresize) return OB_FRAME_CONTEXT_TLCORNER;
770     if (win == self->trresize) return OB_FRAME_CONTEXT_TRCORNER;
771     if (win == self->max)      return OB_FRAME_CONTEXT_MAXIMIZE;
772     if (win == self->iconify)  return OB_FRAME_CONTEXT_ICONIFY;
773     if (win == self->close)    return OB_FRAME_CONTEXT_CLOSE;
774     if (win == self->icon)     return OB_FRAME_CONTEXT_ICON;
775     if (win == self->desk)     return OB_FRAME_CONTEXT_ALLDESKTOPS;
776     if (win == self->shade)    return OB_FRAME_CONTEXT_SHADE;
777
778     return OB_FRAME_CONTEXT_NONE;
779 }
780
781 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
782 {
783     /* horizontal */
784     switch (self->client->gravity) {
785     default:
786     case NorthWestGravity:
787     case SouthWestGravity:
788     case WestGravity:
789         break;
790
791     case NorthGravity:
792     case SouthGravity:
793     case CenterGravity:
794         *x -= (self->size.left + self->size.right) / 2;
795         break;
796
797     case NorthEastGravity:
798     case SouthEastGravity:
799     case EastGravity:
800         *x -= self->size.left + self->size.right;
801         break;
802
803     case ForgetGravity:
804     case StaticGravity:
805         *x -= self->size.left;
806         break;
807     }
808
809     /* vertical */
810     switch (self->client->gravity) {
811     default:
812     case NorthWestGravity:
813     case NorthEastGravity:
814     case NorthGravity:
815         break;
816
817     case CenterGravity:
818     case EastGravity:
819     case WestGravity:
820         *y -= (self->size.top + self->size.bottom) / 2;
821         break;
822
823     case SouthWestGravity:
824     case SouthEastGravity:
825     case SouthGravity:
826         *y -= self->size.top + self->size.bottom;
827         break;
828
829     case ForgetGravity:
830     case StaticGravity:
831         *y -= self->size.top;
832         break;
833     }
834 }
835
836 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
837 {
838     /* horizontal */
839     switch (self->client->gravity) {
840     default:
841     case NorthWestGravity:
842     case WestGravity:
843     case SouthWestGravity:
844         break;
845     case NorthGravity:
846     case CenterGravity:
847     case SouthGravity:
848         *x += (self->size.left + self->size.right) / 2;
849         break;
850     case NorthEastGravity:
851     case EastGravity:
852     case SouthEastGravity:
853         *x += self->size.left + self->size.right;
854         break;
855     case StaticGravity:
856     case ForgetGravity:
857         *x += self->size.left;
858         break;
859     }
860
861     /* vertical */
862     switch (self->client->gravity) {
863     default:
864     case NorthWestGravity:
865     case NorthGravity:
866     case NorthEastGravity:
867         break;
868     case WestGravity:
869     case CenterGravity:
870     case EastGravity:
871         *y += (self->size.top + self->size.bottom) / 2;
872         break;
873     case SouthWestGravity:
874     case SouthGravity:
875     case SouthEastGravity:
876         *y += self->size.top + self->size.bottom;
877         break;
878     case StaticGravity:
879     case ForgetGravity:
880         *y += self->size.top;
881         break;
882     }
883 }
884
885 static void flash_done(gpointer data)
886 {
887     ObFrame *self = data;
888
889     if (self->focused != self->flash_on)
890         frame_adjust_focus(self, self->focused);
891 }
892
893 static gboolean flash_timeout(gpointer data)
894 {
895     ObFrame *self = data;
896     GTimeVal now;
897
898     g_get_current_time(&now);
899     if (now.tv_sec > self->flash_end.tv_sec ||
900         (now.tv_sec == self->flash_end.tv_sec &&
901          now.tv_usec >= self->flash_end.tv_usec))
902         self->flashing = FALSE;
903
904     if (!self->flashing)
905         return FALSE; /* we are done */
906
907     self->flash_on = !self->flash_on;
908     if (!self->focused) {
909         frame_adjust_focus(self, self->flash_on);
910         self->focused = FALSE;
911     }
912
913     return TRUE; /* go again */
914 }
915
916 void frame_flash_start(ObFrame *self)
917 {
918     self->flash_on = self->focused;
919
920     if (!self->flashing)
921         ob_main_loop_timeout_add(ob_main_loop,
922                                  G_USEC_PER_SEC * 0.6,
923                                  flash_timeout,
924                                  self,
925                                  flash_done);
926     g_get_current_time(&self->flash_end);
927     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
928     
929     self->flashing = TRUE;
930 }
931
932 void frame_flash_stop(ObFrame *self)
933 {
934     self->flashing = FALSE;
935 }