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