frame can handle having no border between the titlebar and the client now
[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-2007   Dana 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_cycle.h"
29 #include "focus_cycle_indicator.h"
30 #include "moveresize.h"
31 #include "screen.h"
32 #include "render/theme.h"
33
34 #define PLATE_EVENTMASK (SubstructureRedirectMask | FocusChangeMask)
35 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
36                          ButtonPressMask | ButtonReleaseMask)
37 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
38                            ButtonMotionMask | PointerMotionMask | \
39                            EnterWindowMask | LeaveWindowMask)
40 /* The inner window does not need enter/leave events.
41    If it does get them, then it needs its own context for enter events
42    because sloppy focus will focus the window when you enter the inner window
43    from the frame. */
44 #define INNER_EVENTMASK (ButtonPressMask)
45
46 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
47 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (G_USEC_PER_SEC / 60) /* 60 Hz */
48
49 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_y)
50
51 static void flash_done(gpointer data);
52 static gboolean flash_timeout(gpointer data);
53
54 static void layout_title(ObFrame *self);
55 static void set_theme_statics(ObFrame *self);
56 static void free_theme_statics(ObFrame *self);
57 static gboolean frame_animate_iconify(gpointer self);
58
59 static Window createWindow(Window parent, Visual *visual,
60                            gulong mask, XSetWindowAttributes *attrib)
61 {
62     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
63                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
64                          (visual ? visual : RrVisual(ob_rr_inst)),
65                          mask, attrib);
66                        
67 }
68
69 static Visual *check_32bit_client(ObClient *c)
70 {
71     XWindowAttributes wattrib;
72     Status ret;
73
74     /* we're already running at 32 bit depth, yay. we don't need to use their
75        visual */
76     if (RrDepth(ob_rr_inst) == 32)
77         return NULL;
78
79     ret = XGetWindowAttributes(ob_display, c->window, &wattrib);
80     g_assert(ret != BadDrawable);
81     g_assert(ret != BadWindow);
82
83     if (wattrib.depth == 32)
84         return wattrib.visual;
85     return NULL;
86 }
87
88 ObFrame *frame_new(ObClient *client)
89 {
90     XSetWindowAttributes attrib;
91     gulong mask;
92     ObFrame *self;
93     Visual *visual;
94
95     self = g_new0(ObFrame, 1);
96     self->client = client;
97
98     visual = check_32bit_client(client);
99
100     /* create the non-visible decor windows */
101
102     mask = CWEventMask;
103     if (visual) {
104         /* client has a 32-bit visual */
105         mask |= CWColormap | CWBackPixel | CWBorderPixel;
106         /* create a colormap with the visual */
107         self->colormap = attrib.colormap =
108             XCreateColormap(ob_display,
109                             RootWindow(ob_display, ob_screen),
110                             visual, AllocNone);
111         attrib.background_pixel = BlackPixel(ob_display, ob_screen);
112         attrib.border_pixel = BlackPixel(ob_display, ob_screen);
113     }
114     attrib.event_mask = FRAME_EVENTMASK;
115     self->window = createWindow(RootWindow(ob_display, ob_screen), visual,
116                                 mask, &attrib);
117
118     attrib.event_mask = INNER_EVENTMASK;
119     self->inner = createWindow(self->window, visual, mask, &attrib);
120
121     mask &= ~CWEventMask;
122     self->plate = createWindow(self->inner, visual, mask, &attrib);
123
124     /* create the visible decor windows */
125
126     mask = CWEventMask;
127     if (visual) {
128         /* client has a 32-bit visual */
129         mask |= CWColormap | CWBackPixel | CWBorderPixel;
130         attrib.colormap = RrColormap(ob_rr_inst);
131     }
132     attrib.event_mask = ELEMENT_EVENTMASK;
133     self->title = createWindow(self->window, NULL, mask, &attrib);
134     self->titleleft = createWindow(self->window, NULL, mask, &attrib);
135     self->titletop = createWindow(self->window, NULL, mask, &attrib);
136     self->titletopleft = createWindow(self->window, NULL, mask, &attrib);
137     self->titletopright = createWindow(self->window, NULL, mask, &attrib);
138     self->titleright = createWindow(self->window, NULL, mask, &attrib);
139     self->titlebottom = createWindow(self->window, NULL, mask, &attrib);
140
141     self->topresize = createWindow(self->title, NULL, mask, &attrib);
142     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
143     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
144     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
145     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
146
147     self->left = createWindow(self->window, NULL, mask, &attrib);
148     self->right = createWindow(self->window, NULL, mask, &attrib);
149
150     self->label = createWindow(self->title, NULL, mask, &attrib);
151     self->max = createWindow(self->title, NULL, mask, &attrib);
152     self->close = createWindow(self->title, NULL, mask, &attrib);
153     self->desk = createWindow(self->title, NULL, mask, &attrib);
154     self->shade = createWindow(self->title, NULL, mask, &attrib);
155     self->icon = createWindow(self->title, NULL, mask, &attrib);
156     self->iconify = createWindow(self->title, NULL, mask, &attrib);
157
158     self->handle = createWindow(self->window, NULL, mask, &attrib);
159     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
160     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
161
162     self->handleleft = createWindow(self->handle, NULL, mask, &attrib);
163     self->handleright = createWindow(self->handle, NULL, mask, &attrib);
164
165     self->handletop = createWindow(self->window, NULL, mask, &attrib);
166     self->handlebottom = createWindow(self->window, NULL, mask, &attrib);
167     self->lgripleft = createWindow(self->window, NULL, mask, &attrib);
168     self->lgriptop = createWindow(self->window, NULL, mask, &attrib);
169     self->lgripbottom = createWindow(self->window, NULL, mask, &attrib);
170     self->rgripright = createWindow(self->window, NULL, mask, &attrib);
171     self->rgriptop = createWindow(self->window, NULL, mask, &attrib);
172     self->rgripbottom = createWindow(self->window, NULL, mask, &attrib);
173
174     self->focused = FALSE;
175
176     /* the other stuff is shown based on decor settings */
177     XMapWindow(ob_display, self->plate);
178     XMapWindow(ob_display, self->inner);
179     XMapWindow(ob_display, self->label);
180
181     self->max_press = self->close_press = self->desk_press = 
182         self->iconify_press = self->shade_press = FALSE;
183     self->max_hover = self->close_hover = self->desk_hover = 
184         self->iconify_hover = self->shade_hover = FALSE;
185
186     set_theme_statics(self);
187
188     return (ObFrame*)self;
189 }
190
191 static void set_theme_statics(ObFrame *self)
192 {
193     /* set colors/appearance/sizes for stuff that doesn't change */
194     XResizeWindow(ob_display, self->max,
195                   ob_rr_theme->button_size, ob_rr_theme->button_size);
196     XResizeWindow(ob_display, self->iconify,
197                   ob_rr_theme->button_size, ob_rr_theme->button_size);
198     XResizeWindow(ob_display, self->icon,
199                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
200     XResizeWindow(ob_display, self->close,
201                   ob_rr_theme->button_size, ob_rr_theme->button_size);
202     XResizeWindow(ob_display, self->desk,
203                   ob_rr_theme->button_size, ob_rr_theme->button_size);
204     XResizeWindow(ob_display, self->shade,
205                   ob_rr_theme->button_size, ob_rr_theme->button_size);
206     XResizeWindow(ob_display, self->tltresize,
207                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
208     XResizeWindow(ob_display, self->trtresize,
209                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
210     XResizeWindow(ob_display, self->tllresize,
211                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
212     XResizeWindow(ob_display, self->trrresize,
213                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
214
215     /* set up the dynamic appearances */
216     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
217     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
218     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
219     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
220     self->a_unfocused_handle =
221         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
222     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
223     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
224 }
225
226 static void free_theme_statics(ObFrame *self)
227 {
228     RrAppearanceFree(self->a_unfocused_title); 
229     RrAppearanceFree(self->a_focused_title);
230     RrAppearanceFree(self->a_unfocused_label);
231     RrAppearanceFree(self->a_focused_label);
232     RrAppearanceFree(self->a_unfocused_handle);
233     RrAppearanceFree(self->a_focused_handle);
234     RrAppearanceFree(self->a_icon);
235 }
236
237 void frame_free(ObFrame *self)
238 {
239     free_theme_statics(self);
240
241     XDestroyWindow(ob_display, self->window);
242     if (self->colormap)
243         XFreeColormap(ob_display, self->colormap);
244
245     g_free(self);
246 }
247
248 void frame_show(ObFrame *self)
249 {
250     if (!self->visible) {
251         self->visible = TRUE;
252         XMapWindow(ob_display, self->client->window);
253         XMapWindow(ob_display, self->window);
254     }
255 }
256
257 void frame_hide(ObFrame *self)
258 {
259     if (self->visible) {
260         self->visible = FALSE;
261         if (!frame_iconify_animating(self))
262             XUnmapWindow(ob_display, self->window);
263         /* we unmap the client itself so that we can get MapRequest
264            events, and because the ICCCM tells us to! */
265         XUnmapWindow(ob_display, self->client->window);
266         self->client->ignore_unmaps += 1;
267     }
268 }
269
270 void frame_adjust_theme(ObFrame *self)
271 {
272     free_theme_statics(self);
273     set_theme_statics(self);
274 }
275
276 void frame_adjust_shape(ObFrame *self)
277 {
278 #ifdef SHAPE
279     gint num;
280     XRectangle xrect[2];
281
282     if (!self->client->shaped) {
283         /* clear the shape on the frame window */
284         XShapeCombineMask(ob_display, self->window, ShapeBounding,
285                           self->size.left,
286                           self->size.top,
287                           None, ShapeSet);
288     } else {
289         /* make the frame's shape match the clients */
290         XShapeCombineShape(ob_display, self->window, ShapeBounding,
291                            self->size.left,
292                            self->size.top,
293                            self->client->window,
294                            ShapeBounding, ShapeSet);
295
296         num = 0;
297         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
298             xrect[0].x = -ob_rr_theme->fbwidth;
299             xrect[0].y = -ob_rr_theme->fbwidth;
300             xrect[0].width = self->width + self->bwidth * 2;
301             xrect[0].height = ob_rr_theme->title_height +
302                 self->bwidth * 2;
303             ++num;
304         }
305
306         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
307             xrect[1].x = -ob_rr_theme->fbwidth;
308             xrect[1].y = FRAME_HANDLE_Y(self);
309             xrect[1].width = self->width + self->bwidth * 2;
310             xrect[1].height = ob_rr_theme->handle_height +
311                 self->bwidth * 2;
312             ++num;
313         }
314
315         XShapeCombineRectangles(ob_display, self->window,
316                                 ShapeBounding, 0, 0, xrect, num,
317                                 ShapeUnion, Unsorted);
318     }
319 #endif
320 }
321
322 void frame_adjust_area(ObFrame *self, gboolean moved,
323                        gboolean resized, gboolean fake)
324 {
325     Strut oldsize;
326
327     oldsize = self->size;
328
329     if (resized) {
330         self->decorations = self->client->decorations;
331         self->max_horz = self->client->max_horz;
332
333         if (self->decorations & OB_FRAME_DECOR_BORDER) {
334             self->bwidth = ob_rr_theme->fbwidth;
335             self->cbwidth_x = ob_rr_theme->cbwidthx;
336             self->cbwidth_y = ob_rr_theme->cbwidthy;
337         } else {
338             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
339         }
340         self->rbwidth = self->bwidth;
341
342         if (self->max_horz)
343             self->cbwidth_x = 0;
344
345         self->width = self->client->area.width + self->cbwidth_x * 2;
346         self->width = MAX(self->width, 1); /* no lower than 1 */
347
348         STRUT_SET(self->size,
349                   self->cbwidth_x + self->bwidth,
350                   self->cbwidth_y + self->bwidth,
351                   self->cbwidth_x + self->bwidth,
352                   self->cbwidth_y + self->bwidth);
353
354         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
355             self->size.top += ob_rr_theme->title_height + self->rbwidth;
356         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
357             ob_rr_theme->handle_height > 0)
358         {
359             self->size.bottom += ob_rr_theme->handle_height + self->bwidth;
360         }
361   
362         /* position/size and map/unmap all the windows */
363
364         if (!fake) {
365             if (self->bwidth) {
366                 XMoveResizeWindow(ob_display, self->titletop,
367                                   ob_rr_theme->grip_width + self->bwidth, 0,
368                                   self->client->area.width +
369                                   self->cbwidth_x * 2 + self->bwidth * 2 -
370                                   (ob_rr_theme->grip_width + self->bwidth) * 2,
371                                   self->bwidth);
372                 XMoveResizeWindow(ob_display, self->titletopleft,
373                                   0, 0,
374                                   ob_rr_theme->grip_width + self->bwidth,
375                                   self->bwidth);
376                 XMoveResizeWindow(ob_display, self->titletopright,
377                                   self->client->area.width +
378                                   self->cbwidth_x * 2 + self->bwidth * 2 -
379                                   ob_rr_theme->grip_width - self->bwidth,
380                                   0,
381                                   ob_rr_theme->grip_width + self->bwidth,
382                                   self->bwidth);
383                 XMoveResizeWindow(ob_display, self->titleleft,
384                                   0, self->bwidth,
385                                   self->bwidth,
386                                   ob_rr_theme->grip_width);
387                 XMoveResizeWindow(ob_display, self->titleright,
388                                   self->client->area.width +
389                                   self->cbwidth_x * 2 + self->bwidth,
390                                   self->bwidth,
391                                   self->bwidth,
392                                   ob_rr_theme->grip_width);
393
394                 XMapWindow(ob_display, self->titletop);
395                 XMapWindow(ob_display, self->titletopleft);
396                 XMapWindow(ob_display, self->titletopright);
397                 XMapWindow(ob_display, self->titleleft);
398                 XMapWindow(ob_display, self->titleright);
399
400                 if (self->decorations & OB_FRAME_DECOR_TITLEBAR &&
401                     self->rbwidth)
402                 {
403                     XMoveResizeWindow(ob_display, self->titlebottom,
404                                       self->bwidth,
405                                       ob_rr_theme->title_height + self->bwidth,
406                                       self->client->area.width +
407                                       self->cbwidth_x * 2,
408                                       self->bwidth);
409
410                     XMapWindow(ob_display, self->titlebottom);
411                 } else
412                     XUnmapWindow(ob_display, self->titlebottom);
413             } else {
414                 XUnmapWindow(ob_display, self->titletop);
415                 XUnmapWindow(ob_display, self->titletopleft);
416                 XUnmapWindow(ob_display, self->titletopright);
417                 XUnmapWindow(ob_display, self->titleleft);
418                 XUnmapWindow(ob_display, self->titleright);
419             }
420
421             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
422                 XMoveResizeWindow(ob_display, self->title,
423                                   self->bwidth, self->bwidth,
424                                   self->width, ob_rr_theme->title_height);
425
426                 XMapWindow(ob_display, self->title);
427
428                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
429                     XMoveResizeWindow(ob_display, self->topresize,
430                                       ob_rr_theme->grip_width + self->bwidth,
431                                       0,
432                                       self->width - (ob_rr_theme->grip_width +
433                                                      self->bwidth) * 2,
434                                       ob_rr_theme->paddingy + 1);
435
436                     XMoveWindow(ob_display, self->tltresize, 0, 0);
437                     XMoveWindow(ob_display, self->tllresize, 0, 0);
438                     XMoveWindow(ob_display, self->trtresize,
439                                 self->width - ob_rr_theme->grip_width, 0);
440                     XMoveWindow(ob_display, self->trrresize,
441                                 self->width - ob_rr_theme->paddingx - 1, 0);
442
443                     XMapWindow(ob_display, self->topresize);
444                     XMapWindow(ob_display, self->tltresize);
445                     XMapWindow(ob_display, self->tllresize);
446                     XMapWindow(ob_display, self->trtresize);
447                     XMapWindow(ob_display, self->trrresize);
448                 } else {
449                     XUnmapWindow(ob_display, self->topresize);
450                     XUnmapWindow(ob_display, self->tltresize);
451                     XUnmapWindow(ob_display, self->tllresize);
452                     XUnmapWindow(ob_display, self->trtresize);
453                     XUnmapWindow(ob_display, self->trrresize);
454                 }
455             } else
456                 XUnmapWindow(ob_display, self->title);
457         }
458
459         if ((self->decorations & OB_FRAME_DECOR_TITLEBAR))
460             /* layout the title bar elements */
461             layout_title(self);
462
463         if (!fake) {
464             if (self->bwidth) {
465                 XMoveResizeWindow(ob_display, self->handlebottom,
466                                   ob_rr_theme->grip_width +
467                                   self->bwidth * 2,
468                                   self->size.top + self->client->area.height +
469                                   self->size.bottom - self->bwidth,
470                                   self->width - (ob_rr_theme->grip_width +
471                                                  self->bwidth) * 2,
472                                   self->bwidth);
473
474                 XMoveResizeWindow(ob_display, self->lgripleft,
475                                   0,
476                                   self->size.top + self->client->area.height +
477                                   self->size.bottom -
478                                   ob_rr_theme->grip_width,
479                                   self->bwidth,
480                                   ob_rr_theme->grip_width);
481                 XMoveResizeWindow(ob_display, self->rgripright,
482                                   self->size.left + self->client->area.width +
483                                   self->size.right - self->bwidth,
484                                   self->size.top + self->client->area.height +
485                                   self->size.bottom -
486                                   ob_rr_theme->grip_width,
487                                   self->bwidth,
488                                   ob_rr_theme->grip_width);
489
490                 XMoveResizeWindow(ob_display, self->lgripbottom,
491                                   self->bwidth,
492                                   self->size.top + self->client->area.height +
493                                   self->size.bottom - self->bwidth,
494                                   ob_rr_theme->grip_width + self->bwidth,
495                                   self->bwidth);
496                 XMoveResizeWindow(ob_display, self->rgripbottom,
497                                   self->size.left + self->client->area.width +
498                                   self->size.right - self->bwidth * 2 -
499                                   ob_rr_theme->grip_width,
500                                   self->size.top + self->client->area.height +
501                                   self->size.bottom - self->bwidth,
502                                   ob_rr_theme->grip_width + self->bwidth,
503                                   self->bwidth);
504
505                 XMapWindow(ob_display, self->handlebottom);
506                 XMapWindow(ob_display, self->lgripleft);
507                 XMapWindow(ob_display, self->rgripright);
508                 XMapWindow(ob_display, self->lgripbottom);
509                 XMapWindow(ob_display, self->rgripbottom);
510
511                 if (self->decorations & OB_FRAME_DECOR_HANDLE &&
512                     ob_rr_theme->handle_height > 0)
513                 {
514                     XMoveResizeWindow(ob_display, self->handletop,
515                                       ob_rr_theme->grip_width +
516                                       self->bwidth * 2,
517                                       FRAME_HANDLE_Y(self),
518                                       self->width - (ob_rr_theme->grip_width +
519                                                      self->bwidth) * 2,
520                                       self->bwidth);
521                     XMapWindow(ob_display, self->handletop);
522
523                     if (self->decorations & OB_FRAME_DECOR_GRIPS) {
524                         XMoveResizeWindow(ob_display, self->handleleft,
525                                           ob_rr_theme->grip_width,
526                                           0,
527                                           self->bwidth,
528                                           ob_rr_theme->handle_height);
529                         XMoveResizeWindow(ob_display, self->handleright,
530                                           self->width -
531                                           ob_rr_theme->grip_width -
532                                           self->bwidth,
533                                           0,
534                                           self->bwidth,
535                                           ob_rr_theme->handle_height);
536
537                         XMoveResizeWindow(ob_display, self->lgriptop,
538                                           self->bwidth,
539                                           FRAME_HANDLE_Y(self),
540                                           ob_rr_theme->grip_width +
541                                           self->bwidth,
542                                           self->bwidth);
543                         XMoveResizeWindow(ob_display, self->rgriptop,
544                                           self->size.left +
545                                           self->client->area.width +
546                                           self->size.right - self->bwidth * 2 -
547                                           ob_rr_theme->grip_width,
548                                           FRAME_HANDLE_Y(self),
549                                           ob_rr_theme->grip_width +
550                                           self->bwidth,
551                                           self->bwidth);
552
553                         XMapWindow(ob_display, self->handleleft);
554                         XMapWindow(ob_display, self->handleright);
555                         XMapWindow(ob_display, self->lgriptop);
556                         XMapWindow(ob_display, self->rgriptop);
557                     } else {
558                         XUnmapWindow(ob_display, self->handleleft);
559                         XUnmapWindow(ob_display, self->handleright);
560                         XUnmapWindow(ob_display, self->lgriptop);
561                         XUnmapWindow(ob_display, self->rgriptop);
562                     }
563                 } else
564                     XUnmapWindow(ob_display, self->handletop);
565             } else {
566                 XUnmapWindow(ob_display, self->handlebottom);
567                 XUnmapWindow(ob_display, self->lgripleft);
568                 XUnmapWindow(ob_display, self->rgripright);
569                 XUnmapWindow(ob_display, self->lgripbottom);
570                 XUnmapWindow(ob_display, self->rgripbottom);
571             }
572
573             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
574                 ob_rr_theme->handle_height > 0)
575             {
576                 XMoveResizeWindow(ob_display, self->handle,
577                                   self->bwidth,
578                                   FRAME_HANDLE_Y(self) + self->bwidth,
579                                   self->width, ob_rr_theme->handle_height);
580                 XMapWindow(ob_display, self->handle);
581
582                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
583                     XMoveResizeWindow(ob_display, self->lgrip,
584                                       0, 0,
585                                       ob_rr_theme->grip_width,
586                                       ob_rr_theme->handle_height);
587                     XMoveResizeWindow(ob_display, self->rgrip,
588                                       self->width - ob_rr_theme->grip_width,
589                                       0,
590                                       ob_rr_theme->grip_width,
591                                       ob_rr_theme->handle_height);
592
593                     XMapWindow(ob_display, self->lgrip);
594                     XMapWindow(ob_display, self->rgrip);
595                 } else {
596                     XUnmapWindow(ob_display, self->lgrip);
597                     XUnmapWindow(ob_display, self->rgrip);
598                 }
599             } else
600                 XUnmapWindow(ob_display, self->handle);
601
602             if (self->bwidth && !self->max_horz) {
603                 XMoveResizeWindow(ob_display, self->left,
604                                   0,
605                                   self->bwidth + ob_rr_theme->grip_width,
606                                   self->bwidth,
607                                   self->client->area.height +
608                                   self->size.top + self->size.bottom -
609                                   ob_rr_theme->grip_width * 2);
610                 XMoveResizeWindow(ob_display, self->right,
611                                   self->client->area.width +
612                                   self->cbwidth_x * 2 + self->bwidth,
613                                   self->bwidth + ob_rr_theme->grip_width,
614                                   self->bwidth,
615                                   self->client->area.height +
616                                   self->size.top + self->size.bottom -
617                                   ob_rr_theme->grip_width * 2);
618
619                 XMapWindow(ob_display, self->left);
620                 XMapWindow(ob_display, self->right);
621             } else {
622                 XUnmapWindow(ob_display, self->left);
623                 XUnmapWindow(ob_display, self->right);
624             }
625
626             /* move and resize the inner border window which contains the plate
627              */
628             XMoveResizeWindow(ob_display, self->inner,
629                               0,
630                               self->size.top - self->cbwidth_y,
631                               self->client->area.width +
632                               self->cbwidth_x * 2 + self->bwidth * 2,
633                               self->client->area.height +
634                               self->cbwidth_y * 2);
635
636             /* move the plate */
637             XMoveWindow(ob_display, self->plate,
638                         self->bwidth + self->cbwidth_x, self->cbwidth_y);
639
640             /* when the client has StaticGravity, it likes to move around. */
641             XMoveWindow(ob_display, self->client->window, 0, 0);
642         }
643     }
644
645     /* shading can change without being moved or resized */
646     RECT_SET_SIZE(self->area,
647                   self->client->area.width +
648                   self->size.left + self->size.right,
649                   (self->client->shaded ?
650                    ob_rr_theme->title_height + self->bwidth * 2:
651                    self->client->area.height +
652                    self->size.top + self->size.bottom));
653
654     if (moved || resized) {
655         /* find the new coordinates, done after setting the frame.size, for
656            frame_client_gravity. */
657         self->area.x = self->client->area.x;
658         self->area.y = self->client->area.y;
659         frame_client_gravity(self, &self->area.x, &self->area.y,
660                              self->client->area.width,
661                              self->client->area.height);
662     }
663
664     if (!fake) {
665         if (!frame_iconify_animating(self))
666             /* move and resize the top level frame.
667                shading can change without being moved or resized.
668                
669                but don't do this during an iconify animation. it will be
670                reflected afterwards.
671             */
672             XMoveResizeWindow(ob_display, self->window,
673                               self->area.x,
674                               self->area.y,
675                               self->area.width,
676                               self->area.height);
677
678         if (resized) {
679             framerender_frame(self);
680             frame_adjust_shape(self);
681         }
682
683         if (!STRUT_EQUAL(self->size, oldsize)) {
684             gulong vals[4];
685             vals[0] = self->size.left;
686             vals[1] = self->size.right;
687             vals[2] = self->size.top;
688             vals[3] = self->size.bottom;
689             PROP_SETA32(self->client->window, net_frame_extents,
690                         cardinal, vals, 4);
691             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
692                         cardinal, vals, 4);
693         }
694
695         /* if this occurs while we are focus cycling, the indicator needs to
696            match the changes */
697         if (focus_cycle_target == self->client)
698             focus_cycle_draw_indicator(self->client);
699     }
700     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
701         XResizeWindow(ob_display, self->label, self->label_width,
702                       ob_rr_theme->label_height);
703
704     /* set up cursors */
705     if (!fake &&
706         (self->functions & OB_CLIENT_FUNC_RESIZE) !=
707         (self->client->functions & OB_CLIENT_FUNC_RESIZE))
708     {
709         gboolean r = self->client->functions & OB_CLIENT_FUNC_RESIZE;
710         XSetWindowAttributes a;
711
712         a.cursor = ob_cursor(r ? OB_CURSOR_NORTH : OB_CURSOR_NONE);
713         XChangeWindowAttributes(ob_display, self->topresize, CWCursor, &a);
714         XChangeWindowAttributes(ob_display, self->titletop, CWCursor, &a);
715         a.cursor = ob_cursor(r ? OB_CURSOR_NORTHWEST : OB_CURSOR_NONE);
716         XChangeWindowAttributes(ob_display, self->tltresize, CWCursor, &a);
717         XChangeWindowAttributes(ob_display, self->tllresize, CWCursor, &a);
718         XChangeWindowAttributes(ob_display, self->titletopleft, CWCursor, &a);
719         XChangeWindowAttributes(ob_display, self->titleleft, CWCursor, &a);
720         a.cursor = ob_cursor(r ? OB_CURSOR_NORTHEAST : OB_CURSOR_NONE);
721         XChangeWindowAttributes(ob_display, self->trtresize, CWCursor, &a);
722         XChangeWindowAttributes(ob_display, self->trrresize, CWCursor, &a);
723         XChangeWindowAttributes(ob_display, self->titletopright, CWCursor, &a);
724         XChangeWindowAttributes(ob_display, self->titleright, CWCursor, &a);
725         a.cursor = ob_cursor(r ? OB_CURSOR_WEST : OB_CURSOR_NONE);
726         XChangeWindowAttributes(ob_display, self->left, CWCursor, &a);
727         a.cursor = ob_cursor(r ? OB_CURSOR_EAST : OB_CURSOR_NONE);
728         XChangeWindowAttributes(ob_display, self->right, CWCursor, &a);
729         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTH : OB_CURSOR_NONE);
730         XChangeWindowAttributes(ob_display, self->handle, CWCursor, &a);
731         XChangeWindowAttributes(ob_display, self->handletop, CWCursor, &a);
732         XChangeWindowAttributes(ob_display, self->handlebottom, CWCursor, &a);
733         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHWEST : OB_CURSOR_NONE);
734         XChangeWindowAttributes(ob_display, self->lgrip, CWCursor, &a);
735         XChangeWindowAttributes(ob_display, self->handleleft, CWCursor, &a);
736         XChangeWindowAttributes(ob_display, self->lgripleft, CWCursor, &a);
737         XChangeWindowAttributes(ob_display, self->lgriptop, CWCursor, &a);
738         XChangeWindowAttributes(ob_display, self->lgripbottom, CWCursor, &a);
739         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHEAST : OB_CURSOR_NONE);
740         XChangeWindowAttributes(ob_display, self->rgrip, CWCursor, &a);
741         XChangeWindowAttributes(ob_display, self->handleright, CWCursor, &a);
742         XChangeWindowAttributes(ob_display, self->rgripright, CWCursor, &a);
743         XChangeWindowAttributes(ob_display, self->rgriptop, CWCursor, &a);
744         XChangeWindowAttributes(ob_display, self->rgripbottom, CWCursor, &a);
745
746         self->functions = self->client->functions;
747     }
748 }
749
750 void frame_adjust_client_area(ObFrame *self)
751 {
752     /* resize the plate */
753     XResizeWindow(ob_display, self->plate,
754                   self->client->area.width, self->client->area.height);
755 }
756
757 void frame_adjust_state(ObFrame *self)
758 {
759     framerender_frame(self);
760 }
761
762 void frame_adjust_focus(ObFrame *self, gboolean hilite)
763 {
764     self->focused = hilite;
765     framerender_frame(self);
766     XFlush(ob_display);
767 }
768
769 void frame_adjust_title(ObFrame *self)
770 {
771     framerender_frame(self);
772 }
773
774 void frame_adjust_icon(ObFrame *self)
775 {
776     framerender_frame(self);
777 }
778
779 void frame_grab_client(ObFrame *self)
780 {
781     /* reparent the client to the frame */
782     XReparentWindow(ob_display, self->client->window, self->plate, 0, 0);
783
784     /*
785       When reparenting the client window, it is usually not mapped yet, since
786       this occurs from a MapRequest. However, in the case where Openbox is
787       starting up, the window is already mapped, so we'll see unmap events for
788       it. There are 2 unmap events generated that we see, one with the 'event'
789       member set the root window, and one set to the client, but both get
790       handled and need to be ignored.
791     */
792     if (ob_state() == OB_STATE_STARTING)
793         self->client->ignore_unmaps += 2;
794
795     /* select the event mask on the client's parent (to receive config/map
796        req's) the ButtonPress is to catch clicks on the client border */
797     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
798
799     /* map the client so it maps when the frame does */
800     XMapWindow(ob_display, self->client->window);
801
802     /* set all the windows for the frame in the window_map */
803     g_hash_table_insert(window_map, &self->window, self->client);
804     g_hash_table_insert(window_map, &self->plate, self->client);
805     g_hash_table_insert(window_map, &self->inner, self->client);
806     g_hash_table_insert(window_map, &self->title, self->client);
807     g_hash_table_insert(window_map, &self->label, self->client);
808     g_hash_table_insert(window_map, &self->max, self->client);
809     g_hash_table_insert(window_map, &self->close, self->client);
810     g_hash_table_insert(window_map, &self->desk, self->client);
811     g_hash_table_insert(window_map, &self->shade, self->client);
812     g_hash_table_insert(window_map, &self->icon, self->client);
813     g_hash_table_insert(window_map, &self->iconify, self->client);
814     g_hash_table_insert(window_map, &self->handle, self->client);
815     g_hash_table_insert(window_map, &self->lgrip, self->client);
816     g_hash_table_insert(window_map, &self->rgrip, self->client);
817     g_hash_table_insert(window_map, &self->topresize, self->client);
818     g_hash_table_insert(window_map, &self->tltresize, self->client);
819     g_hash_table_insert(window_map, &self->tllresize, self->client);
820     g_hash_table_insert(window_map, &self->trtresize, self->client);
821     g_hash_table_insert(window_map, &self->trrresize, self->client);
822     g_hash_table_insert(window_map, &self->left, self->client);
823     g_hash_table_insert(window_map, &self->right, self->client);
824     g_hash_table_insert(window_map, &self->titleleft, self->client);
825     g_hash_table_insert(window_map, &self->titletop, self->client);
826     g_hash_table_insert(window_map, &self->titletopleft, self->client);
827     g_hash_table_insert(window_map, &self->titletopright, self->client);
828     g_hash_table_insert(window_map, &self->titleright, self->client);
829     g_hash_table_insert(window_map, &self->titlebottom, self->client);
830     g_hash_table_insert(window_map, &self->handleleft, self->client);
831     g_hash_table_insert(window_map, &self->handletop, self->client);
832     g_hash_table_insert(window_map, &self->handleright, self->client);
833     g_hash_table_insert(window_map, &self->handlebottom, self->client);
834     g_hash_table_insert(window_map, &self->lgripleft, self->client);
835     g_hash_table_insert(window_map, &self->lgriptop, self->client);
836     g_hash_table_insert(window_map, &self->lgripbottom, self->client);
837     g_hash_table_insert(window_map, &self->rgripright, self->client);
838     g_hash_table_insert(window_map, &self->rgriptop, self->client);
839     g_hash_table_insert(window_map, &self->rgripbottom, self->client);
840 }
841
842 void frame_release_client(ObFrame *self)
843 {
844     XEvent ev;
845     gboolean reparent = TRUE;
846
847     /* if there was any animation going on, kill it */
848     ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
849                                      self, FALSE);
850
851     /* check if the app has already reparented its window away */
852     while (XCheckTypedWindowEvent(ob_display, self->client->window,
853                                   ReparentNotify, &ev))
854     {
855         /* This check makes sure we don't catch our own reparent action to
856            our frame window. This doesn't count as the app reparenting itself
857            away of course.
858
859            Reparent events that are generated by us are just discarded here.
860            They are of no consequence to us anyhow.
861         */
862         if (ev.xreparent.parent != self->plate) {
863             reparent = FALSE;
864             XPutBackEvent(ob_display, &ev);
865             break;
866         }
867     }
868
869     if (reparent) {
870         /* according to the ICCCM - if the client doesn't reparent itself,
871            then we will reparent the window to root for them */
872         XReparentWindow(ob_display, self->client->window,
873                         RootWindow(ob_display, ob_screen),
874                         self->client->area.x,
875                         self->client->area.y);
876     }
877
878     /* remove all the windows for the frame from the window_map */
879     g_hash_table_remove(window_map, &self->window);
880     g_hash_table_remove(window_map, &self->plate);
881     g_hash_table_remove(window_map, &self->inner);
882     g_hash_table_remove(window_map, &self->title);
883     g_hash_table_remove(window_map, &self->label);
884     g_hash_table_remove(window_map, &self->max);
885     g_hash_table_remove(window_map, &self->close);
886     g_hash_table_remove(window_map, &self->desk);
887     g_hash_table_remove(window_map, &self->shade);
888     g_hash_table_remove(window_map, &self->icon);
889     g_hash_table_remove(window_map, &self->iconify);
890     g_hash_table_remove(window_map, &self->handle);
891     g_hash_table_remove(window_map, &self->lgrip);
892     g_hash_table_remove(window_map, &self->rgrip);
893     g_hash_table_remove(window_map, &self->topresize);
894     g_hash_table_remove(window_map, &self->tltresize);
895     g_hash_table_remove(window_map, &self->tllresize);
896     g_hash_table_remove(window_map, &self->trtresize);
897     g_hash_table_remove(window_map, &self->trrresize);
898     g_hash_table_remove(window_map, &self->left);
899     g_hash_table_remove(window_map, &self->right);
900     g_hash_table_remove(window_map, &self->titleleft);
901     g_hash_table_remove(window_map, &self->titletop);
902     g_hash_table_remove(window_map, &self->titletopleft);
903     g_hash_table_remove(window_map, &self->titletopright);
904     g_hash_table_remove(window_map, &self->titleright);
905     g_hash_table_remove(window_map, &self->titlebottom);
906     g_hash_table_remove(window_map, &self->handleleft);
907     g_hash_table_remove(window_map, &self->handletop);
908     g_hash_table_remove(window_map, &self->handleright);
909     g_hash_table_remove(window_map, &self->handlebottom);
910     g_hash_table_remove(window_map, &self->lgripleft);
911     g_hash_table_remove(window_map, &self->lgriptop);
912     g_hash_table_remove(window_map, &self->lgripbottom);
913     g_hash_table_remove(window_map, &self->rgripright);
914     g_hash_table_remove(window_map, &self->rgriptop);
915     g_hash_table_remove(window_map, &self->rgripbottom);
916
917     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
918 }
919
920 /* is there anything present between us and the label? */
921 static gboolean is_button_present(ObFrame *self, const gchar *lc, gint dir) {
922     for (; *lc != '\0' && lc >= config_title_layout; lc += dir) {
923         if (*lc == ' ') continue; /* it was invalid */
924         if (*lc == 'N' && self->decorations & OB_FRAME_DECOR_ICON)
925             return TRUE;
926         if (*lc == 'D' && self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
927             return TRUE;
928         if (*lc == 'S' && self->decorations & OB_FRAME_DECOR_SHADE)
929             return TRUE;
930         if (*lc == 'I' && self->decorations & OB_FRAME_DECOR_ICONIFY)
931             return TRUE;
932         if (*lc == 'M' && self->decorations & OB_FRAME_DECOR_MAXIMIZE)
933             return TRUE;
934         if (*lc == 'C' && self->decorations & OB_FRAME_DECOR_CLOSE)
935             return TRUE;
936         if (*lc == 'L') return FALSE;
937     }
938     return FALSE;
939 }
940
941 static void layout_title(ObFrame *self)
942 {
943     gchar *lc;
944     gint i;
945
946     const gint bwidth = ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
947     /* position of the left most button */
948     const gint left = ob_rr_theme->paddingx + 1;
949     /* position of the right most button */
950     const gint right = self->width - bwidth;
951
952     /* turn them all off */
953     self->icon_on = self->desk_on = self->shade_on = self->iconify_on =
954         self->max_on = self->close_on = self->label_on = FALSE;
955     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
956     self->leftmost = self->rightmost = OB_FRAME_CONTEXT_NONE;
957
958     /* figure out what's being show, find each element's position, and the
959        width of the label
960
961        do the ones before the label, then after the label,
962        i will be +1 the first time through when working to the left,
963        and -1 the second time through when working to the right */
964     for (i = 1; i >= -1; i-=2) {
965         gint x;
966         ObFrameContext *firstcon;
967
968         if (i > 0) {
969             x = left;
970             lc = config_title_layout;
971             firstcon = &self->leftmost;
972         } else {
973             x = right;
974             lc = config_title_layout + strlen(config_title_layout)-1;
975             firstcon = &self->rightmost;
976         }
977
978         /* stop at the end of the string (or the label, which calls break) */
979         for (; *lc != '\0' && lc >= config_title_layout; lc+=i) {
980             if (*lc == 'L') {
981                 if (i > 0) {
982                     self->label_on = TRUE;
983                     self->label_x = x;
984                 }
985                 break; /* break the for loop, do other side of label */
986             } else if (*lc == 'N') {
987                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICON;
988                 if ((self->icon_on = is_button_present(self, lc, i))) {
989                     /* icon is bigger than buttons */
990                     self->label_width -= bwidth + 2;
991                     self->icon_x = x;
992                     x += i * (bwidth + 2);
993                 }
994             } else if (*lc == 'D') {
995                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ALLDESKTOPS;
996                 if ((self->desk_on = is_button_present(self, lc, i))) {
997                     self->label_width -= bwidth;
998                     self->desk_x = x;
999                     x += i * bwidth;
1000                 }
1001             } else if (*lc == 'S') {
1002                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_SHADE;
1003                 if ((self->shade_on = is_button_present(self, lc, i))) {
1004                     self->label_width -= bwidth;
1005                     self->shade_x = x;
1006                     x += i * bwidth;
1007                 }
1008             } else if (*lc == 'I') {
1009                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_ICONIFY;
1010                 if ((self->iconify_on = is_button_present(self, lc, i))) {
1011                     self->label_width -= bwidth;
1012                     self->iconify_x = x;
1013                     x += i * bwidth;
1014                 }
1015             } else if (*lc == 'M') {
1016                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_MAXIMIZE;
1017                 if ((self->max_on = is_button_present(self, lc, i))) {
1018                     self->label_width -= bwidth;
1019                     self->max_x = x;
1020                     x += i * bwidth;
1021                 }
1022             } else if (*lc == 'C') {
1023                 if (firstcon) *firstcon = OB_FRAME_CONTEXT_CLOSE;
1024                 if ((self->close_on = is_button_present(self, lc, i))) {
1025                     self->label_width -= bwidth;
1026                     self->close_x = x;
1027                     x += i * bwidth;
1028                 }
1029             } else
1030                 continue; /* don't set firstcon */
1031             firstcon = NULL;
1032         }
1033     }
1034
1035     /* position and map the elements */
1036     if (self->icon_on) {
1037         XMapWindow(ob_display, self->icon);
1038         XMoveWindow(ob_display, self->icon, self->icon_x,
1039                     ob_rr_theme->paddingy);
1040     } else
1041         XUnmapWindow(ob_display, self->icon);
1042
1043     if (self->desk_on) {
1044         XMapWindow(ob_display, self->desk);
1045         XMoveWindow(ob_display, self->desk, self->desk_x,
1046                     ob_rr_theme->paddingy + 1);
1047     } else
1048         XUnmapWindow(ob_display, self->desk);
1049
1050     if (self->shade_on) {
1051         XMapWindow(ob_display, self->shade);
1052         XMoveWindow(ob_display, self->shade, self->shade_x,
1053                     ob_rr_theme->paddingy + 1);
1054     } else
1055         XUnmapWindow(ob_display, self->shade);
1056
1057     if (self->iconify_on) {
1058         XMapWindow(ob_display, self->iconify);
1059         XMoveWindow(ob_display, self->iconify, self->iconify_x,
1060                     ob_rr_theme->paddingy + 1);
1061     } else
1062         XUnmapWindow(ob_display, self->iconify);
1063
1064     if (self->max_on) {
1065         XMapWindow(ob_display, self->max);
1066         XMoveWindow(ob_display, self->max, self->max_x,
1067                     ob_rr_theme->paddingy + 1);
1068     } else
1069         XUnmapWindow(ob_display, self->max);
1070
1071     if (self->close_on) {
1072         XMapWindow(ob_display, self->close);
1073         XMoveWindow(ob_display, self->close, self->close_x,
1074                     ob_rr_theme->paddingy + 1);
1075     } else
1076         XUnmapWindow(ob_display, self->close);
1077
1078     if (self->label_on) {
1079         self->label_width = MAX(1, self->label_width); /* no lower than 1 */
1080         XMapWindow(ob_display, self->label);
1081         XMoveWindow(ob_display, self->label, self->label_x,
1082                     ob_rr_theme->paddingy);
1083     } else
1084         XUnmapWindow(ob_display, self->label);
1085 }
1086
1087 ObFrameContext frame_context_from_string(const gchar *name)
1088 {
1089     if (!g_ascii_strcasecmp("Desktop", name))
1090         return OB_FRAME_CONTEXT_DESKTOP;
1091     else if (!g_ascii_strcasecmp("Root", name))
1092         return OB_FRAME_CONTEXT_ROOT;
1093     else if (!g_ascii_strcasecmp("Client", name))
1094         return OB_FRAME_CONTEXT_CLIENT;
1095     else if (!g_ascii_strcasecmp("Titlebar", name))
1096         return OB_FRAME_CONTEXT_TITLEBAR;
1097     else if (!g_ascii_strcasecmp("Frame", name))
1098         return OB_FRAME_CONTEXT_FRAME;
1099     else if (!g_ascii_strcasecmp("TLCorner", name))
1100         return OB_FRAME_CONTEXT_TLCORNER;
1101     else if (!g_ascii_strcasecmp("TRCorner", name))
1102         return OB_FRAME_CONTEXT_TRCORNER;
1103     else if (!g_ascii_strcasecmp("BLCorner", name))
1104         return OB_FRAME_CONTEXT_BLCORNER;
1105     else if (!g_ascii_strcasecmp("BRCorner", name))
1106         return OB_FRAME_CONTEXT_BRCORNER;
1107     else if (!g_ascii_strcasecmp("Top", name))
1108         return OB_FRAME_CONTEXT_TOP;
1109     else if (!g_ascii_strcasecmp("Bottom", name))
1110         return OB_FRAME_CONTEXT_BOTTOM;
1111     else if (!g_ascii_strcasecmp("Left", name))
1112         return OB_FRAME_CONTEXT_LEFT;
1113     else if (!g_ascii_strcasecmp("Right", name))
1114         return OB_FRAME_CONTEXT_RIGHT;
1115     else if (!g_ascii_strcasecmp("Maximize", name))
1116         return OB_FRAME_CONTEXT_MAXIMIZE;
1117     else if (!g_ascii_strcasecmp("AllDesktops", name))
1118         return OB_FRAME_CONTEXT_ALLDESKTOPS;
1119     else if (!g_ascii_strcasecmp("Shade", name))
1120         return OB_FRAME_CONTEXT_SHADE;
1121     else if (!g_ascii_strcasecmp("Iconify", name))
1122         return OB_FRAME_CONTEXT_ICONIFY;
1123     else if (!g_ascii_strcasecmp("Icon", name))
1124         return OB_FRAME_CONTEXT_ICON;
1125     else if (!g_ascii_strcasecmp("Close", name))
1126         return OB_FRAME_CONTEXT_CLOSE;
1127     else if (!g_ascii_strcasecmp("MoveResize", name))
1128         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1129     return OB_FRAME_CONTEXT_NONE;
1130 }
1131
1132 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
1133 {
1134     ObFrame *self;
1135
1136     if (moveresize_in_progress)
1137         return OB_FRAME_CONTEXT_MOVE_RESIZE;
1138
1139     if (win == RootWindow(ob_display, ob_screen))
1140         return OB_FRAME_CONTEXT_ROOT ;
1141     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
1142     if (win == client->window) {
1143         /* conceptually, this is the desktop, as far as users are
1144            concerned */
1145         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1146             return OB_FRAME_CONTEXT_DESKTOP;
1147         return OB_FRAME_CONTEXT_CLIENT;
1148     }
1149
1150     self = client->frame;
1151     if (win == self->inner || win == self->plate) {
1152         /* conceptually, this is the desktop, as far as users are
1153            concerned */
1154         if (client->type == OB_CLIENT_TYPE_DESKTOP)
1155             return OB_FRAME_CONTEXT_DESKTOP;
1156         return OB_FRAME_CONTEXT_CLIENT;
1157     }
1158
1159     if (win == self->title) {
1160         /* when the user clicks in the corners of the titlebar and the client
1161            is fully maximized, then treat it like they clicked in the
1162            button that is there */
1163         if (self->client->max_horz && self->client->max_vert &&
1164             y < ob_rr_theme->paddingy + 1 + ob_rr_theme->button_size)
1165         {
1166             if (x < ((ob_rr_theme->paddingx + 1) * 2 +
1167                      ob_rr_theme->button_size)) {
1168                 if (self->leftmost != OB_FRAME_CONTEXT_NONE)
1169                     return self->leftmost;
1170             }
1171             else if (x > (self->width -
1172                           (ob_rr_theme->paddingx + 1 +
1173                            ob_rr_theme->button_size)))
1174             {
1175                 if (self->rightmost != OB_FRAME_CONTEXT_NONE)
1176                     return self->rightmost;
1177             }
1178         }
1179         return OB_FRAME_CONTEXT_TITLEBAR;
1180     }
1181
1182     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
1183     if (win == self->label)             return OB_FRAME_CONTEXT_TITLEBAR;
1184     if (win == self->handle)            return OB_FRAME_CONTEXT_BOTTOM;
1185     if (win == self->handletop)         return OB_FRAME_CONTEXT_BOTTOM;
1186     if (win == self->handlebottom)      return OB_FRAME_CONTEXT_BOTTOM;
1187     if (win == self->handleleft)        return OB_FRAME_CONTEXT_BLCORNER;
1188     if (win == self->lgrip)             return OB_FRAME_CONTEXT_BLCORNER;
1189     if (win == self->lgripleft)         return OB_FRAME_CONTEXT_BLCORNER;
1190     if (win == self->lgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1191     if (win == self->lgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1192     if (win == self->handleright)       return OB_FRAME_CONTEXT_BRCORNER;
1193     if (win == self->rgrip)             return OB_FRAME_CONTEXT_BRCORNER;
1194     if (win == self->rgripright)        return OB_FRAME_CONTEXT_BLCORNER;
1195     if (win == self->rgriptop)          return OB_FRAME_CONTEXT_BLCORNER;
1196     if (win == self->rgripbottom)       return OB_FRAME_CONTEXT_BLCORNER;
1197     if (win == self->titletop)          return OB_FRAME_CONTEXT_TOP;
1198     if (win == self->topresize)         return OB_FRAME_CONTEXT_TOP;
1199     if (win == self->tltresize)         return OB_FRAME_CONTEXT_TLCORNER;
1200     if (win == self->tllresize)         return OB_FRAME_CONTEXT_TLCORNER;
1201     if (win == self->titleleft)         return OB_FRAME_CONTEXT_TLCORNER;
1202     if (win == self->titletopleft)      return OB_FRAME_CONTEXT_TLCORNER;
1203     if (win == self->trtresize)         return OB_FRAME_CONTEXT_TRCORNER;
1204     if (win == self->trrresize)         return OB_FRAME_CONTEXT_TRCORNER;
1205     if (win == self->titleright)        return OB_FRAME_CONTEXT_TRCORNER;
1206     if (win == self->titletopright)     return OB_FRAME_CONTEXT_TRCORNER;
1207     if (win == self->left)              return OB_FRAME_CONTEXT_LEFT;
1208     if (win == self->right)             return OB_FRAME_CONTEXT_RIGHT;
1209     if (win == self->max)               return OB_FRAME_CONTEXT_MAXIMIZE;
1210     if (win == self->iconify)           return OB_FRAME_CONTEXT_ICONIFY;
1211     if (win == self->close)             return OB_FRAME_CONTEXT_CLOSE;
1212     if (win == self->icon)              return OB_FRAME_CONTEXT_ICON;
1213     if (win == self->desk)              return OB_FRAME_CONTEXT_ALLDESKTOPS;
1214     if (win == self->shade)             return OB_FRAME_CONTEXT_SHADE;
1215
1216     return OB_FRAME_CONTEXT_NONE;
1217 }
1218
1219 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
1220 {
1221     /* horizontal */
1222     switch (self->client->gravity) {
1223     default:
1224     case NorthWestGravity:
1225     case SouthWestGravity:
1226     case WestGravity:
1227         break;
1228
1229     case NorthGravity:
1230     case SouthGravity:
1231     case CenterGravity:
1232         *x -= (self->size.left + w) / 2;
1233         break;
1234
1235     case NorthEastGravity:
1236     case SouthEastGravity:
1237     case EastGravity:
1238         *x -= (self->size.left + self->size.right + w) - 1;
1239         break;
1240
1241     case ForgetGravity:
1242     case StaticGravity:
1243         *x -= self->size.left;
1244         break;
1245     }
1246
1247     /* vertical */
1248     switch (self->client->gravity) {
1249     default:
1250     case NorthWestGravity:
1251     case NorthEastGravity:
1252     case NorthGravity:
1253         break;
1254
1255     case CenterGravity:
1256     case EastGravity:
1257     case WestGravity:
1258         *y -= (self->size.top + h) / 2;
1259         break;
1260
1261     case SouthWestGravity:
1262     case SouthEastGravity:
1263     case SouthGravity:
1264         *y -= (self->size.top + self->size.bottom + h) - 1;
1265         break;
1266
1267     case ForgetGravity:
1268     case StaticGravity:
1269         *y -= self->size.top;
1270         break;
1271     }
1272 }
1273
1274 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
1275 {
1276     /* horizontal */
1277     switch (self->client->gravity) {
1278     default:
1279     case NorthWestGravity:
1280     case WestGravity:
1281     case SouthWestGravity:
1282         break;
1283     case NorthGravity:
1284     case CenterGravity:
1285     case SouthGravity:
1286         *x += (self->size.left + w) / 2;
1287         break;
1288     case NorthEastGravity:
1289     case EastGravity:
1290     case SouthEastGravity:
1291         *x += (self->size.left + self->size.right + w) - 1;
1292         break;
1293     case StaticGravity:
1294     case ForgetGravity:
1295         *x += self->size.left;
1296         break;
1297     }
1298
1299     /* vertical */
1300     switch (self->client->gravity) {
1301     default:
1302     case NorthWestGravity:
1303     case NorthGravity:
1304     case NorthEastGravity:
1305         break;
1306     case WestGravity:
1307     case CenterGravity:
1308     case EastGravity:
1309         *y += (self->size.top + h) / 2;
1310         break;
1311     case SouthWestGravity:
1312     case SouthGravity:
1313     case SouthEastGravity:
1314         *y += (self->size.top + self->size.bottom + h) - 1;
1315         break;
1316     case StaticGravity:
1317     case ForgetGravity:
1318         *y += self->size.top;
1319         break;
1320     }
1321 }
1322
1323 static void flash_done(gpointer data)
1324 {
1325     ObFrame *self = data;
1326
1327     if (self->focused != self->flash_on)
1328         frame_adjust_focus(self, self->focused);
1329 }
1330
1331 static gboolean flash_timeout(gpointer data)
1332 {
1333     ObFrame *self = data;
1334     GTimeVal now;
1335
1336     g_get_current_time(&now);
1337     if (now.tv_sec > self->flash_end.tv_sec ||
1338         (now.tv_sec == self->flash_end.tv_sec &&
1339          now.tv_usec >= self->flash_end.tv_usec))
1340         self->flashing = FALSE;
1341
1342     if (!self->flashing)
1343         return FALSE; /* we are done */
1344
1345     self->flash_on = !self->flash_on;
1346     if (!self->focused) {
1347         frame_adjust_focus(self, self->flash_on);
1348         self->focused = FALSE;
1349     }
1350
1351     return TRUE; /* go again */
1352 }
1353
1354 void frame_flash_start(ObFrame *self)
1355 {
1356     self->flash_on = self->focused;
1357
1358     if (!self->flashing)
1359         ob_main_loop_timeout_add(ob_main_loop,
1360                                  G_USEC_PER_SEC * 0.6,
1361                                  flash_timeout,
1362                                  self,
1363                                  g_direct_equal,
1364                                  flash_done);
1365     g_get_current_time(&self->flash_end);
1366     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1367     
1368     self->flashing = TRUE;
1369 }
1370
1371 void frame_flash_stop(ObFrame *self)
1372 {
1373     self->flashing = FALSE;
1374 }
1375
1376 static gulong frame_animate_iconify_time_left(ObFrame *self,
1377                                               const GTimeVal *now)
1378 {
1379     glong sec, usec;
1380     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1381     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1382     if (usec < 0) {
1383         usec += G_USEC_PER_SEC;
1384         sec--;
1385     }
1386     /* no negative values */
1387     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1388 }
1389
1390 static gboolean frame_animate_iconify(gpointer p)
1391 {
1392     ObFrame *self = p;
1393     gint x, y, w, h;
1394     gint iconx, icony, iconw;
1395     GTimeVal now;
1396     gulong time;
1397     gboolean iconifying;
1398
1399     if (self->client->icon_geometry.width == 0) {
1400         /* there is no icon geometry set so just go straight down */
1401         Rect *a = screen_physical_area();
1402         iconx = self->area.x + self->area.width / 2 + 32;
1403         icony = a->y + a->width;
1404         iconw = 64;
1405     } else {
1406         iconx = self->client->icon_geometry.x;
1407         icony = self->client->icon_geometry.y;
1408         iconw = self->client->icon_geometry.width;
1409     }
1410
1411     iconifying = self->iconify_animation_going > 0;
1412
1413     /* how far do we have left to go ? */
1414     g_get_current_time(&now);
1415     time = frame_animate_iconify_time_left(self, &now);
1416     
1417     if (time == 0 || iconifying) {
1418         /* start where the frame is supposed to be */
1419         x = self->area.x;
1420         y = self->area.y;
1421         w = self->area.width - self->bwidth * 2;
1422         h = self->area.height - self->bwidth * 2;
1423     } else {
1424         /* start at the icon */
1425         x = iconx;
1426         y = icony;
1427         w = iconw;
1428         h = self->size.top; /* just the titlebar */
1429     }
1430
1431     if (time > 0) {
1432         glong dx, dy, dw;
1433         glong elapsed;
1434
1435         dx = self->area.x - iconx;
1436         dy = self->area.y - icony;
1437         dw = self->area.width - self->bwidth * 2 - iconw;
1438          /* if restoring, we move in the opposite direction */
1439         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1440
1441         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1442         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1443         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1444         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1445         h = self->size.top; /* just the titlebar */
1446     }
1447
1448     if (time == 0)
1449         frame_end_iconify_animation(self);
1450     else {
1451         XMoveResizeWindow(ob_display, self->window, x, y, w, h);
1452         XFlush(ob_display);
1453     }
1454
1455     return time > 0; /* repeat until we're out of time */
1456 }
1457
1458 void frame_end_iconify_animation(ObFrame *self)
1459 {
1460     /* see if there is an animation going */
1461     if (self->iconify_animation_going == 0) return;
1462
1463     if (!self->visible)
1464         XUnmapWindow(ob_display, self->window);
1465     else
1466         /* Send a ConfigureNotify when the animation is done, this fixes
1467            KDE's pager showing the window in the wrong place. */
1468         client_reconfigure(self->client);
1469
1470     /* we're not animating any more ! */
1471     self->iconify_animation_going = 0;
1472
1473     XMoveResizeWindow(ob_display, self->window,
1474                       self->area.x, self->area.y,
1475                       self->area.width - self->bwidth * 2,
1476                       self->area.height - self->bwidth * 2);
1477     XFlush(ob_display);
1478 }
1479
1480 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1481 {
1482     gulong time;
1483     gboolean new_anim = FALSE;
1484     gboolean set_end = TRUE;
1485     GTimeVal now;
1486
1487     /* if there is no titlebar, just don't animate for now
1488        XXX it would be nice tho.. */
1489     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1490         return;
1491
1492     /* get the current time */
1493     g_get_current_time(&now);
1494
1495     /* get how long until the end */
1496     time = FRAME_ANIMATE_ICONIFY_TIME;
1497     if (self->iconify_animation_going) {
1498         if (!!iconifying != (self->iconify_animation_going > 0)) {
1499             /* animation was already going on in the opposite direction */
1500             time = time - frame_animate_iconify_time_left(self, &now);
1501         } else
1502             /* animation was already going in the same direction */
1503             set_end = FALSE;
1504     } else
1505         new_anim = TRUE;
1506     self->iconify_animation_going = iconifying ? 1 : -1;
1507
1508     /* set the ending time */
1509     if (set_end) {
1510         self->iconify_animation_end.tv_sec = now.tv_sec;
1511         self->iconify_animation_end.tv_usec = now.tv_usec;
1512         g_time_val_add(&self->iconify_animation_end, time);
1513     }
1514
1515     if (new_anim) {
1516         ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
1517                                          self, FALSE);
1518         ob_main_loop_timeout_add(ob_main_loop,
1519                                  FRAME_ANIMATE_ICONIFY_STEP_TIME,
1520                                  frame_animate_iconify, self,
1521                                  g_direct_equal, NULL);
1522
1523         /* do the first step */
1524         frame_animate_iconify(self);
1525
1526         /* show it during the animation even if it is not "visible" */
1527         if (!self->visible)
1528             XMapWindow(ob_display, self->window);
1529     }
1530 }