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