]> icculus.org git repositories - dana/openbox.git/blob - openbox/frame.c
make the root background color a config option, and add name_window_pixmap() helper...
[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        (it should go to 0,0 as the window's redirected "area" is initialized
515        to this value) */
516     XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
517
518     /*
519       When reparenting the client window, it is usually not mapped yet, since
520       this occurs from a MapRequest. However, in the case where Openbox is
521       starting up, the window is already mapped, so we'll see an unmap event
522       for it.
523
524       We also have to ignore a second UnmapNotify because we have
525       selected for SubstructureNotify on root.  For whatever good reason, this
526       means we get 2 UnmapNotify events.
527     */
528     if (ob_state() == OB_STATE_STARTING)
529         self->client->ignore_unmaps += 2;
530
531     /* select the event mask on the client's parent (to receive config/map
532        req's) the ButtonPress is to catch clicks on the client border */
533     XSelectInput(obt_display, self->window, FRAME_EVENTMASK);
534
535     /* set all the windows for the frame in the window_map */
536     window_add(&self->window, CLIENT_AS_WINDOW(self->client));
537     window_add(&self->backback, CLIENT_AS_WINDOW(self->client));
538     window_add(&self->backfront, CLIENT_AS_WINDOW(self->client));
539     /* XXX add any decor sub-windows that may receive events */
540 }
541
542 static gboolean find_reparent(XEvent *e, gpointer data)
543 {
544     const ObFrame *self = data;
545
546     /* Find ReparentNotify events for the window that aren't being reparented into the
547        frame, thus the client reparenting itself off the frame. */
548     return e->type == ReparentNotify && e->xreparent.window == self->client->window &&
549         e->xreparent.parent != self->window;
550 }
551
552 void frame_release_client(ObFrame *self)
553 {
554     /* if there was any animation going on, kill it */
555     if (self->iconify_animation_timer)
556         g_source_remove(self->iconify_animation_timer);
557
558     /* check if the app has already reparented its window away */
559     if (!xqueue_exists_local(find_reparent, self)) {
560         /* according to the ICCCM - if the client doesn't reparent itself,
561            then we will reparent the window to root for them */
562         XReparentWindow(obt_display, self->client->window, obt_root(ob_screen),
563                         self->client->area.x, self->client->area.y);
564     }
565
566     /* remove all the windows for the frame from the window_map */
567     window_remove(self->window);
568     window_remove(self->backback);
569     window_remove(self->backfront);
570     /* XXX add any decor sub-windows that may receive events */
571
572     if (self->flash_timer) g_source_remove(self->flash_timer);
573 }
574
575 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
576 {
577     gchar *p, *n;
578
579     if (!*names) /* empty string */
580         return FALSE;
581
582     /* find the first space */
583     for (p = names; *p; p = g_utf8_next_char(p)) {
584         const gunichar c = g_utf8_get_char(p);
585         if (g_unichar_isspace(c)) break;
586     }
587
588     if (p == names) {
589         /* leading spaces in the string */
590         n = g_utf8_next_char(names);
591         if (!frame_next_context_from_string(n, cx))
592             return FALSE;
593     } else {
594         n = p;
595         if (*p) {
596             /* delete the space with null zero(s) */
597             while (n < g_utf8_next_char(p))
598                 *(n++) = '\0';
599         }
600
601         *cx = frame_context_from_string(names);
602
603         /* find the next non-space */
604         for (; *n; n = g_utf8_next_char(n)) {
605             const gunichar c = g_utf8_get_char(n);
606             if (!g_unichar_isspace(c)) break;
607         }
608     }
609
610     /* delete everything we just read (copy everything at n to the start of
611        the string */
612     for (p = names; *n; ++p, ++n)
613         *p = *n;
614     *p = *n;
615
616     return TRUE;
617 }
618
619 ObFrameContext frame_context_from_string(const gchar *name)
620 {
621     if (!g_ascii_strcasecmp("Desktop", name))
622         return OB_FRAME_CONTEXT_DESKTOP;
623     else if (!g_ascii_strcasecmp("Root", name))
624         return OB_FRAME_CONTEXT_ROOT;
625     else if (!g_ascii_strcasecmp("Client", name))
626         return OB_FRAME_CONTEXT_CLIENT;
627     else if (!g_ascii_strcasecmp("Titlebar", name))
628         return OB_FRAME_CONTEXT_TITLEBAR;
629     else if (!g_ascii_strcasecmp("Frame", name))
630         return OB_FRAME_CONTEXT_FRAME;
631     else if (!g_ascii_strcasecmp("TLCorner", name))
632         return OB_FRAME_CONTEXT_TLCORNER;
633     else if (!g_ascii_strcasecmp("TRCorner", name))
634         return OB_FRAME_CONTEXT_TRCORNER;
635     else if (!g_ascii_strcasecmp("BLCorner", name))
636         return OB_FRAME_CONTEXT_BLCORNER;
637     else if (!g_ascii_strcasecmp("BRCorner", name))
638         return OB_FRAME_CONTEXT_BRCORNER;
639     else if (!g_ascii_strcasecmp("Top", name))
640         return OB_FRAME_CONTEXT_TOP;
641     else if (!g_ascii_strcasecmp("Bottom", name))
642         return OB_FRAME_CONTEXT_BOTTOM;
643     else if (!g_ascii_strcasecmp("Left", name))
644         return OB_FRAME_CONTEXT_LEFT;
645     else if (!g_ascii_strcasecmp("Right", name))
646         return OB_FRAME_CONTEXT_RIGHT;
647     else if (!g_ascii_strcasecmp("Maximize", name))
648         return OB_FRAME_CONTEXT_MAXIMIZE;
649     else if (!g_ascii_strcasecmp("AllDesktops", name))
650         return OB_FRAME_CONTEXT_ALLDESKTOPS;
651     else if (!g_ascii_strcasecmp("Shade", name))
652         return OB_FRAME_CONTEXT_SHADE;
653     else if (!g_ascii_strcasecmp("Iconify", name))
654         return OB_FRAME_CONTEXT_ICONIFY;
655     else if (!g_ascii_strcasecmp("Icon", name))
656         return OB_FRAME_CONTEXT_ICON;
657     else if (!g_ascii_strcasecmp("Close", name))
658         return OB_FRAME_CONTEXT_CLOSE;
659     else if (!g_ascii_strcasecmp("MoveResize", name))
660         return OB_FRAME_CONTEXT_MOVE_RESIZE;
661     else if (!g_ascii_strcasecmp("Dock", name))
662         return OB_FRAME_CONTEXT_DOCK;
663
664     return OB_FRAME_CONTEXT_NONE;
665 }
666
667 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
668 {
669     ObFrame *self;
670     ObWindow *obwin;
671
672     if (moveresize_in_progress)
673         return OB_FRAME_CONTEXT_MOVE_RESIZE;
674
675     if (win == obt_root(ob_screen))
676         return OB_FRAME_CONTEXT_ROOT;
677     if ((obwin = window_find(win))) {
678         if (WINDOW_IS_DOCK(obwin)) {
679           return OB_FRAME_CONTEXT_DOCK;
680         }
681     }
682     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
683     if (win == client->window) {
684         /* conceptually, this is the desktop, as far as users are
685            concerned */
686         if (client->type == OB_CLIENT_TYPE_DESKTOP)
687             return OB_FRAME_CONTEXT_DESKTOP;
688         return OB_FRAME_CONTEXT_CLIENT;
689     }
690
691     self = client->frame;
692
693     /* when the user clicks in the corners of the titlebar and the client
694        is fully maximized, then treat it like they clicked in the
695        button that is there */
696     if (self->max_horz && self->max_vert &&
697         (win == 0
698          /* XXX some windows give a different content when fully maxd */))
699     {
700         /* XXX i.e. the topright corner was considered to be == the rightmost
701            titlebar button */
702         return OB_FRAME_CONTEXT_TITLEBAR;
703     }
704     else if (self->max_vert &&
705              (win == 0
706               /* XXX some windows give a different content when vert maxd */))
707         /* XXX i.e. the top stopped existing and became the titlebar cuz you
708            couldnt resize it anymore (this is changing) */
709         return OB_FRAME_CONTEXT_TITLEBAR;
710     else if (self->shaded &&
711              (win == 0
712               /* XXX some windows give a different context when shaded */))
713         /* XXX i.e. the top/bottom became the titlebar cuz you
714            can't resize vertically when shaded (still true) */
715         return OB_FRAME_CONTEXT_TITLEBAR;
716
717     if (win == self->window)            return OB_FRAME_CONTEXT_FRAME;
718
719     /* XXX add all the decor sub-windows, or calculate position of the mouse
720        to determine the context */
721
722     /* XXX if its not in any other context then its not in an input context */
723     return OB_FRAME_CONTEXT_NONE;
724 }
725
726 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
727 {
728     /* horizontal */
729     switch (self->client->gravity) {
730     default:
731     case NorthWestGravity:
732     case SouthWestGravity:
733     case WestGravity:
734         break;
735
736     case NorthGravity:
737     case SouthGravity:
738     case CenterGravity:
739         /* the middle of the client will be the middle of the frame */
740         *x -= (self->size.right - self->size.left) / 2;
741         break;
742
743     case NorthEastGravity:
744     case SouthEastGravity:
745     case EastGravity:
746         /* the right side of the client will be the right side of the frame */
747         *x -= self->size.right + self->size.left -
748             self->client->border_width * 2;
749         break;
750
751     case ForgetGravity:
752     case StaticGravity:
753         /* the client's position won't move */
754         *x -= self->size.left - self->client->border_width;
755         break;
756     }
757
758     /* vertical */
759     switch (self->client->gravity) {
760     default:
761     case NorthWestGravity:
762     case NorthEastGravity:
763     case NorthGravity:
764         break;
765
766     case CenterGravity:
767     case EastGravity:
768     case WestGravity:
769         /* the middle of the client will be the middle of the frame */
770         *y -= (self->size.bottom - self->size.top) / 2;
771         break;
772
773     case SouthWestGravity:
774     case SouthEastGravity:
775     case SouthGravity:
776         /* the bottom of the client will be the bottom of the frame */
777         *y -= self->size.bottom + self->size.top -
778             self->client->border_width * 2;
779         break;
780
781     case ForgetGravity:
782     case StaticGravity:
783         /* the client's position won't move */
784         *y -= self->size.top - self->client->border_width;
785         break;
786     }
787 }
788
789 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
790 {
791     /* horizontal */
792     switch (self->client->gravity) {
793     default:
794     case NorthWestGravity:
795     case WestGravity:
796     case SouthWestGravity:
797         break;
798     case NorthGravity:
799     case CenterGravity:
800     case SouthGravity:
801         /* the middle of the client will be the middle of the frame */
802         *x += (self->size.right - self->size.left) / 2;
803         break;
804     case NorthEastGravity:
805     case EastGravity:
806     case SouthEastGravity:
807         /* the right side of the client will be the right side of the frame */
808         *x += self->size.right + self->size.left -
809             self->client->border_width * 2;
810         break;
811     case StaticGravity:
812     case ForgetGravity:
813         /* the client's position won't move */
814         *x += self->size.left - self->client->border_width;
815         break;
816     }
817
818     /* vertical */
819     switch (self->client->gravity) {
820     default:
821     case NorthWestGravity:
822     case NorthGravity:
823     case NorthEastGravity:
824         break;
825     case WestGravity:
826     case CenterGravity:
827     case EastGravity:
828         /* the middle of the client will be the middle of the frame */
829         *y += (self->size.bottom - self->size.top) / 2;
830         break;
831     case SouthWestGravity:
832     case SouthGravity:
833     case SouthEastGravity:
834         /* the bottom of the client will be the bottom of the frame */
835         *y += self->size.bottom + self->size.top -
836             self->client->border_width * 2;
837         break;
838     case StaticGravity:
839     case ForgetGravity:
840         /* the client's position won't move */
841         *y += self->size.top - self->client->border_width;
842         break;
843     }
844 }
845
846 void frame_rect_to_frame(ObFrame *self, Rect *r)
847 {
848     r->width += self->size.left + self->size.right;
849     r->height += self->size.top + self->size.bottom;
850     frame_client_gravity(self, &r->x, &r->y);
851 }
852
853 void frame_rect_to_client(ObFrame *self, Rect *r)
854 {
855     r->width -= self->size.left + self->size.right;
856     r->height -= self->size.top + self->size.bottom;
857     frame_frame_gravity(self, &r->x, &r->y);
858 }
859
860 static void flash_done(gpointer data)
861 {
862     ObFrame *self = data;
863
864     if (self->focused != self->flash_on)
865         frame_adjust_focus(self, self->focused);
866 }
867
868 static gboolean flash_timeout(gpointer data)
869 {
870     ObFrame *self = data;
871     GTimeVal now;
872
873     g_get_current_time(&now);
874     if (now.tv_sec > self->flash_end.tv_sec ||
875         (now.tv_sec == self->flash_end.tv_sec &&
876          now.tv_usec >= self->flash_end.tv_usec))
877         self->flashing = FALSE;
878
879     if (!self->flashing)
880         return FALSE; /* we are done */
881
882     self->flash_on = !self->flash_on;
883     if (!self->focused) {
884         frame_adjust_focus(self, self->flash_on);
885         self->focused = FALSE;
886     }
887
888     XFlush(obt_display);
889     return TRUE; /* go again */
890 }
891
892 void frame_flash_start(ObFrame *self)
893 {
894     self->flash_on = self->focused;
895
896     if (!self->flashing)
897         self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
898                                                600, flash_timeout, self,
899                                                flash_done);
900     g_get_current_time(&self->flash_end);
901     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
902
903     self->flashing = TRUE;
904 }
905
906 void frame_flash_stop(ObFrame *self)
907 {
908     self->flashing = FALSE;
909 }
910
911 static gulong frame_animate_iconify_time_left(ObFrame *self,
912                                               const GTimeVal *now)
913 {
914     glong sec, usec;
915     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
916     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
917     if (usec < 0) {
918         usec += G_USEC_PER_SEC;
919         sec--;
920     }
921     /* no negative values */
922     return MAX(sec * G_USEC_PER_SEC + usec, 0);
923 }
924
925 static gboolean frame_animate_iconify(gpointer p)
926 {
927     ObFrame *self = p;
928     gint x, y, w, h;
929     gint iconx, icony, iconw;
930     GTimeVal now;
931     gulong time;
932     gboolean iconifying;
933
934     if (self->client->icon_geometry.width == 0) {
935         /* there is no icon geometry set so just go straight down */
936         const Rect *a;
937
938         a = screen_physical_area_monitor(screen_find_monitor(&self->area));
939         iconx = self->area.x + self->area.width / 2 + 32;
940         icony = a->y + a->width;
941         iconw = 64;
942     } else {
943         iconx = self->client->icon_geometry.x;
944         icony = self->client->icon_geometry.y;
945         iconw = self->client->icon_geometry.width;
946     }
947
948     iconifying = self->iconify_animation_going > 0;
949
950     /* how far do we have left to go ? */
951     g_get_current_time(&now);
952     time = frame_animate_iconify_time_left(self, &now);
953
954     if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
955         /* start where the frame is supposed to be */
956         x = self->area.x;
957         y = self->area.y;
958         w = self->area.width;
959         h = self->area.height;
960     } else {
961         /* start at the icon */
962         x = iconx;
963         y = icony;
964         w = iconw;
965         h = self->size.top; /* just the titlebar */
966     }
967
968     if (time > 0) {
969         glong dx, dy, dw;
970         glong elapsed;
971
972         dx = self->area.x - iconx;
973         dy = self->area.y - icony;
974         dw = self->area.width - self->bwidth * 2 - iconw;
975          /* if restoring, we move in the opposite direction */
976         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
977
978         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
979         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
980         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
981         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
982         h = self->size.top; /* just the titlebar */
983     }
984
985     XMoveResizeWindow(obt_display, self->window, x, y, w, h);
986
987     if (time == 0)
988         frame_end_iconify_animation(self);
989
990     XFlush(obt_display);
991     return time > 0; /* repeat until we're out of time */
992 }
993
994 void frame_end_iconify_animation(ObFrame *self)
995 {
996     /* see if there is an animation going */
997     if (self->iconify_animation_going == 0) return;
998
999     if (!self->visible)
1000         XUnmapWindow(obt_display, self->window);
1001     else {
1002         /* Send a ConfigureNotify when the animation is done, this fixes
1003            KDE's pager showing the window in the wrong place.  since the
1004            window is mapped at a different location and is then moved, we
1005            need to send the synthetic configurenotify, since apps may have
1006            read the position when the client mapped, apparently. */
1007         client_reconfigure(self->client, TRUE);
1008     }
1009
1010     /* we're not animating any more ! */
1011     self->iconify_animation_going = 0;
1012
1013     XMoveResizeWindow(obt_display, self->window,
1014                       self->area.x, self->area.y,
1015                       self->area.width, self->area.height);
1016     /* we delay re-rendering until after we're done animating */
1017     framerender_frame(self);
1018     XFlush(obt_display);
1019 }
1020
1021 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1022 {
1023     gulong time;
1024     gboolean new_anim = FALSE;
1025     gboolean set_end = TRUE;
1026     GTimeVal now;
1027
1028     /* if there is no titlebar, just don't animate for now
1029        XXX it would be nice tho.. */
1030     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1031         return;
1032
1033     /* get the current time */
1034     g_get_current_time(&now);
1035
1036     /* get how long until the end */
1037     time = FRAME_ANIMATE_ICONIFY_TIME;
1038     if (self->iconify_animation_going) {
1039         if (!!iconifying != (self->iconify_animation_going > 0)) {
1040             /* animation was already going on in the opposite direction */
1041             time = time - frame_animate_iconify_time_left(self, &now);
1042         } else
1043             /* animation was already going in the same direction */
1044             set_end = FALSE;
1045     } else
1046         new_anim = TRUE;
1047     self->iconify_animation_going = iconifying ? 1 : -1;
1048
1049     /* set the ending time */
1050     if (set_end) {
1051         self->iconify_animation_end.tv_sec = now.tv_sec;
1052         self->iconify_animation_end.tv_usec = now.tv_usec;
1053         g_time_val_add(&self->iconify_animation_end, time);
1054     }
1055
1056     if (new_anim) {
1057         if (self->iconify_animation_timer)
1058             g_source_remove(self->iconify_animation_timer);
1059         self->iconify_animation_timer =
1060             g_timeout_add_full(G_PRIORITY_DEFAULT,
1061                                FRAME_ANIMATE_ICONIFY_STEP_TIME,
1062                                frame_animate_iconify, self, NULL);
1063                                
1064
1065         /* do the first step */
1066         frame_animate_iconify(self);
1067
1068         /* show it during the animation even if it is not "visible" */
1069         if (!self->visible)
1070             XMapWindow(obt_display, self->window);
1071     }
1072 }