1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 frame.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
26 #include "framerender.h"
27 #include "focus_cycle.h"
28 #include "focus_cycle_indicator.h"
29 #include "moveresize.h"
31 #include "obrender/theme.h"
32 #include "obt/display.h"
33 #include "obt/xqueue.h"
36 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
37 ButtonPressMask | ButtonReleaseMask | \
38 SubstructureRedirectMask | FocusChangeMask)
39 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
40 ButtonMotionMask | PointerMotionMask | \
41 EnterWindowMask | LeaveWindowMask)
43 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
44 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (1000 / 60) /* 60 Hz */
46 #define FRAME_HANDLE_Y(f) (f->size.top + f->client->area.height + f->cbwidth_b)
48 static void flash_done(gpointer data);
49 static gboolean flash_timeout(gpointer data);
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);
56 static Window createWindow(Window parent, Visual *visual, int depth,
57 gulong mask, XSetWindowAttributes *attrib)
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)),
66 static Visual *check_32bit_client(ObClient *c)
68 XWindowAttributes wattrib;
71 /* we're already running at 32 bit depth, yay. we don't need to use their
73 if (RrDepth(ob_rr_inst) == 32)
76 ret = XGetWindowAttributes(obt_display, c->window, &wattrib);
77 g_assert(ret != BadDrawable);
78 g_assert(ret != BadWindow);
80 if (wattrib.depth == 32)
81 return wattrib.visual;
85 ObFrame *frame_new(ObClient *client)
87 XSetWindowAttributes attrib;
92 self = g_slice_new0(ObFrame);
93 self->client = client;
95 visual = check_32bit_client(client);
97 /* create the non-visible decor windows */
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),
107 attrib.background_pixel = BlackPixel(obt_display, ob_screen);
108 attrib.border_pixel = BlackPixel(obt_display, ob_screen);
110 self->depth = visual ? 32 : RrDepth(ob_rr_inst);
111 self->window = createWindow(obt_root(ob_screen), visual, self->depth,
114 /* create the visible decor windows */
118 /* client has a 32-bit visual */
119 mask = CWColormap | CWBackPixel | CWBorderPixel;
120 attrib.colormap = RrColormap(ob_rr_inst);
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);
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 */
133 self->focused = FALSE;
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;
140 /* make sure the size will be different the first time, so the extent hints
142 STRUT_SET(self->oldsize, -1, -1, -1, -1);
144 set_theme_statics(self);
149 static void set_theme_statics(ObFrame *self)
151 /* XXX set colors/appearance/sizes for stuff that doesn't change */
154 static void free_theme_statics(ObFrame *self)
158 void frame_free(ObFrame *self)
160 free_theme_statics(self);
162 XDestroyWindow(obt_display, self->window);
164 XFreeColormap(obt_display, self->colormap);
166 g_slice_free(ObFrame, self);
169 void frame_show(ObFrame *self)
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. */
178 XMapWindow(obt_display, self->client->window);
179 XMapWindow(obt_display, self->window);
184 void frame_hide(ObFrame *self)
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;
197 void frame_adjust_theme(ObFrame *self)
199 free_theme_statics(self);
200 set_theme_statics(self);
204 void frame_adjust_shape_kind(ObFrame *self, int kind)
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,
217 /* make the frame's shape match the clients */
218 XShapeCombineShape(obt_display, self->window, kind,
221 self->client->window,
225 if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
228 xrect[0].width = self->area.width;
229 xrect[0].height = self->size.top;
233 if (self->decorations & OB_FRAME_DECOR_HANDLE &&
234 ob_rr_theme->handle_height > 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 +
244 XShapeCombineRectangles(obt_display, self->window,
245 ShapeBounding, 0, 0, xrect, num,
246 ShapeUnion, Unsorted);
251 void frame_adjust_shape(ObFrame *self)
254 frame_adjust_shape_kind(self, ShapeBounding);
255 frame_adjust_shape_kind(self, ShapeInput);
259 void frame_adjust_area(ObFrame *self, gboolean moved,
260 gboolean resized, gboolean fake)
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.
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 !
278 resized is only false when reconfiguring (move/resizing) a window
279 and not changing its size, and its maximized/shaded states and decor
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.
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
292 frame_adjust_cursors(self);
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;
300 if (self->decorations & OB_FRAME_DECOR_BORDER)
301 self->bwidth = ob_rr_theme->fbwidth;
305 if (self->decorations & OB_FRAME_DECOR_BORDER &&
306 !self->client->undecorated)
312 if (self->max_horz) {
313 /* horz removes some decor? */;
315 /* vert also removes more decor? */;
317 /* only vert or not max at all */;
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)
325 if (self->decorations & OB_FRAME_DECOR_HANDLE)
328 /* XXX set the size of the frame, which may depend on states such as
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);
337 XMoveResizeWindow(obt_display, self->backback,
338 self->size.left, self->size.top,
339 self->client->area.width,
340 self->client->area.height);
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
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
353 i don't know why this does not also happen for fakes.
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);
364 /* XXX not sure why this happens even if moved and resized are
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.
372 but don't do this during an iconify animation. it will be
373 reflected afterwards.
375 XMoveResizeWindow(obt_display, self->window,
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
386 XMoveWindow(obt_display, self->client->window,
387 self->size.left, self->size.top);
391 /* mark the frame that it needs to be repainted */
392 self->need_render = TRUE;
394 framerender_frame(self);
395 /* adjust the shape masks inside the frame to match the client's */
396 frame_adjust_shape(self);
399 /* set hints to tell apps what their frame size is */
400 if (!STRUT_EQUAL(self->size, self->oldsize)) {
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,
408 OBT_PROP_SETA32(self->client->window, KDE_NET_WM_FRAME_STRUT,
410 self->oldsize = self->size;
413 /* if this occurs while we are focus cycling, the indicator needs to
415 if (focus_cycle_target == self->client)
416 focus_cycle_update_indicator(self->client);
420 static void frame_adjust_cursors(ObFrame *self)
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)
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;
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); */
443 /* these ones change when shaded */
444 a.cursor = ob_cursor(r ? (sh ? OB_CURSOR_WEST : OB_CURSOR_NORTHWEST) :
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) :
450 /* XXX set northeast cursors on decor sub-windows
451 XChangeWindowAttributes(obt_display, ..., CWCursor, &a); */
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); */
469 void frame_adjust_client_area(ObFrame *self)
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);
477 void frame_adjust_state(ObFrame *self)
479 self->need_render = TRUE;
480 framerender_frame(self);
483 void frame_adjust_focus(ObFrame *self, gboolean hilite)
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);
494 void frame_adjust_title(ObFrame *self)
496 self->need_render = TRUE;
497 framerender_frame(self);
500 void frame_adjust_icon(ObFrame *self)
502 self->need_render = TRUE;
503 framerender_frame(self);
506 void frame_grab_client(ObFrame *self)
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.
513 /* reparent the client to the frame */
514 XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
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
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.
526 if (ob_state() == OB_STATE_STARTING)
527 self->client->ignore_unmaps += 2;
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);
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 */
540 static gboolean find_reparent(XEvent *e, gpointer data)
542 const ObFrame *self = data;
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;
550 void frame_release_client(ObFrame *self)
552 /* if there was any animation going on, kill it */
553 if (self->iconify_animation_timer)
554 g_source_remove(self->iconify_animation_timer);
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);
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 */
570 if (self->flash_timer) g_source_remove(self->flash_timer);
573 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
577 if (!*names) /* empty string */
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;
587 /* leading spaces in the string */
588 n = g_utf8_next_char(names);
589 if (!frame_next_context_from_string(n, cx))
594 /* delete the space with null zero(s) */
595 while (n < g_utf8_next_char(p))
599 *cx = frame_context_from_string(names);
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;
608 /* delete everything we just read (copy everything at n to the start of
610 for (p = names; *n; ++p, ++n)
617 ObFrameContext frame_context_from_string(const gchar *name)
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;
662 return OB_FRAME_CONTEXT_NONE;
665 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
670 if (moveresize_in_progress)
671 return OB_FRAME_CONTEXT_MOVE_RESIZE;
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;
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
684 if (client->type == OB_CLIENT_TYPE_DESKTOP)
685 return OB_FRAME_CONTEXT_DESKTOP;
686 return OB_FRAME_CONTEXT_CLIENT;
689 self = client->frame;
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 &&
696 /* XXX some windows give a different content when fully maxd */))
698 /* XXX i.e. the topright corner was considered to be == the rightmost
700 return OB_FRAME_CONTEXT_TITLEBAR;
702 else if (self->max_vert &&
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 &&
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;
715 if (win == self->window) return OB_FRAME_CONTEXT_FRAME;
717 /* XXX add all the decor sub-windows, or calculate position of the mouse
718 to determine the context */
720 /* XXX if its not in any other context then its not in an input context */
721 return OB_FRAME_CONTEXT_NONE;
724 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
727 switch (self->client->gravity) {
729 case NorthWestGravity:
730 case SouthWestGravity:
737 /* the middle of the client will be the middle of the frame */
738 *x -= (self->size.right - self->size.left) / 2;
741 case NorthEastGravity:
742 case SouthEastGravity:
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;
751 /* the client's position won't move */
752 *x -= self->size.left - self->client->border_width;
757 switch (self->client->gravity) {
759 case NorthWestGravity:
760 case NorthEastGravity:
767 /* the middle of the client will be the middle of the frame */
768 *y -= (self->size.bottom - self->size.top) / 2;
771 case SouthWestGravity:
772 case SouthEastGravity:
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;
781 /* the client's position won't move */
782 *y -= self->size.top - self->client->border_width;
787 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
790 switch (self->client->gravity) {
792 case NorthWestGravity:
794 case SouthWestGravity:
799 /* the middle of the client will be the middle of the frame */
800 *x += (self->size.right - self->size.left) / 2;
802 case NorthEastGravity:
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;
811 /* the client's position won't move */
812 *x += self->size.left - self->client->border_width;
817 switch (self->client->gravity) {
819 case NorthWestGravity:
821 case NorthEastGravity:
826 /* the middle of the client will be the middle of the frame */
827 *y += (self->size.bottom - self->size.top) / 2;
829 case SouthWestGravity:
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;
838 /* the client's position won't move */
839 *y += self->size.top - self->client->border_width;
844 void frame_rect_to_frame(ObFrame *self, Rect *r)
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);
851 void frame_rect_to_client(ObFrame *self, Rect *r)
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);
858 static void flash_done(gpointer data)
860 ObFrame *self = data;
862 if (self->focused != self->flash_on)
863 frame_adjust_focus(self, self->focused);
866 static gboolean flash_timeout(gpointer data)
868 ObFrame *self = data;
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;
878 return FALSE; /* we are done */
880 self->flash_on = !self->flash_on;
881 if (!self->focused) {
882 frame_adjust_focus(self, self->flash_on);
883 self->focused = FALSE;
887 return TRUE; /* go again */
890 void frame_flash_start(ObFrame *self)
892 self->flash_on = self->focused;
895 self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
896 600, flash_timeout, self,
898 g_get_current_time(&self->flash_end);
899 g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
901 self->flashing = TRUE;
904 void frame_flash_stop(ObFrame *self)
906 self->flashing = FALSE;
909 static gulong frame_animate_iconify_time_left(ObFrame *self,
913 sec = self->iconify_animation_end.tv_sec - now->tv_sec;
914 usec = self->iconify_animation_end.tv_usec - now->tv_usec;
916 usec += G_USEC_PER_SEC;
919 /* no negative values */
920 return MAX(sec * G_USEC_PER_SEC + usec, 0);
923 static gboolean frame_animate_iconify(gpointer p)
927 gint iconx, icony, iconw;
932 if (self->client->icon_geometry.width == 0) {
933 /* there is no icon geometry set so just go straight down */
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;
941 iconx = self->client->icon_geometry.x;
942 icony = self->client->icon_geometry.y;
943 iconw = self->client->icon_geometry.width;
946 iconifying = self->iconify_animation_going > 0;
948 /* how far do we have left to go ? */
949 g_get_current_time(&now);
950 time = frame_animate_iconify_time_left(self, &now);
952 if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
953 /* start where the frame is supposed to be */
956 w = self->area.width;
957 h = self->area.height;
959 /* start at the icon */
963 h = self->size.top; /* just the titlebar */
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; }
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 */
983 XMoveResizeWindow(obt_display, self->window, x, y, w, h);
986 frame_end_iconify_animation(self);
989 return time > 0; /* repeat until we're out of time */
992 void frame_end_iconify_animation(ObFrame *self)
994 /* see if there is an animation going */
995 if (self->iconify_animation_going == 0) return;
998 XUnmapWindow(obt_display, self->window);
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);
1008 /* we're not animating any more ! */
1009 self->iconify_animation_going = 0;
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);
1019 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1022 gboolean new_anim = FALSE;
1023 gboolean set_end = TRUE;
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))
1031 /* get the current time */
1032 g_get_current_time(&now);
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);
1041 /* animation was already going in the same direction */
1045 self->iconify_animation_going = iconifying ? 1 : -1;
1047 /* set the ending time */
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);
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);
1063 /* do the first step */
1064 frame_animate_iconify(self);
1066 /* show it during the animation even if it is not "visible" */
1068 XMapWindow(obt_display, self->window);