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