]> icculus.org git repositories - dana/openbox.git/blob - openbox/frame.c
when managing a window, make sure its area is within its min/max bounds
[dana/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "grab.h"
24 #include "debug.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "focus_cycle.h"
28 #include "focus_cycle_indicator.h"
29 #include "moveresize.h"
30 #include "screen.h"
31 #include "obrender/theme.h"
32 #include "obt/display.h"
33 #include "obt/xqueue.h"
34 #include "obt/prop.h"
35
36 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
37                          ButtonPressMask | ButtonReleaseMask | \
38                          SubstructureRedirectMask | FocusChangeMask)
39 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
40                            ButtonMotionMask | PointerMotionMask | \
41                            EnterWindowMask | LeaveWindowMask)
42
43 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
44 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (1000 / 60) /* 60 Hz */
45
46 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_b)
47
48 static void flash_done(gpointer data);
49 static gboolean flash_timeout(gpointer data);
50
51 static void set_theme_statics(ObFrame *self);
52 static void free_theme_statics(ObFrame *self);
53 static gboolean frame_animate_iconify(gpointer self);
54 static void frame_adjust_cursors(ObFrame *self);
55
56 static Window createWindow(Window parent, Visual *visual, int depth,
57                            gulong mask, XSetWindowAttributes *attrib)
58 {
59     return XCreateWindow(obt_display, parent, 0, 0, 1, 1, 0,
60                          (depth ? depth : RrDepth(ob_rr_inst)), InputOutput,
61                          (visual ? visual : RrVisual(ob_rr_inst)),
62                          mask, attrib);
63
64 }
65
66 static Visual *check_32bit_client(ObClient *c)
67 {
68     XWindowAttributes wattrib;
69     Status ret;
70
71     /* we're already running at 32 bit depth, yay. we don't need to use their
72        visual */
73     if (RrDepth(ob_rr_inst) == 32)
74         return NULL;
75
76     ret = XGetWindowAttributes(obt_display, c->window, &wattrib);
77     g_assert(ret != BadDrawable);
78     g_assert(ret != BadWindow);
79
80     if (wattrib.depth == 32)
81         return wattrib.visual;
82     return NULL;
83 }
84
85 ObFrame *frame_new(ObClient *client)
86 {
87     XSetWindowAttributes attrib;
88     gulong mask;
89     ObFrame *self;
90     Visual *visual;
91
92     self = g_slice_new0(ObFrame);
93     self->client = client;
94
95     visual = check_32bit_client(client);
96
97     /* create the non-visible decor windows */
98
99     mask = 0;
100     if (visual) {
101         /* client has a 32-bit visual */
102         mask = CWColormap | CWBackPixel | CWBorderPixel;
103         /* create a colormap with the visual */
104         self->colormap = attrib.colormap =
105             XCreateColormap(obt_display, obt_root(ob_screen),
106                             visual, AllocNone);
107         attrib.background_pixel = BlackPixel(obt_display, ob_screen);
108         attrib.border_pixel = BlackPixel(obt_display, ob_screen);
109     }
110     self->depth = visual ? 32 : RrDepth(ob_rr_inst);
111     self->window = createWindow(obt_root(ob_screen), visual, self->depth,
112                                 mask, &attrib);
113
114     /* create the visible decor windows */
115
116     mask = 0;
117     if (visual) {
118         /* client has a 32-bit visual */
119         mask = CWColormap | CWBackPixel | CWBorderPixel;
120         attrib.colormap = RrColormap(ob_rr_inst);
121     }
122
123     self->backback = createWindow(self->window, NULL, 0, mask, &attrib);
124     self->backfront = createWindow(self->backback, NULL, 0, mask, &attrib);
125     XMapWindow(obt_display, self->backback);
126     XMapWindow(obt_display, self->backfront);
127
128     mask |= CWEventMask;
129     attrib.event_mask = ELEMENT_EVENTMASK;
130     /* XXX make visible decor sub-windows here */
131     /* XXX map decor sub-windows that are always shown here */
132
133     self->focused = FALSE;
134
135     self->max_press = self->close_press = self->desk_press =
136         self->iconify_press = self->shade_press = FALSE;
137     self->max_hover = self->close_hover = self->desk_hover =
138         self->iconify_hover = self->shade_hover = FALSE;
139
140     /* make sure the size will be different the first time, so the extent hints
141        will be set */
142     STRUT_SET(self->oldsize, -1, -1, -1, -1);
143
144     set_theme_statics(self);
145
146     return self;
147 }
148
149 static void set_theme_statics(ObFrame *self)
150 {
151     /* XXX set colors/appearance/sizes for stuff that doesn't change */
152 }
153
154 static void free_theme_statics(ObFrame *self)
155 {
156 }
157
158 void frame_free(ObFrame *self)
159 {
160     free_theme_statics(self);
161
162     XDestroyWindow(obt_display, self->window);
163     if (self->colormap)
164         XFreeColormap(obt_display, self->colormap);
165
166     g_slice_free(ObFrame, self);
167 }
168
169 void frame_show(ObFrame *self)
170 {
171     if (!self->visible) {
172         self->visible = TRUE;
173         framerender_frame(self);
174         /* Grab the server to make sure that the frame window is mapped before
175            the client gets its MapNotify, i.e. to make sure the client is
176            _visible_ when it gets MapNotify. */
177         grab_server(TRUE);
178         XMapWindow(obt_display, self->client->window);
179         XMapWindow(obt_display, self->window);
180         grab_server(FALSE);
181     }
182 }
183
184 void frame_hide(ObFrame *self)
185 {
186     if (self->visible) {
187         self->visible = FALSE;
188         if (!frame_iconify_animating(self))
189             XUnmapWindow(obt_display, self->window);
190         /* we unmap the client itself so that we can get MapRequest
191            events, and because the ICCCM tells us to! */
192         XUnmapWindow(obt_display, self->client->window);
193         self->client->ignore_unmaps += 1;
194     }
195 }
196
197 void frame_adjust_theme(ObFrame *self)
198 {
199     free_theme_statics(self);
200     set_theme_statics(self);
201 }
202
203 #ifdef SHAPE
204 void frame_adjust_shape_kind(ObFrame *self, int kind)
205 {
206     gint num;
207     XRectangle xrect[2];
208
209     if (!((kind == ShapeBounding && self->client->shaped) ||
210           (kind == ShapeInput && self->client->shaped_input))) {
211         /* clear the shape on the frame window */
212         XShapeCombineMask(obt_display, self->window, kind,
213                           self->size.left,
214                           self->size.top,
215                           None, ShapeSet);
216     } else {
217         /* make the frame's shape match the clients */
218         XShapeCombineShape(obt_display, self->window, kind,
219                            self->size.left,
220                            self->size.top,
221                            self->client->window,
222                            kind, ShapeSet);
223
224         num = 0;
225         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
226             xrect[0].x = 0;
227             xrect[0].y = 0;
228             xrect[0].width = self->area.width;
229             xrect[0].height = self->size.top;
230             ++num;
231         }
232
233         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
234             ob_rr_theme->handle_height > 0)
235         {
236             xrect[1].x = 0;
237             xrect[1].y = FRAME_HANDLE_Y(self);
238             xrect[1].width = self->area.width;
239             xrect[1].height = ob_rr_theme->handle_height +
240                 self->bwidth * 2;
241             ++num;
242         }
243
244         XShapeCombineRectangles(obt_display, self->window,
245                                 ShapeBounding, 0, 0, xrect, num,
246                                 ShapeUnion, Unsorted);
247     }
248 }
249 #endif
250
251 void frame_adjust_shape(ObFrame *self)
252 {
253 #ifdef SHAPE
254   frame_adjust_shape_kind(self, ShapeBounding);
255   frame_adjust_shape_kind(self, ShapeInput);
256 #endif
257 }
258
259 void frame_adjust_area(ObFrame *self, gboolean moved,
260                        gboolean resized, gboolean fake)
261 {
262     /* XXX fake should not exist !! it is used in two cases:
263        1) when "fake managing a window" just to report the window's frame.size
264        2) when trying out a move/resize to see what the result would be.
265           again, this is just to find out the frame's theoretical geometry,
266           and actually changing it is potentially problematic.  there should
267           be a separate function that returns the frame's geometry that this
268           can use, and outsiders can use it to "test" a configuration.
269     */
270
271     /* XXX this notion of "resized" doesn't really make sense anymore, it is
272        more of a "the frame might be changing more than it's position.  it
273        also occurs from state changes in the client, and a different name for
274        it would make sense.  basically, if resized is FALSE then the client
275        can only have moved and had no other change take place. if moved is
276        also FALSE then it didn't change at all !
277
278        resized is only false when reconfiguring (move/resizing) a window
279        and not changing its size, and its maximized/shaded states and decor
280        match the frame's.
281
282        moved and resized should be something decided HERE, not by the caller?
283        would need to keep the client's old position/size/gravity/etc all
284        mirrored here, thats maybe a lot of state, maybe not more than was here
285        already.. not all states affect decor.
286     */
287
288     if (resized) {
289         /* do this before changing the frame's status like max_horz max_vert,
290            as it compares them to the client's to see if things need to
291            change */
292         frame_adjust_cursors(self);
293
294         self->functions = self->client->functions;
295         self->decorations = self->client->decorations;
296         self->max_horz = self->client->max_horz;
297         self->max_vert = self->client->max_vert;
298         self->shaded = self->client->shaded;
299
300         if (self->decorations & OB_FRAME_DECOR_BORDER)
301             self->bwidth = ob_rr_theme->fbwidth;
302         else
303             self->bwidth = 0;
304
305         if (self->decorations & OB_FRAME_DECOR_BORDER &&
306             !self->client->undecorated)
307         {
308             self->cbwidth_b = 0;
309         } else
310             self->cbwidth_b = 0;
311
312         if (self->max_horz) {
313             /* horz removes some decor? */;
314             if (self->max_vert)
315                 /* vert also removes more decor? */;
316         } else
317             /* only vert or not max at all */;
318
319         /* XXX set the size of the frame around the client... */
320         STRUT_SET(self->size,
321                   self->bwidth, self->bwidth, self->bwidth, self->bwidth);
322         /* ... which may depend on what decor is being shown */
323         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
324             ;
325         if (self->decorations & OB_FRAME_DECOR_HANDLE)
326             ;
327
328         /* XXX set the size of the frame, which may depend on states such as
329            being shaded. */
330         RECT_SET_SIZE(self->area,
331                       self->client->area.width +
332                       self->size.left + self->size.right,
333                       self->client->area.height +
334                       self->size.top + self->size.bottom);
335
336         if (!fake) {
337             XMoveResizeWindow(obt_display, self->backback,
338                               self->size.left, self->size.top,
339                               self->client->area.width,
340                               self->client->area.height);
341
342             /* XXX set up all the decor sub-windows if not fake.  when it's
343                fake that means we want to calc stuff but not change anything
344                visibly. */
345         }
346     }
347
348     if ((moved || resized) && !fake) {
349         /* XXX the geometry (such as frame.size strut) of the frame is
350            recalculated when !fake, but the position of the frame is not
351            changed.
352
353            i don't know why this does not also happen for fakes.
354         */
355
356         /* find the new coordinates for the frame, which must be done after
357            setting the frame.size (frame_client_gravity uses it) */
358         self->area.x = self->client->area.x;
359         self->area.y = self->client->area.y;
360         frame_client_gravity(self, &self->area.x, &self->area.y);
361     }
362
363     if (!fake) {
364         /* XXX not sure why this happens even if moved and resized are
365            FALSE. */
366
367         /* actually move/resize the frame window if !fake and not animating */
368         if (!frame_iconify_animating(self)) {
369             /* move and resize the top level frame.
370                shading can change without being moved or resized.
371
372                but don't do this during an iconify animation. it will be
373                reflected afterwards.
374             */
375             XMoveResizeWindow(obt_display, self->window,
376                               self->area.x,
377                               self->area.y,
378                               self->area.width,
379                               self->area.height);
380
381             /* when the client has StaticGravity, it likes to move around.
382                also this correctly positions the client when it maps.
383                this also needs to be run when the frame's decorations sizes
384                change!
385             */
386             XMoveWindow(obt_display, self->client->window,
387                         self->size.left, self->size.top);
388         }
389
390         if (resized) {
391             /* mark the frame that it needs to be repainted */
392             self->need_render = TRUE;
393             /* draw the frame */
394             framerender_frame(self);
395             /* adjust the shape masks inside the frame to match the client's */
396             frame_adjust_shape(self);
397         }
398
399         /* set hints to tell apps what their frame size is */
400         if (!STRUT_EQUAL(self->size, self->oldsize)) {
401             gulong vals[4];
402             vals[0] = self->size.left;
403             vals[1] = self->size.right;
404             vals[2] = self->size.top;
405             vals[3] = self->size.bottom;
406             OBT_PROP_SETA32(self->client->window, NET_FRAME_EXTENTS,
407                             CARDINAL, vals, 4);
408             OBT_PROP_SETA32(self->client->window, KDE_NET_WM_FRAME_STRUT,
409                             CARDINAL, vals, 4);
410             self->oldsize = self->size;
411         }
412
413         /* if this occurs while we are focus cycling, the indicator needs to
414            match the changes */
415         if (focus_cycle_target == self->client)
416             focus_cycle_update_indicator(self->client);
417     }
418 }
419
420 static void frame_adjust_cursors(ObFrame *self)
421 {
422     if ((self->functions & OB_CLIENT_FUNC_RESIZE) !=
423         (self->client->functions & OB_CLIENT_FUNC_RESIZE) ||
424         self->max_horz != self->client->max_horz ||
425         self->max_vert != self->client->max_vert ||
426         self->shaded != self->client->shaded)
427     {
428         gboolean r = (self->client->functions & OB_CLIENT_FUNC_RESIZE) &&
429             !(self->client->max_horz && self->client->max_vert);
430         gboolean topbot = !self->client->max_vert;
431         gboolean sh = self->client->shaded;
432         XSetWindowAttributes a;
433
434         /* these ones turn off when max vert, and some when shaded */
435         a.cursor = ob_cursor(r && topbot && !sh ?
436                              OB_CURSOR_NORTH : OB_CURSOR_NONE);
437         /* XXX set north cursors on decor sub-windows
438            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
439         a.cursor = ob_cursor(r && topbot ? OB_CURSOR_SOUTH : OB_CURSOR_NONE);
440         /* XXX set south cursors on decor sub-windows
441            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
442
443         /* these ones change when shaded */
444         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_WEST : OB_CURSOR_NORTHWEST) :
445                              OB_CURSOR_NONE);
446         /* XXX set northwest cursors on decor sub-windows
447            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
448         a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_EAST : OB_CURSOR_NORTHEAST) :
449                              OB_CURSOR_NONE);
450         /* XXX set northeast cursors on decor sub-windows
451            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
452
453         /* these ones are pretty static */
454         a.cursor = ob_cursor(r ? OB_CURSOR_WEST : OB_CURSOR_NONE);
455         /* XXX set west cursors on decor sub-windows
456            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
457         a.cursor = ob_cursor(r ? OB_CURSOR_EAST : OB_CURSOR_NONE);
458         /* XXX set east cursors on decor sub-windows
459            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
460         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHWEST : OB_CURSOR_NONE);
461         /* XXX set southwest cursors on decor sub-windows
462            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
463         a.cursor = ob_cursor(r ? OB_CURSOR_SOUTHEAST : OB_CURSOR_NONE);
464         /* XXX set southeast cursors on decor sub-windows
465            XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
466     }
467 }
468
469 void frame_adjust_client_area(ObFrame *self)
470 {
471     /* adjust the window which is there to prevent flashing on unmap */
472     XMoveResizeWindow(obt_display, self->backfront, 0, 0,
473                       self->client->area.width,
474                       self->client->area.height);
475 }
476
477 void frame_adjust_state(ObFrame *self)
478 {
479     self->need_render = TRUE;
480     framerender_frame(self);
481 }
482
483 void frame_adjust_focus(ObFrame *self, gboolean hilite)
484 {
485     ob_debug_type(OB_DEBUG_FOCUS,
486                   "Frame for 0x%x has focus: %d\n",
487                   self->client->window, hilite);
488     self->focused = hilite;
489     self->need_render = TRUE;
490     framerender_frame(self);
491     XFlush(obt_display);
492 }
493
494 void frame_adjust_title(ObFrame *self)
495 {
496     self->need_render = TRUE;
497     framerender_frame(self);
498 }
499
500 void frame_adjust_icon(ObFrame *self)
501 {
502     self->need_render = TRUE;
503     framerender_frame(self);
504 }
505
506 void frame_grab_client(ObFrame *self)
507 {
508     /* DO NOT map the client window here. we used to do that, but it is bogus.
509        we need to set up the client's dimensions and everything before we
510        send a mapnotify or we create race conditions.
511     */
512
513     /* reparent the client to the frame */
514     XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
515
516     /*
517       When reparenting the client window, it is usually not mapped yet, since
518       this occurs from a MapRequest. However, in the case where Openbox is
519       starting up, the window is already mapped, so we'll see an unmap event
520       for it.
521
522       We also have to ignore a second UnmapNotify because we have
523       selected for SubstructureNotify on root.  For whatever good reason, this
524       means we get 2 UnmapNotify events.
525     */
526     if (ob_state() == OB_STATE_STARTING)
527         self->client->ignore_unmaps += 2;
528
529     /* select the event mask on the client's parent (to receive config/map
530        req's) the ButtonPress is to catch clicks on the client border */
531     XSelectInput(obt_display, self->window, FRAME_EVENTMASK);
532
533     /* set all the windows for the frame in the window_map */
534     window_add(&self->window, CLIENT_AS_WINDOW(self->client));
535     window_add(&self->backback, CLIENT_AS_WINDOW(self->client));
536     window_add(&self->backfront, CLIENT_AS_WINDOW(self->client));
537     /* XXX add any decor sub-windows that may receive events */
538 }
539
540 static gboolean find_reparent(XEvent *e, gpointer data)
541 {
542     const ObFrame *self = data;
543
544     /* Find ReparentNotify events for the window that aren't being reparented into the
545        frame, thus the client reparenting itself off the frame. */
546     return e->type == ReparentNotify && e->xreparent.window == self->client->window &&
547         e->xreparent.parent != self->window;
548 }
549
550 void frame_release_client(ObFrame *self)
551 {
552     /* if there was any animation going on, kill it */
553     if (self->iconify_animation_timer)
554         g_source_remove(self->iconify_animation_timer);
555
556     /* check if the app has already reparented its window away */
557     if (!xqueue_exists_local(find_reparent, self)) {
558         /* according to the ICCCM - if the client doesn't reparent itself,
559            then we will reparent the window to root for them */
560         XReparentWindow(obt_display, self->client->window, obt_root(ob_screen),
561                         self->client->area.x, self->client->area.y);
562     }
563
564     /* remove all the windows for the frame from the window_map */
565     window_remove(self->window);
566     window_remove(self->backback);
567     window_remove(self->backfront);
568     /* XXX add any decor sub-windows that may receive events */
569
570     if (self->flash_timer) g_source_remove(self->flash_timer);
571 }
572
573 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
574 {
575     gchar *p, *n;
576
577     if (!*names) /* empty string */
578         return FALSE;
579
580     /* find the first space */
581     for (p = names; *p; p = g_utf8_next_char(p)) {
582         const gunichar c = g_utf8_get_char(p);
583         if (g_unichar_isspace(c)) break;
584     }
585
586     if (p == names) {
587         /* leading spaces in the string */
588         n = g_utf8_next_char(names);
589         if (!frame_next_context_from_string(n, cx))
590             return FALSE;
591     } else {
592         n = p;
593         if (*p) {
594             /* delete the space with null zero(s) */
595             while (n < g_utf8_next_char(p))
596                 *(n++) = '\0';
597         }
598
599         *cx = frame_context_from_string(names);
600
601         /* find the next non-space */
602         for (; *n; n = g_utf8_next_char(n)) {
603             const gunichar c = g_utf8_get_char(n);
604             if (!g_unichar_isspace(c)) break;
605         }
606     }
607
608     /* delete everything we just read (copy everything at n to the start of
609        the string */
610     for (p = names; *n; ++p, ++n)
611         *p = *n;
612     *p = *n;
613
614     return TRUE;
615 }
616
617 ObFrameContext frame_context_from_string(const gchar *name)
618 {
619     if (!g_ascii_strcasecmp("Desktop", name))
620         return OB_FRAME_CONTEXT_DESKTOP;
621     else if (!g_ascii_strcasecmp("Root", name))
622         return OB_FRAME_CONTEXT_ROOT;
623     else if (!g_ascii_strcasecmp("Client", name))
624         return OB_FRAME_CONTEXT_CLIENT;
625     else if (!g_ascii_strcasecmp("Titlebar", name))
626         return OB_FRAME_CONTEXT_TITLEBAR;
627     else if (!g_ascii_strcasecmp("Frame", name))
628         return OB_FRAME_CONTEXT_FRAME;
629     else if (!g_ascii_strcasecmp("TLCorner", name))
630         return OB_FRAME_CONTEXT_TLCORNER;
631     else if (!g_ascii_strcasecmp("TRCorner", name))
632         return OB_FRAME_CONTEXT_TRCORNER;
633     else if (!g_ascii_strcasecmp("BLCorner", name))
634         return OB_FRAME_CONTEXT_BLCORNER;
635     else if (!g_ascii_strcasecmp("BRCorner", name))
636         return OB_FRAME_CONTEXT_BRCORNER;
637     else if (!g_ascii_strcasecmp("Top", name))
638         return OB_FRAME_CONTEXT_TOP;
639     else if (!g_ascii_strcasecmp("Bottom", name))
640         return OB_FRAME_CONTEXT_BOTTOM;
641     else if (!g_ascii_strcasecmp("Left", name))
642         return OB_FRAME_CONTEXT_LEFT;
643     else if (!g_ascii_strcasecmp("Right", name))
644         return OB_FRAME_CONTEXT_RIGHT;
645     else if (!g_ascii_strcasecmp("Maximize", name))
646         return OB_FRAME_CONTEXT_MAXIMIZE;
647     else if (!g_ascii_strcasecmp("AllDesktops", name))
648         return OB_FRAME_CONTEXT_ALLDESKTOPS;
649     else if (!g_ascii_strcasecmp("Shade", name))
650         return OB_FRAME_CONTEXT_SHADE;
651     else if (!g_ascii_strcasecmp("Iconify", name))
652         return OB_FRAME_CONTEXT_ICONIFY;
653     else if (!g_ascii_strcasecmp("Icon", name))
654         return OB_FRAME_CONTEXT_ICON;
655     else if (!g_ascii_strcasecmp("Close", name))
656         return OB_FRAME_CONTEXT_CLOSE;
657     else if (!g_ascii_strcasecmp("MoveResize", name))
658         return OB_FRAME_CONTEXT_MOVE_RESIZE;
659     else if (!g_ascii_strcasecmp("Dock", name))
660         return OB_FRAME_CONTEXT_DOCK;
661
662     return OB_FRAME_CONTEXT_NONE;
663 }
664
665 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
666 {
667     ObFrame *self;
668     ObWindow *obwin;
669
670     if (moveresize_in_progress)
671         return OB_FRAME_CONTEXT_MOVE_RESIZE;
672
673     if (win == obt_root(ob_screen))
674         return OB_FRAME_CONTEXT_ROOT;
675     if ((obwin = window_find(win))) {
676         if (WINDOW_IS_DOCK(obwin)) {
677           return OB_FRAME_CONTEXT_DOCK;
678         }
679     }
680     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
681     if (win == client->window) {
682         /* conceptually, this is the desktop, as far as users are
683            concerned */
684         if (client->type == OB_CLIENT_TYPE_DESKTOP)
685             return OB_FRAME_CONTEXT_DESKTOP;
686         return OB_FRAME_CONTEXT_CLIENT;
687     }
688
689     self = client->frame;
690
691     /* when the user clicks in the corners of the titlebar and the client
692        is fully maximized, then treat it like they clicked in the
693        button that is there */
694     if (self->max_horz && self->max_vert &&
695         (win == 0
696          /* XXX some windows give a different content when fully maxd */))
697     {
698         /* XXX i.e. the topright corner was considered to be == the rightmost
699            titlebar button */
700         return OB_FRAME_CONTEXT_TITLEBAR;
701     }
702     else if (self->max_vert &&
703              (win == 0
704               /* XXX some windows give a different content when vert maxd */))
705         /* XXX i.e. the top stopped existing and became the titlebar cuz you
706            couldnt resize it anymore (this is changing) */
707         return OB_FRAME_CONTEXT_TITLEBAR;
708     else if (self->shaded &&
709              (win == 0
710               /* XXX some windows give a different context when shaded */))
711         /* XXX i.e. the top/bottom became the titlebar cuz you
712            can't resize vertically when shaded (still true) */
713         return OB_FRAME_CONTEXT_TITLEBAR;
714
715     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
716
717     /* XXX add all the decor sub-windows, or calculate position of the mouse
718        to determine the context */
719
720     /* XXX if its not in any other context then its not in an input context */
721     return OB_FRAME_CONTEXT_NONE;
722 }
723
724 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
725 {
726     /* horizontal */
727     switch (self->client->gravity) {
728     default:
729     case NorthWestGravity:
730     case SouthWestGravity:
731     case WestGravity:
732         break;
733
734     case NorthGravity:
735     case SouthGravity:
736     case CenterGravity:
737         /* the middle of the client will be the middle of the frame */
738         *x -= (self->size.right - self->size.left) / 2;
739         break;
740
741     case NorthEastGravity:
742     case SouthEastGravity:
743     case EastGravity:
744         /* the right side of the client will be the right side of the frame */
745         *x -= self->size.right + self->size.left -
746             self->client->border_width * 2;
747         break;
748
749     case ForgetGravity:
750     case StaticGravity:
751         /* the client's position won't move */
752         *x -= self->size.left - self->client->border_width;
753         break;
754     }
755
756     /* vertical */
757     switch (self->client->gravity) {
758     default:
759     case NorthWestGravity:
760     case NorthEastGravity:
761     case NorthGravity:
762         break;
763
764     case CenterGravity:
765     case EastGravity:
766     case WestGravity:
767         /* the middle of the client will be the middle of the frame */
768         *y -= (self->size.bottom - self->size.top) / 2;
769         break;
770
771     case SouthWestGravity:
772     case SouthEastGravity:
773     case SouthGravity:
774         /* the bottom of the client will be the bottom of the frame */
775         *y -= self->size.bottom + self->size.top -
776             self->client->border_width * 2;
777         break;
778
779     case ForgetGravity:
780     case StaticGravity:
781         /* the client's position won't move */
782         *y -= self->size.top - self->client->border_width;
783         break;
784     }
785 }
786
787 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
788 {
789     /* horizontal */
790     switch (self->client->gravity) {
791     default:
792     case NorthWestGravity:
793     case WestGravity:
794     case SouthWestGravity:
795         break;
796     case NorthGravity:
797     case CenterGravity:
798     case SouthGravity:
799         /* the middle of the client will be the middle of the frame */
800         *x += (self->size.right - self->size.left) / 2;
801         break;
802     case NorthEastGravity:
803     case EastGravity:
804     case SouthEastGravity:
805         /* the right side of the client will be the right side of the frame */
806         *x += self->size.right + self->size.left -
807             self->client->border_width * 2;
808         break;
809     case StaticGravity:
810     case ForgetGravity:
811         /* the client's position won't move */
812         *x += self->size.left - self->client->border_width;
813         break;
814     }
815
816     /* vertical */
817     switch (self->client->gravity) {
818     default:
819     case NorthWestGravity:
820     case NorthGravity:
821     case NorthEastGravity:
822         break;
823     case WestGravity:
824     case CenterGravity:
825     case EastGravity:
826         /* the middle of the client will be the middle of the frame */
827         *y += (self->size.bottom - self->size.top) / 2;
828         break;
829     case SouthWestGravity:
830     case SouthGravity:
831     case SouthEastGravity:
832         /* the bottom of the client will be the bottom of the frame */
833         *y += self->size.bottom + self->size.top -
834             self->client->border_width * 2;
835         break;
836     case StaticGravity:
837     case ForgetGravity:
838         /* the client's position won't move */
839         *y += self->size.top - self->client->border_width;
840         break;
841     }
842 }
843
844 void frame_rect_to_frame(ObFrame *self, Rect *r)
845 {
846     r->width += self->size.left + self->size.right;
847     r->height += self->size.top + self->size.bottom;
848     frame_client_gravity(self, &r->x, &r->y);
849 }
850
851 void frame_rect_to_client(ObFrame *self, Rect *r)
852 {
853     r->width -= self->size.left + self->size.right;
854     r->height -= self->size.top + self->size.bottom;
855     frame_frame_gravity(self, &r->x, &r->y);
856 }
857
858 static void flash_done(gpointer data)
859 {
860     ObFrame *self = data;
861
862     if (self->focused != self->flash_on)
863         frame_adjust_focus(self, self->focused);
864 }
865
866 static gboolean flash_timeout(gpointer data)
867 {
868     ObFrame *self = data;
869     GTimeVal now;
870
871     g_get_current_time(&now);
872     if (now.tv_sec > self->flash_end.tv_sec ||
873         (now.tv_sec == self->flash_end.tv_sec &&
874          now.tv_usec >= self->flash_end.tv_usec))
875         self->flashing = FALSE;
876
877     if (!self->flashing)
878         return FALSE; /* we are done */
879
880     self->flash_on = !self->flash_on;
881     if (!self->focused) {
882         frame_adjust_focus(self, self->flash_on);
883         self->focused = FALSE;
884     }
885
886     XFlush(obt_display);
887     return TRUE; /* go again */
888 }
889
890 void frame_flash_start(ObFrame *self)
891 {
892     self->flash_on = self->focused;
893
894     if (!self->flashing)
895         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
896                                                600, flash_timeout, self,
897                                                flash_done);
898     g_get_current_time(&self->flash_end);
899     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
900
901     self->flashing = TRUE;
902 }
903
904 void frame_flash_stop(ObFrame *self)
905 {
906     self->flashing = FALSE;
907 }
908
909 static gulong frame_animate_iconify_time_left(ObFrame *self,
910                                               const GTimeVal *now)
911 {
912     glong sec, usec;
913     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
914     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
915     if (usec < 0) {
916         usec += G_USEC_PER_SEC;
917         sec--;
918     }
919     /* no negative values */
920     return MAX(sec * G_USEC_PER_SEC + usec, 0);
921 }
922
923 static gboolean frame_animate_iconify(gpointer p)
924 {
925     ObFrame *self = p;
926     gint x, y, w, h;
927     gint iconx, icony, iconw;
928     GTimeVal now;
929     gulong time;
930     gboolean iconifying;
931
932     if (self->client->icon_geometry.width == 0) {
933         /* there is no icon geometry set so just go straight down */
934         const Rect *a;
935
936         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
937         iconx = self->area.x + self->area.width / 2 + 32;
938         icony = a->y + a->width;
939         iconw = 64;
940     } else {
941         iconx = self->client->icon_geometry.x;
942         icony = self->client->icon_geometry.y;
943         iconw = self->client->icon_geometry.width;
944     }
945
946     iconifying = self->iconify_animation_going > 0;
947
948     /* how far do we have left to go ? */
949     g_get_current_time(&now);
950     time = frame_animate_iconify_time_left(self, &now);
951
952     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
953         /* start where the frame is supposed to be */
954         x = self->area.x;
955         y = self->area.y;
956         w = self->area.width;
957         h = self->area.height;
958     } else {
959         /* start at the icon */
960         x = iconx;
961         y = icony;
962         w = iconw;
963         h = self->size.top; /* just the titlebar */
964     }
965
966     if (time > 0) {
967         glong dx, dy, dw;
968         glong elapsed;
969
970         dx = self->area.x - iconx;
971         dy = self->area.y - icony;
972         dw = self->area.width - self->bwidth * 2 - iconw;
973          /* if restoring, we move in the opposite direction */
974         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
975
976         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
977         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
978         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
979         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
980         h = self->size.top; /* just the titlebar */
981     }
982
983     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
984
985     if (time == 0)
986         frame_end_iconify_animation(self);
987
988     XFlush(obt_display);
989     return time > 0; /* repeat until we're out of time */
990 }
991
992 void frame_end_iconify_animation(ObFrame *self)
993 {
994     /* see if there is an animation going */
995     if (self->iconify_animation_going == 0) return;
996
997     if (!self->visible)
998         XUnmapWindow(obt_display, self->window);
999     else {
1000         /* Send a ConfigureNotify when the animation is done, this fixes
1001            KDE's pager showing the window in the wrong place.  since the
1002            window is mapped at a different location and is then moved, we
1003            need to send the synthetic configurenotify, since apps may have
1004            read the position when the client mapped, apparently. */
1005         client_reconfigure(self->client, TRUE);
1006     }
1007
1008     /* we're not animating any more ! */
1009     self->iconify_animation_going = 0;
1010
1011     XMoveResizeWindow(obt_display, self->window,
1012                       self->area.x, self->area.y,
1013                       self->area.width, self->area.height);
1014     /* we delay re-rendering until after we're done animating */
1015     framerender_frame(self);
1016     XFlush(obt_display);
1017 }
1018
1019 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1020 {
1021     gulong time;
1022     gboolean new_anim = FALSE;
1023     gboolean set_end = TRUE;
1024     GTimeVal now;
1025
1026     /* if there is no titlebar, just don't animate for now
1027        XXX it would be nice tho.. */
1028     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1029         return;
1030
1031     /* get the current time */
1032     g_get_current_time(&now);
1033
1034     /* get how long until the end */
1035     time = FRAME_ANIMATE_ICONIFY_TIME;
1036     if (self->iconify_animation_going) {
1037         if (!!iconifying != (self->iconify_animation_going > 0)) {
1038             /* animation was already going on in the opposite direction */
1039             time = time - frame_animate_iconify_time_left(self, &now);
1040         } else
1041             /* animation was already going in the same direction */
1042             set_end = FALSE;
1043     } else
1044         new_anim = TRUE;
1045     self->iconify_animation_going = iconifying ? 1 : -1;
1046
1047     /* set the ending time */
1048     if (set_end) {
1049         self->iconify_animation_end.tv_sec = now.tv_sec;
1050         self->iconify_animation_end.tv_usec = now.tv_usec;
1051         g_time_val_add(&self->iconify_animation_end, time);
1052     }
1053
1054     if (new_anim) {
1055         if (self->iconify_animation_timer)
1056             g_source_remove(self->iconify_animation_timer);
1057         self->iconify_animation_timer =
1058             g_timeout_add_full(G_PRIORITY_DEFAULT,
1059                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1060                                frame_animate_iconify, self, NULL);
1061                                
1062
1063         /* do the first step */
1064         frame_animate_iconify(self);
1065
1066         /* show it during the animation even if it is not "visible" */
1067         if (!self->visible)
1068             XMapWindow(obt_display, self->window);
1069     }
1070 }