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 (it should go to 0,0 as the window's redirected "area" is initialized
516 XReparentWindow(obt_display, self->client->window, self->window, 0, 0);
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
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.
528 if (ob_state() == OB_STATE_STARTING)
529 self->client->ignore_unmaps += 2;
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);
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 */
542 static gboolean find_reparent(XEvent *e, gpointer data)
544 const ObFrame *self = data;
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;
552 void frame_release_client(ObFrame *self)
554 /* if there was any animation going on, kill it */
555 if (self->iconify_animation_timer)
556 g_source_remove(self->iconify_animation_timer);
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);
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 */
572 if (self->flash_timer) g_source_remove(self->flash_timer);
575 gboolean frame_next_context_from_string(gchar *names, ObFrameContext *cx)
579 if (!*names) /* empty string */
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;
589 /* leading spaces in the string */
590 n = g_utf8_next_char(names);
591 if (!frame_next_context_from_string(n, cx))
596 /* delete the space with null zero(s) */
597 while (n < g_utf8_next_char(p))
601 *cx = frame_context_from_string(names);
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;
610 /* delete everything we just read (copy everything at n to the start of
612 for (p = names; *n; ++p, ++n)
619 ObFrameContext frame_context_from_string(const gchar *name)
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;
664 return OB_FRAME_CONTEXT_NONE;
667 ObFrameContext frame_context(ObClient *client, Window win, gint x, gint y)
672 if (moveresize_in_progress)
673 return OB_FRAME_CONTEXT_MOVE_RESIZE;
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;
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
686 if (client->type == OB_CLIENT_TYPE_DESKTOP)
687 return OB_FRAME_CONTEXT_DESKTOP;
688 return OB_FRAME_CONTEXT_CLIENT;
691 self = client->frame;
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 &&
698 /* XXX some windows give a different content when fully maxd */))
700 /* XXX i.e. the topright corner was considered to be == the rightmost
702 return OB_FRAME_CONTEXT_TITLEBAR;
704 else if (self->max_vert &&
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 &&
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;
717 if (win == self->window) return OB_FRAME_CONTEXT_FRAME;
719 /* XXX add all the decor sub-windows, or calculate position of the mouse
720 to determine the context */
722 /* XXX if its not in any other context then its not in an input context */
723 return OB_FRAME_CONTEXT_NONE;
726 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
729 switch (self->client->gravity) {
731 case NorthWestGravity:
732 case SouthWestGravity:
739 /* the middle of the client will be the middle of the frame */
740 *x -= (self->size.right - self->size.left) / 2;
743 case NorthEastGravity:
744 case SouthEastGravity:
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;
753 /* the client's position won't move */
754 *x -= self->size.left - self->client->border_width;
759 switch (self->client->gravity) {
761 case NorthWestGravity:
762 case NorthEastGravity:
769 /* the middle of the client will be the middle of the frame */
770 *y -= (self->size.bottom - self->size.top) / 2;
773 case SouthWestGravity:
774 case SouthEastGravity:
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;
783 /* the client's position won't move */
784 *y -= self->size.top - self->client->border_width;
789 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
792 switch (self->client->gravity) {
794 case NorthWestGravity:
796 case SouthWestGravity:
801 /* the middle of the client will be the middle of the frame */
802 *x += (self->size.right - self->size.left) / 2;
804 case NorthEastGravity:
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;
813 /* the client's position won't move */
814 *x += self->size.left - self->client->border_width;
819 switch (self->client->gravity) {
821 case NorthWestGravity:
823 case NorthEastGravity:
828 /* the middle of the client will be the middle of the frame */
829 *y += (self->size.bottom - self->size.top) / 2;
831 case SouthWestGravity:
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;
840 /* the client's position won't move */
841 *y += self->size.top - self->client->border_width;
846 void frame_rect_to_frame(ObFrame *self, Rect *r)
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);
853 void frame_rect_to_client(ObFrame *self, Rect *r)
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);
860 static void flash_done(gpointer data)
862 ObFrame *self = data;
864 if (self->focused != self->flash_on)
865 frame_adjust_focus(self, self->focused);
868 static gboolean flash_timeout(gpointer data)
870 ObFrame *self = data;
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;
880 return FALSE; /* we are done */
882 self->flash_on = !self->flash_on;
883 if (!self->focused) {
884 frame_adjust_focus(self, self->flash_on);
885 self->focused = FALSE;
889 return TRUE; /* go again */
892 void frame_flash_start(ObFrame *self)
894 self->flash_on = self->focused;
897 self->flash_timer = g_timeout_add_full(G_PRIORITY_DEFAULT,
898 600, flash_timeout, self,
900 g_get_current_time(&self->flash_end);
901 g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
903 self->flashing = TRUE;
906 void frame_flash_stop(ObFrame *self)
908 self->flashing = FALSE;
911 static gulong frame_animate_iconify_time_left(ObFrame *self,
915 sec = self->iconify_animation_end.tv_sec - now->tv_sec;
916 usec = self->iconify_animation_end.tv_usec - now->tv_usec;
918 usec += G_USEC_PER_SEC;
921 /* no negative values */
922 return MAX(sec * G_USEC_PER_SEC + usec, 0);
925 static gboolean frame_animate_iconify(gpointer p)
929 gint iconx, icony, iconw;
934 if (self->client->icon_geometry.width == 0) {
935 /* there is no icon geometry set so just go straight down */
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;
943 iconx = self->client->icon_geometry.x;
944 icony = self->client->icon_geometry.y;
945 iconw = self->client->icon_geometry.width;
948 iconifying = self->iconify_animation_going > 0;
950 /* how far do we have left to go ? */
951 g_get_current_time(&now);
952 time = frame_animate_iconify_time_left(self, &now);
954 if ((time > 0 && iconifying) || (time == 0 && !iconifying)) {
955 /* start where the frame is supposed to be */
958 w = self->area.width;
959 h = self->area.height;
961 /* start at the icon */
965 h = self->size.top; /* just the titlebar */
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; }
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 */
985 XMoveResizeWindow(obt_display, self->window, x, y, w, h);
988 frame_end_iconify_animation(self);
991 return time > 0; /* repeat until we're out of time */
994 void frame_end_iconify_animation(ObFrame *self)
996 /* see if there is an animation going */
997 if (self->iconify_animation_going == 0) return;
1000 XUnmapWindow(obt_display, self->window);
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);
1010 /* we're not animating any more ! */
1011 self->iconify_animation_going = 0;
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);
1021 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying)
1024 gboolean new_anim = FALSE;
1025 gboolean set_end = TRUE;
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))
1033 /* get the current time */
1034 g_get_current_time(&now);
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);
1043 /* animation was already going in the same direction */
1047 self->iconify_animation_going = iconifying ? 1 : -1;
1049 /* set the ending time */
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);
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);
1065 /* do the first step */
1066 frame_animate_iconify(self);
1068 /* show it during the animation even if it is not "visible" */
1070 XMapWindow(obt_display, self->window);