mutter/src/compositor.c

2017 lines
46 KiB
C
Raw Normal View History

2003-11-16 04:35:16 +00:00
/*
* Copyright (C) 2003, 2004, 2005, 2006 Red Hat, Inc.
* Copyright (C) 2003 Keith Packard
2003-11-16 04:35:16 +00:00
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <config.h>
#include "compositor.h"
#include "screen.h"
#include "errors.h"
#include "window.h"
#include "frame.h"
#include "workspace.h"
#include <math.h>
#include <stdlib.h>
#ifdef HAVE_COMPOSITE_EXTENSIONS
#include <cm/node.h>
#include <cm/drawable-node.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include <cm/ws.h>
#include <cm/wsint.h>
#include <cm/stacker.h>
#include <cm/cube.h>
#include <cm/rotation.h>
#include <X11/extensions/shape.h>
2003-11-16 04:35:16 +00:00
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xfixes.h>
2003-11-16 04:35:16 +00:00
#include <X11/extensions/Xrender.h>
#include "spring-model.h"
#include <cm/state.h>
2003-11-16 04:35:16 +00:00
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#define FRAME_INTERVAL_MILLISECONDS ((int)(1000.0/40.0))
#ifdef HAVE_COMPOSITE_EXTENSIONS
/* Screen specific information */
2003-11-16 04:35:16 +00:00
typedef struct
{
/* top of stack is first in list */
GList *compositor_nodes;
WsWindow *glw;
int idle_id;
} ScreenInfo;
2003-11-16 04:35:16 +00:00
typedef struct MoveInfo MoveInfo;
2003-11-16 04:35:16 +00:00
struct MetaCompositor
{
MetaDisplay *meta_display;
WsDisplay *display;
GHashTable *window_hash;
guint repair_idle;
2003-11-16 04:35:16 +00:00
guint enabled : 1;
guint have_composite : 1;
guint have_damage : 1;
guint have_fixes : 1;
guint have_name_window_pixmap : 1;
guint debug_updates : 1;
GList *ignored_damage;
CmStacker *stacker;
CmCube *cube;
CmRotation *rotation;
MoveInfo *move_info;
};
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
free_window_hash_value (void *v)
{
CmDrawableNode *drawable_node = v;
}
static WsDisplay *compositor_display;
#endif /* HAVE_COMPOSITE_EXTENSIONS */
2003-11-16 04:35:16 +00:00
MetaCompositor*
meta_compositor_new (MetaDisplay *display)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
MetaCompositor *compositor;
2003-11-16 04:35:16 +00:00
compositor = g_new0 (MetaCompositor, 1);
if (!compositor_display)
{
gboolean has_extensions;
compositor_display = ws_display_new (NULL);
has_extensions =
ws_display_init_composite (compositor_display) &&
ws_display_init_damage (compositor_display) &&
ws_display_init_fixes (compositor_display) &&
ws_display_init_test (compositor_display);
if (!has_extensions)
{
g_warning ("Disabling compositor since the server is missing at "
"least one of the COMPOSITE, DAMAGE, FIXES or TEST "
"extensions");
return NULL;
}
ws_display_set_ignore_grabs (compositor_display, TRUE);
}
compositor->display = compositor_display;
ws_display_set_synchronize (compositor_display,
getenv ("METACITY_SYNC") != NULL);
2003-11-16 04:35:16 +00:00
compositor->meta_display = display;
2003-11-16 04:35:16 +00:00
compositor->window_hash =
g_hash_table_new_full (meta_unsigned_long_hash,
meta_unsigned_long_equal,
NULL,
free_window_hash_value);
2003-11-16 04:35:16 +00:00
compositor->enabled = TRUE;
compositor->cube = cm_cube_new ();
compositor->stacker = cm_stacker_new ();
compositor->rotation = cm_rotation_new (CM_NODE (compositor->cube));
cm_cube_set_face (compositor->cube, 0, CM_NODE (compositor->stacker));
cm_cube_set_face (compositor->cube, 1, CM_NODE (compositor->stacker));
cm_cube_set_face (compositor->cube, 2, CM_NODE (compositor->stacker));
cm_cube_set_face (compositor->cube, 3, CM_NODE (compositor->stacker));
cm_cube_set_face (compositor->cube, 4, CM_NODE (compositor->stacker));
cm_cube_set_face (compositor->cube, 5, CM_NODE (compositor->stacker));
2003-11-16 04:35:16 +00:00
return compositor;
#else /* HAVE_COMPOSITE_EXTENSIONS */
return NULL;
2003-11-16 04:35:16 +00:00
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
void
meta_compositor_set_debug_updates (MetaCompositor *compositor,
gboolean debug_updates)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
compositor->debug_updates = !!debug_updates;
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
remove_repair_idle (MetaCompositor *compositor)
{
if (compositor->repair_idle)
{
meta_topic (META_DEBUG_COMPOSITOR, "Damage idle removed\n");
g_source_remove (compositor->repair_idle);
compositor->repair_idle = 0;
}
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
2003-11-16 04:35:16 +00:00
void
meta_compositor_unref (MetaCompositor *compositor)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
/* There isn't really a refcount at the moment since
* there's no ref()
*/
remove_repair_idle (compositor);
if (compositor->window_hash)
g_hash_table_destroy (compositor->window_hash);
2003-11-16 04:35:16 +00:00
g_free (compositor);
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
draw_windows (MetaScreen *screen,
GList *list)
{
CmNode *node;
if (!list)
return;
node = list->data;
draw_windows (screen, list->next);
#if 0
g_print ("rendering: %p\n", node);
#endif
cm_node_render (node, NULL);
}
static MetaScreen *
node_get_screen (Display *dpy,
CmDrawableNode *node)
{
/* FIXME: we should probably have a reverse mapping
* from nodes to screens
*/
Screen *screen = XDefaultScreenOfDisplay (dpy);
return meta_screen_for_x_screen (screen);
}
static void
handle_restacking (MetaCompositor *compositor,
CmDrawableNode *node,
CmDrawableNode *above)
{
GList *window_link, *above_link;
MetaScreen *screen;
ScreenInfo *scr_info;
screen = node_get_screen (compositor->meta_display->xdisplay, node);
scr_info = screen->compositor_data;
window_link = g_list_find (scr_info->compositor_nodes, node);
above_link = g_list_find (scr_info->compositor_nodes, above);
if (!window_link || !above_link)
return;
if (window_link == above_link)
{
/* This can happen if the topmost window is raised above
* the GL window
*/
return;
}
#if 0
g_print ("restacking\n");
#endif
if (window_link->next != above_link)
{
ScreenInfo *scr_info = screen->compositor_data;
scr_info->compositor_nodes =
g_list_delete_link (scr_info->compositor_nodes, window_link);
scr_info->compositor_nodes =
g_list_insert_before (scr_info->compositor_nodes, above_link, node);
}
cm_stacker_restack_child (compositor->stacker, CM_NODE (node), CM_NODE (above));
}
static void
process_configure_notify (MetaCompositor *compositor,
XConfigureEvent *event)
{
WsWindow *above_window;
CmDrawableNode *node = g_hash_table_lookup (compositor->window_hash,
&event->window);
CmDrawableNode *above_node;
MetaScreen *screen;
ScreenInfo *scr_info;
#if 0
g_print ("processing configure\n");
#endif
if (!node)
return;
#if 0
g_print ("we do now have a node\n");
#endif
screen = node_get_screen (compositor->meta_display->xdisplay, node);
scr_info = screen->compositor_data;
above_window = ws_window_lookup (WS_RESOURCE (node->drawable)->display,
event->above);
if (above_window == scr_info->glw)
{
above_node = scr_info->compositor_nodes->data;
}
else
{
above_node = g_hash_table_lookup (compositor->window_hash,
&event->above);
}
#if 0
cm_drawable_node_set_size (node,
event->x, event->y, event->width, event->height);
#endif
handle_restacking (compositor, node, above_node);
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_expose (MetaCompositor *compositor,
XExposeEvent *event)
{
/* FIXME: queue repaint */
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void queue_repaint (CmDrawableNode *node, gpointer data);
typedef struct
{
CmDrawableNode *node;
GTimer *timer;
} FadeInfo;
#define FADE_TIME 0.3
static gboolean
fade_in (gpointer data)
{
FadeInfo *info = data;
gdouble elapsed = g_timer_elapsed (info->timer, NULL);
gdouble alpha;
if (elapsed > FADE_TIME)
alpha = 1.0;
else
alpha = elapsed / FADE_TIME;
cm_drawable_node_set_alpha (info->node, alpha);
if (elapsed >= FADE_TIME)
{
return FALSE;
}
else
{
return TRUE;
}
}
static gboolean
fade_out (gpointer data)
{
FadeInfo *info = data;
gdouble elapsed = g_timer_elapsed (info->timer, NULL);
gdouble alpha;
if (elapsed > FADE_TIME)
alpha = 0.0;
else
alpha = 1 - (elapsed / FADE_TIME);
cm_drawable_node_set_alpha (info->node, alpha);
#if 0
g_print ("fade out: %f\n", alpha);
#endif
if (elapsed >= FADE_TIME)
{
g_object_unref (info->node);
cm_drawable_node_set_viewable (info->node, FALSE);
return FALSE;
}
else
{
return TRUE;
}
}
#endif
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_map (MetaCompositor *compositor,
XMapEvent *event)
{
CmDrawableNode *node;
MetaScreen *screen;
/* FIXME: do we sometimes get mapnotifies for windows that are
* not (direct) children of the root?
*/
/* See if window was mapped as child of root */
screen = meta_display_screen_for_root (compositor->meta_display,
event->event);
if (screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"MapNotify received on non-root 0x%lx for 0x%lx\n",
event->event, event->window);
return; /* MapNotify wasn't for a child of the root */
}
#if 0
g_print ("processing map for %lx\n", event->window);
#endif
node = g_hash_table_lookup (compositor->window_hash,
&event->window);
if (node == NULL)
{
XWindowAttributes attrs;
meta_error_trap_push_with_return (compositor->meta_display);
XGetWindowAttributes (compositor->meta_display->xdisplay,
event->window, &attrs);
if (meta_error_trap_pop_with_return (compositor->meta_display, TRUE) != Success)
{
meta_topic (META_DEBUG_COMPOSITOR, "Failed to get attributes for window 0x%lx\n",
event->window);
}
else
{
meta_compositor_add_window (compositor,
event->window, &attrs);
}
}
else
{
cm_drawable_node_update_pixmap (node);
cm_drawable_node_set_alpha (node, 1.0);
FadeInfo *info = g_new (FadeInfo, 1);
info->node = g_object_ref (node);
info->timer = g_timer_new ();
cm_drawable_node_set_viewable (node, TRUE);
}
queue_repaint (node, screen);
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_unmap (MetaCompositor *compositor,
XUnmapEvent *event)
{
CmDrawableNode *node;
MetaScreen *screen;
/* See if window was unmapped as child of root */
screen = meta_display_screen_for_root (compositor->meta_display,
event->event);
if (screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"UnmapNotify received on non-root 0x%lx for 0x%lx\n",
event->event, event->window);
return; /* UnmapNotify wasn't for a child of the root */
}
#if 0
g_print ("processing unmap on %lx\n", event->window);
#endif
node = g_hash_table_lookup (compositor->window_hash,
&event->window);
if (node != NULL)
{
FadeInfo *info = g_new (FadeInfo, 1);
info->node = g_object_ref (node);
info->timer = g_timer_new ();
g_idle_add (fade_out, info);
}
queue_repaint (node, screen);
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_create (MetaCompositor *compositor,
XCreateWindowEvent *event)
{
MetaScreen *screen;
XWindowAttributes attrs;
screen = meta_display_screen_for_root (compositor->meta_display,
event->parent);
if (screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"CreateNotify received on non-root 0x%lx for 0x%lx\n",
event->parent, event->window);
return;
}
meta_error_trap_push_with_return (compositor->meta_display);
XGetWindowAttributes (compositor->meta_display->xdisplay,
event->window, &attrs);
if (meta_error_trap_pop_with_return (compositor->meta_display, TRUE) != Success)
{
meta_topic (META_DEBUG_COMPOSITOR, "Failed to get attributes for window 0x%lx\n",
event->window);
}
else
{
#if 0
g_print (//META_DEBUG_COMPOSITOR,
"Create window 0x%lx, adding\n", event->window);
#endif
meta_compositor_add_window (compositor,
event->window, &attrs);
}
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_destroy (MetaCompositor *compositor,
XDestroyWindowEvent *event)
{
MetaScreen *screen;
screen = meta_display_screen_for_root (compositor->meta_display,
event->event);
if (screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"DestroyNotify received on non-root 0x%lx for 0x%lx\n",
event->event, event->window);
return;
}
meta_topic (META_DEBUG_COMPOSITOR,
"Destroy window 0x%lx\n", event->window);
meta_compositor_remove_window (compositor, event->window);
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
process_reparent (MetaCompositor *compositor,
XReparentEvent *event)
{
/* Reparent from one screen to another doesn't happen now, but
* it's been suggested as a future extension
*/
MetaScreen *event_screen;
MetaScreen *parent_screen;
CmDrawableNode *node;
XWindowAttributes attrs;
event_screen = meta_display_screen_for_root (compositor->meta_display,
event->event);
if (event_screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"ReparentNotify received on non-root 0x%lx for 0x%lx\n",
event->event, event->window);
return;
}
#if 0
g_print (//META_DEBUG_COMPOSITOR,
"Reparent window 0x%lx new parent 0x%lx received on 0x%lx\n",
event->window, event->parent, event->event);
#endif
parent_screen = meta_display_screen_for_root (compositor->meta_display,
event->parent);
if (parent_screen == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"ReparentNotify 0x%lx to a non-screen or unmanaged screen 0x%lx\n",
event->window, event->parent);
meta_compositor_remove_window (compositor, event->window);
return;
}
node = g_hash_table_lookup (compositor->window_hash,
&event->window);
meta_error_trap_push_with_return (compositor->meta_display);
XGetWindowAttributes (compositor->meta_display->xdisplay,
event->window, &attrs);
if (meta_error_trap_pop_with_return (compositor->meta_display, TRUE) != Success)
{
meta_topic (META_DEBUG_COMPOSITOR, "Failed to get attributes for window 0x%lx\n",
event->window);
}
else
{
meta_topic (META_DEBUG_COMPOSITOR,
"Reparent window 0x%lx into screen 0x%lx, adding\n",
event->window, event->parent);
meta_compositor_add_window (compositor,
event->window, &attrs);
}
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
2003-11-16 04:35:16 +00:00
void
meta_compositor_process_event (MetaCompositor *compositor,
XEvent *event,
2003-11-16 04:35:16 +00:00
MetaWindow *window)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
if (!compositor->enabled)
return; /* no extension */
/* FIXME support CirculateNotify */
if (event->type == ConfigureNotify)
{
process_configure_notify (compositor,
(XConfigureEvent*) event);
}
else if (event->type == Expose)
{
process_expose (compositor,
(XExposeEvent*) event);
}
else if (event->type == UnmapNotify)
{
process_unmap (compositor,
(XUnmapEvent*) event);
}
else if (event->type == MapNotify)
{
process_map (compositor,
(XMapEvent*) event);
}
else if (event->type == ReparentNotify)
{
process_reparent (compositor,
(XReparentEvent*) event);
}
else if (event->type == CreateNotify)
{
process_create (compositor,
(XCreateWindowEvent*) event);
}
else if (event->type == DestroyNotify)
{
process_destroy (compositor,
(XDestroyWindowEvent*) event);
}
2003-11-16 04:35:16 +00:00
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
wavy (double time,
double in_x, double in_y,
double *out_x, double *out_y,
gpointer data)
{
static int m;
time = time * 5;
double dx = 0.0025 * sin (time + 35 * in_y);
double dy = 0.0025 * cos (time + 35 * in_x);
*out_x = in_x + dx;
*out_y = in_y + dy;
m++;
}
static void
update_frame_counter (void)
{
#define BUFSIZE 128
static GTimer *timer;
static double buffer [BUFSIZE];
static int next = 0;
if (!timer)
timer = g_timer_new ();
buffer[next++] = g_timer_elapsed (timer, NULL);
if (next == BUFSIZE)
{
int i;
double total;
next = 0;
total = 0.0;
for (i = 1; i < BUFSIZE; ++i)
total += buffer[i] - buffer[i - 1];
g_print ("frames per second: %f\n", 1 / (total / (BUFSIZE - 1)));
}
}
static GTimer *timer;
static gboolean
update (gpointer data)
{
MetaScreen *screen = data;
ScreenInfo *scr_info = screen->compositor_data;
WsWindow *gl_window = scr_info->glw;
gdouble angle;
glViewport (0, 0, screen->rect.width, screen->rect.height);
if (!timer)
timer = g_timer_new ();
#if 0
g_print ("rotation: %f\n", 360 * g_timer_elapsed (timer, NULL));
#endif
angle = g_timer_elapsed (timer, NULL) * 90;
#if 0
angle = 180.0;
#endif
cm_rotation_set_rotation (screen->display->compositor->rotation,
angle,
0.0, 1.0, 0.0);
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable (GL_TEXTURE_2D);
glDisable (GL_DEPTH_TEST);
ws_window_raise (gl_window);
#if 0
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
#endif
#if 0
glTranslatef (-1.0, -1.0, 0.0);
#endif
#if 0
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
gluPerspective( 45.0f, 1.0, 0.1f, 10.0f );
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
glTranslatef (0, 0, -3);
glEnable (GL_DEPTH_TEST);
#endif
#if 0
draw_windows (screen, scr_info->compositor_nodes);
#endif
/* FIXME: we should probably grab the server around the raise/swap
*/
CmState *state = cm_state_new ();
cm_state_disable_depth_buffer_update (state);
cm_node_render (CM_NODE (screen->display->compositor->stacker), state);
cm_state_enable_depth_buffer_update (state);
g_object_unref (state);
#if 0
ws_display_grab (ws_drawable_get_display ((WsDrawable *)gl_window));
#endif
ws_window_gl_swap_buffers (gl_window);
glFinish();
update_frame_counter ();
scr_info->idle_id = 0;
return FALSE;
}
static void
queue_repaint (CmDrawableNode *node, gpointer data)
{
MetaScreen *screen = data;
ScreenInfo *scr_info = screen->compositor_data;
#if 0
g_print ("metacity queueing repaint for %p\n", node);
#endif
if (!scr_info)
{
/* compositor has been turned off */
return;
}
if (!scr_info->idle_id)
{
scr_info->idle_id = g_idle_add (update, screen);
#if 0
g_print ("done\n");
#endif
}
else
{
#if 0
g_print ("one was queued already\n");
#endif
}
}
#endif /* HAVE_COMPOSITE_EXTENSIONS */
#ifdef HAVE_COMPOSITE_EXTENSIONS
static void
dump_stacking_order (GList *nodes)
{
GList *list;
for (list = nodes; list != NULL; list = list->next)
{
CmDrawableNode *node = list->data;
g_print ("%lx, ", WS_RESOURCE_XID (node->drawable));
}
g_print ("\n");
}
#endif
2003-11-16 04:35:16 +00:00
/* This is called when metacity does its XQueryTree() on startup
* and when a new window is mapped.
2003-11-16 04:35:16 +00:00
*/
void
meta_compositor_add_window (MetaCompositor *compositor,
Window xwindow,
XWindowAttributes *attrs)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
CmDrawableNode *node;
MetaScreen *screen;
WsDrawable *drawable;
ScreenInfo *scr_info;
2003-11-16 04:35:16 +00:00
if (!compositor->enabled)
return; /* no extension */
screen = meta_screen_for_x_screen (attrs->screen);
g_assert (screen != NULL);
node = g_hash_table_lookup (compositor->window_hash,
&xwindow);
#if 0
g_print ("adding %lx\n", xwindow);
#endif
if (node != NULL)
{
g_print ("window %lx already added\n", xwindow);
meta_topic (META_DEBUG_COMPOSITOR,
"Window 0x%lx already added\n", xwindow);
return;
}
ws_display_begin_error_trap (compositor->display);
drawable = (WsDrawable *)ws_window_lookup (compositor->display, xwindow);
scr_info = screen->compositor_data;
ws_display_end_error_trap (compositor->display);
if (!drawable)
return;
g_assert (scr_info);
ws_display_begin_error_trap (compositor->display);
if (ws_window_query_input_only ((WsWindow *)drawable) ||
drawable == (WsDrawable *)scr_info->glw)
{
ws_display_end_error_trap (compositor->display);
return;
}
ws_display_end_error_trap (compositor->display);
node = cm_drawable_node_new (drawable);
cm_stacker_add_child (compositor->stacker, CM_NODE (node));
g_object_unref (node);
cm_drawable_node_set_damage_func (node, queue_repaint, screen);
#if 0
drawable_node_set_deformation_func (node, wavy, NULL);
#endif
/* FIXME: we should probably just store xid's directly */
g_hash_table_insert (compositor->window_hash,
&(WS_RESOURCE (node->drawable)->xid), node);
/* assume cwindow is at the top of the stack as it was either just
* created or just reparented to the root window
*/
scr_info->compositor_nodes = g_list_prepend (scr_info->compositor_nodes,
node);
#if 0
dump_stacking_order (scr_info->compositor_nodes);
#endif
2003-11-16 04:35:16 +00:00
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
void
meta_compositor_remove_window (MetaCompositor *compositor,
Window xwindow)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
CmDrawableNode *node;
MetaScreen *screen;
ScreenInfo *scr_info;
if (!compositor->enabled)
return; /* no extension */
node = g_hash_table_lookup (compositor->window_hash,
&xwindow);
if (node == NULL)
{
meta_topic (META_DEBUG_COMPOSITOR,
"Window 0x%lx already removed\n", xwindow);
return;
}
screen = node_get_screen (compositor->meta_display->xdisplay, node);
scr_info = screen->compositor_data;
scr_info->compositor_nodes = g_list_remove (scr_info->compositor_nodes,
node);
/* Frees node as side effect */
g_hash_table_remove (compositor->window_hash,
&xwindow);
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
2003-11-16 04:35:16 +00:00
static gboolean
cont_update (gpointer data)
{
update (data);
return TRUE;
}
void
meta_compositor_manage_screen (MetaCompositor *compositor,
MetaScreen *screen)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
ScreenInfo *scr_info = g_new0 (ScreenInfo, 1);
WsScreen *ws_screen =
ws_display_get_screen_from_number (compositor->display, screen->number);
WsWindow *root = ws_screen_get_root_window (ws_screen);
WsRegion *region;
Window current_cm_sn_owner;
WsWindow *new_cm_sn_owner;
Display *xdisplay;
Atom cm_sn_atom;
char buf[128];
if (screen->compositor_data)
return;
scr_info->glw = ws_screen_get_gl_window (ws_screen);
scr_info->compositor_nodes = NULL;
scr_info->idle_id = 0;
g_print ("setting compositor_data for screen %p to %p\n", screen, scr_info);
screen->compositor_data = scr_info;
ws_display_init_composite (compositor->display);
ws_display_init_damage (compositor->display);
ws_display_init_fixes (compositor->display);
g_print ("redirecting\n");
ws_window_redirect_subwindows (root);
ws_window_set_override_redirect (scr_info->glw, TRUE);
ws_window_unredirect (scr_info->glw);
region = ws_region_new (compositor->display);
ws_window_set_input_shape (scr_info->glw, region);
g_object_unref (G_OBJECT (region));
xdisplay = ws_screen->display->xdisplay;
snprintf(buf, sizeof(buf), "CM_S%d", screen->number);
cm_sn_atom = XInternAtom (xdisplay, buf, False);
current_cm_sn_owner = XGetSelectionOwner (xdisplay, cm_sn_atom);
if (current_cm_sn_owner != None)
{
meta_warning (_("Screen %d on display \"%s\" already has a compositing manager\n"),
screen->number, ",madgh");
}
new_cm_sn_owner = ws_screen_get_root_window (ws_screen);
XSetSelectionOwner (xdisplay, cm_sn_atom, WS_RESOURCE_XID (new_cm_sn_owner),
CurrentTime);
ws_window_map (scr_info->glw);
ws_display_sync (compositor->display);
#if 0
g_idle_add (cont_update, screen);
#endif
#if 0
children = ws_window_list_children (root);
#endif
#endif
}
2003-11-16 04:35:16 +00:00
void
meta_compositor_unmanage_screen (MetaCompositor *compositor,
MetaScreen *screen)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
ScreenInfo *scr_info = screen->compositor_data;
WsScreen *ws_screen =
ws_display_get_screen_from_number (compositor->display, screen->number);
WsWindow *root = ws_screen_get_root_window (ws_screen);
if (!compositor->enabled)
return; /* no extension */
while (scr_info->compositor_nodes != NULL)
{
CmDrawableNode *node = scr_info->compositor_nodes->data;
meta_compositor_remove_window (compositor,
WS_RESOURCE (node->drawable)->xid);
}
ws_window_raise (scr_info->glw);
g_print ("unredirecting\n");
ws_window_unredirect_subwindows (root);
ws_window_unmap (scr_info->glw);
/* We need to sync here, because if someone is furiously
* clicking the 'compositing manager' check box, we might
* attempt to redirect the window again before this unredirect
* has reached the server
*/
ws_display_sync (compositor->display);
screen->compositor_data = NULL;
#endif /* HAVE_COMPOSITE_EXTENSIONS */
}
2003-11-16 04:35:16 +00:00
#ifdef HAVE_COMPOSITE_EXTENSIONS
static CmDrawableNode *
window_to_node (MetaCompositor *compositor,
MetaWindow *window)
{
Window xwindow;
CmDrawableNode *node;
if (window->frame)
xwindow = window->frame->xwindow;
else
xwindow = window->xwindow;
node = g_hash_table_lookup (compositor->window_hash,
&xwindow);
return node;
}
#endif
typedef struct
{
double x;
double y;
double width;
double height;
} DoubleRect;
#if 0
static gdouble
interpolate (gdouble t, gdouble begin, gdouble end, double power)
{
return (begin + (end - begin) * pow (t, power));
}
#endif
#if 0
static gboolean
stop_minimize (gpointer data)
{
MiniInfo *info = data;
g_source_remove (info->repaint_id);
cm_drawable_node_set_deformation_func (info->node, NULL, NULL);
if (info->finished_func)
info->finished_func (info->finished_data);
g_free (info);
return FALSE;
}
#endif
#if 0
static void
minimize_deformation (gdouble time,
double in_x,
double in_y,
double *out_x,
double *out_y,
gpointer data)
{
#define MINIMIZE_TIME 0.5
MiniInfo *info = data;
gdouble elapsed;
gdouble pos;
if (info->start_time == -1)
info->start_time = time;
elapsed = time - info->start_time;
pos = elapsed / MINIMIZE_TIME;
*out_x = interpolate (pos, in_x, info->target.x + info->target.width * ((in_x - info->start.x) / info->start.width), 10 * in_y);
*out_y = interpolate (pos, in_y, info->target.y + info->target.height * ((in_y - info->start.y) / info->start.height), 1.0);
if (elapsed > MINIMIZE_TIME)
{
g_assert (info->node);
if (!info->idle_id)
info->idle_id = g_idle_add (stop_minimize, info);
}
}
#endif
#ifdef HAVE_COMPOSITE_EXTENSIONS
static gdouble
interpolate (gdouble t, gdouble begin, gdouble end, double power)
{
return (begin + (end - begin) * pow (t, power));
}
static void
interpolate_rectangle (gdouble t,
WsRectangle * from,
WsRectangle * to,
WsRectangle * result)
{
if (!result)
return;
result->x = interpolate (t, from->x, to->x, 1);
result->y = interpolate (t, from->y, to->y, 1);
result->width = interpolate (t, from->width, to->width, 1);
result->height = interpolate (t, from->height, to->height, 1);
}
#endif
#define MINIMIZE_STYLE 3
#ifndef HAVE_COMPOSITE_EXTENSIONS
#undef MINIMIZE_STYLE
#define MINIMIZE_STYLE 0
#endif
#if MINIMIZE_STYLE == 0
void
meta_compositor_minimize (MetaCompositor *compositor,
MetaWindow *window,
int x,
int y,
int width,
int height,
MetaAnimationFinishedFunc finished,
gpointer data)
{
}
#elif MINIMIZE_STYLE == 1
typedef struct
{
CmDrawableNode *node;
GTimer *timer;
MetaCompositor *compositor;
ScreenInfo *scr_info;
MetaAnimationFinishedFunc finished_func;
gpointer finished_data;
gdouble aspect_ratio;
WsRectangle current_geometry;
WsRectangle target_geometry;
gdouble current_alpha;
gdouble target_alpha;
int button_x;
int button_y;
int button_width;
int button_height;
/* FIXME: maybe would be simpler if all of this was an array */
gboolean phase_1_started;
gboolean phase_2_started;
gboolean phase_3_started;
gboolean phase_4_started;
gboolean phase_5_started;
} MiniInfo;
static void
set_geometry (MiniInfo *info, gdouble elapsed)
{
WsRectangle rect;
interpolate_rectangle (elapsed, &info->current_geometry, &info->target_geometry, &rect);
#if 0
g_print ("y: %d %d (%f => %d)\n", info->current_geometry.y, info->target_geometry.y,
elapsed, rect.y);
g_print ("setting: %d %d %d %d\n", rect.x, rect.y, rect.width, rect.height);
#endif
cm_drawable_node_set_geometry (info->node,
rect.x, rect.y,
rect.width, rect.height);
}
static int
center (gdouble what, gdouble in)
{
return (in - what) / 2.0 + 0.5;
}
static void
run_phase_1 (MiniInfo *info, gdouble elapsed)
{
if (!info->phase_1_started)
{
#if 0
g_print ("starting phase 1\n");
#endif
info->phase_1_started = TRUE;
info->current_geometry.x = info->node->real_x;
info->current_geometry.y = info->node->real_y;
info->current_geometry.width = info->node->real_width;
info->current_geometry.height = info->node->real_height;
info->target_geometry.height = info->button_height;
info->target_geometry.width = info->button_height * info->aspect_ratio;
info->target_geometry.x = info->button_x + center (info->target_geometry.width, info->button_width);
info->target_geometry.y = info->node->real_y + center (info->button_height, info->node->real_height);
handle_restacking (info->compositor, info->node,
info->scr_info->compositor_nodes->data);
}
set_geometry (info, elapsed);
}
static void
run_phase_2 (MiniInfo *info, gdouble elapsed)
{
#define WOBBLE_FACTOR 3
if (!info->phase_2_started)
{
WsRectangle cur = info->target_geometry;
g_print ("starting phase 2\n");
info->phase_2_started = TRUE;
info->current_geometry = cur;
info->target_geometry.x = cur.x + center (WOBBLE_FACTOR * cur.width, cur.width);
info->target_geometry.y = cur.y + center (WOBBLE_FACTOR * cur.height, cur.height);
info->target_geometry.width = cur.width * WOBBLE_FACTOR;
info->target_geometry.height = cur.height * WOBBLE_FACTOR;
}
set_geometry (info, elapsed);
}
static void
run_phase_3 (MiniInfo *info, gdouble elapsed)
{
if (!info->phase_3_started)
{
WsRectangle cur = info->target_geometry;
g_print ("starting phase 3\n");
info->phase_3_started = TRUE;
info->current_geometry = cur;
info->target_geometry.height = info->button_height;
info->target_geometry.width = info->button_height * info->aspect_ratio;
info->target_geometry.x = info->button_x + center (info->target_geometry.width, info->button_width);
info->target_geometry.y = info->node->real_y + center (info->button_height, info->node->real_height);
}
set_geometry (info, elapsed);
}
static void
run_phase_4 (MiniInfo *info, gdouble elapsed)
{
if (!info->phase_4_started)
{
WsRectangle cur = info->target_geometry;
g_print ("starting phase 4\n");
info->phase_4_started = TRUE;
info->current_geometry = cur;
info->target_geometry.height = info->button_height;
info->target_geometry.width = info->button_height * info->aspect_ratio;
info->target_geometry.x = cur.x;
g_print ("button y: %d\n", info->button_y);
info->target_geometry.y = info->button_y;
}
set_geometry (info, elapsed);
}
static void
run_phase_5 (MiniInfo *info, gdouble elapsed)
{
if (!info->phase_5_started)
{
WsRectangle cur = info->target_geometry;
g_print ("starting phase 5\n");
info->phase_5_started = TRUE;
info->current_geometry = cur;
info->target_geometry.x = info->button_x;
info->target_geometry.y = info->button_y;
info->target_geometry.width = info->button_width;
info->target_geometry.height = info->button_height;
}
set_geometry (info, elapsed);
cm_drawable_node_set_alpha (info->node, 1 - elapsed);
}
static gboolean
run_animation_01 (gpointer data)
{
MiniInfo *info = data;
gdouble elapsed;
elapsed = g_timer_elapsed (info->timer, NULL);
#define PHASE_0 0.0
#define PHASE_1 0.225 /* scale to size of button */
#define PHASE_2 0.325 /* scale up a little */
#define PHASE_3 0.425 /* scale back a little */
#define PHASE_4 0.650 /* move to button */
#define PHASE_5 1.0 /* fade out */
if (elapsed < PHASE_1)
{
/* phase one */
run_phase_1 (info, (elapsed - PHASE_0)/(PHASE_1 - PHASE_0));
}
else if (elapsed < PHASE_2)
{
/* phase two */
run_phase_2 (info, (elapsed - PHASE_1)/(PHASE_2 - PHASE_1));
}
else if (elapsed < PHASE_3)
{
/* phase three */
run_phase_3 (info, (elapsed - PHASE_2)/(PHASE_3 - PHASE_2));
}
else if (elapsed < PHASE_4)
{
/* phase four */
run_phase_4 (info, (elapsed - PHASE_3)/(PHASE_4 - PHASE_3));
}
else if (elapsed < PHASE_5)
{
/* phase five */
run_phase_5 (info, (elapsed - PHASE_4)/(PHASE_5 - PHASE_4));
}
else
{
cm_drawable_node_set_viewable (info->node, FALSE);
cm_drawable_node_unset_geometry (info->node);
cm_drawable_node_set_alpha (info->node, 1.0);
if (info->finished_func)
info->finished_func (info->finished_data);
return FALSE;
}
return TRUE;
}
void
meta_compositor_minimize (MetaCompositor *compositor,
MetaWindow *window,
int x,
int y,
int width,
int height,
MetaAnimationFinishedFunc finished,
gpointer data)
{
MiniInfo *info = g_new (MiniInfo, 1);
CmDrawableNode *node = window_to_node (compositor, window);
WsRectangle start;
MetaScreen *screen = window->screen;
info->node = node;
info->timer = g_timer_new ();
info->finished_func = finished;
info->finished_data = data;
info->phase_1_started = FALSE;
info->phase_2_started = FALSE;
info->phase_3_started = FALSE;
info->phase_4_started = FALSE;
info->phase_5_started = FALSE;
info->button_x = x;
info->button_y = y;
info->button_width = width;
info->button_height = height;
info->compositor = compositor;
info->scr_info = screen->compositor_data;
#if 0
cm_drawable_node_set_deformation_func (node, minimize_deformation, info);
#endif
info->aspect_ratio = 1.3;
g_idle_add (run_animation_01, info);
}
void
meta_compositor_unminimize (MetaCompositor *compositor,
MetaWindow *window,
int x,
int y,
int width,
int height,
MetaAnimationFinishedFunc finished,
gpointer data)
{
finished(data);
}
#elif MINIMIZE_STYLE == 2
#if 0
static gboolean
do_minimize_animation (gpointer data)
{
MiniInfo *info = data;
double elapsed;
gboolean done = FALSE;
#define FADE_TIME 0.5
elapsed = g_timer_elapsed (info->timer, NULL);
elapsed = elapsed / FADE_TIME;
if (elapsed >= 1.0)
{
elapsed = 1.0;
done = TRUE;
}
g_print ("%f\n", elapsed);
cm_drawable_node_set_geometry (info->node,
info->node->real_x + interpolate (elapsed, 0, info->node->real_width / 2, 1),
info->node->real_y + interpolate (elapsed, 0, info->node->real_height / 2, 1),
interpolate (elapsed, info->node->real_width, 0, 1),
interpolate (elapsed, info->node->real_height, 0, 1));
if (done)
return FALSE;
#if 0
g_print ("inter: %f %f %f\n", 0, 735, interpolate (0.0, 735.0, 0.5, 1.0));
g_print ("inter x .5: %f (%d %d)\n", info->node->real_x + interpolate (0, info->node->real_width / 2, .5, 1), 0, info->node->real_width);
#endif
cm_drawable_node_set_alpha (info->node, 1 - elapsed);
if (done)
{
}
else
{
return TRUE;
}
#if 0
queue_repaint (info->node,
node_get_screen (info->window->display->xdisplay,
info->node));
#endif
}
#endif
#elif MINIMIZE_STYLE == 3
typedef struct
{
CmDrawableNode *node;
GTimer *timer;
gboolean expand;
MetaCompositor *compositor;
MetaScreen *screen;
MetaRectangle rect;
double last_time;
MetaAnimationFinishedFunc finished_func;
gpointer finished_data;
Model *model;
int button_x;
int button_y;
int button_width;
int button_height;
} MiniInfo;
#define WOBBLE_TIME 1.0
static void
set_patch (CmDrawableNode *node,
Model *model,
gdouble blend,
MetaRectangle *target)
{
int i, j;
CmPoint points[4][4];
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
{
double obj_x, obj_y;
int p_x, p_y;
model_get_position (model, i, j, &obj_x, &obj_y);
#if 0
target_x = info->node->real_x + i * info->node->real_width / 3;
target_y = info->node->real_y + j * info->node->real_height / 3;
#endif
if (target)
{
p_x = target->x + i * target->width / 3;
p_y = target->y + j * target->height / 3;
points[j][i].x = (1 - blend) * obj_x + blend * p_x;
points[j][i].y = (1 - blend) * obj_y + blend * p_y;
}
else
{
points[j][i].x = obj_x;
points[j][i].y = obj_y;
}
}
cm_drawable_node_set_patch (node, points);
}
static gboolean
run_animation (gpointer data)
{
MiniInfo *info = data;
gdouble t, blend;
double n_steps;
int i;
t = g_timer_elapsed (info->timer, NULL);
n_steps = floor ((t - info->last_time) * 75);
for (i = 0; i < n_steps; ++i)
model_step (info->model);
if (i > 0)
info->last_time = t;
blend = t / WOBBLE_TIME;
set_patch (info->node, info->model, 0.0, NULL);
if (info->expand)
cm_drawable_node_set_alpha (info->node, t / WOBBLE_TIME);
else
cm_drawable_node_set_alpha (info->node, 1.0 - t / WOBBLE_TIME);
if (t > WOBBLE_TIME)
{
cm_drawable_node_set_viewable (info->node, info->expand);
cm_drawable_node_unset_geometry (info->node);
cm_drawable_node_set_alpha (info->node, 1.0);
if (info->finished_func)
{
info->finished_func (info->finished_data);
model_destroy (info->model);
info->model = NULL;
}
return FALSE;
}
else
{
queue_repaint (info->node, info->screen);
return TRUE;
}
}
void
meta_compositor_minimize (MetaCompositor *compositor,
MetaWindow *window,
int x,
int y,
int width,
int height,
MetaAnimationFinishedFunc finished,
gpointer data)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
MiniInfo *info = g_new (MiniInfo, 1);
CmDrawableNode *node = window_to_node (compositor, window);
MetaScreen *screen = window->screen;
info->node = node;
info->timer = g_timer_new ();
info->finished_func = finished;
info->finished_data = data;
info->rect = window->user_rect;
info->model = model_new (&info->rect, FALSE);
info->last_time = 0.0;
info->expand = FALSE;
info->button_x = x;
info->button_y = y;
info->button_width = width;
info->button_height = height;
info->compositor = compositor;
info->screen = screen;
g_idle_add (run_animation, info);
#endif
}
void
meta_compositor_unminimize (MetaCompositor *compositor,
MetaWindow *window,
int x,
int y,
int width,
int height,
MetaAnimationFinishedFunc finished,
gpointer data)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
MiniInfo *info = g_new (MiniInfo, 1);
CmDrawableNode *node = window_to_node (compositor, window);
MetaScreen *screen = window->screen;
info->node = node;
info->timer = g_timer_new ();
info->finished_func = finished;
info->finished_data = data;
info->rect = window->user_rect;
info->model = model_new (&info->rect, TRUE);
info->expand = TRUE;
info->button_x = x;
info->button_y = y;
info->button_width = width;
info->button_height = height;
info->compositor = compositor;
info->screen = screen;
g_idle_add (run_animation, info);
#endif
}
#endif
void
meta_compositor_set_updates (MetaCompositor *compositor,
MetaWindow *window,
gboolean updates)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
CmDrawableNode *node = window_to_node (compositor, window);
if (node)
{
g_print ("turning updates %s\n", updates? "on" : "off");
cm_drawable_node_set_updates (node, updates);
update (window->screen);
}
#endif
}
#ifdef HAVE_COMPOSITE_EXTENSIONS
#define BALLOON_TIME 2
typedef struct
{
CmDrawableNode *node;
MetaAnimationFinishedFunc finished;
gpointer finished_data;
GTimer *timer;
} BalloonInfo;
static gboolean
blow_up (gpointer data)
{
BalloonInfo *info = data;
gdouble elapsed = g_timer_elapsed (info->timer, NULL) / BALLOON_TIME;
CmPoint points[4][4];
int i, j;
if (elapsed > BALLOON_TIME)
{
cm_drawable_node_set_viewable (info->node, FALSE);
return FALSE;
}
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
points[i][j].x = info->node->real_x + j;
points[i][j].y = info->node->real_y + i;
}
}
cm_drawable_node_set_patch (info->node, points);
return TRUE;
}
void
meta_compositor_delete_window (MetaCompositor *compositor,
MetaWindow *window,
MetaAnimationFinishedFunc finished,
gpointer data)
{
CmDrawableNode *node;
BalloonInfo *info = g_new (BalloonInfo, 1);
node = window_to_node (compositor, window);
if (!node)
{
finished (data);
return;
}
info->finished = finished;
info->finished_data = data;
info->timer = g_timer_new ();
g_idle_add (blow_up, info);
}
#endif
void
meta_compositor_destroy (MetaCompositor *compositor)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
#if 0
GSList *list;
#endif
#if 0
/* FIXME */
ws_display_free (compositor->display);
#endif
g_hash_table_destroy (compositor->window_hash);
g_free (compositor);
#endif
}
#ifdef HAVE_COMPOSITE_EXTENSIONS
struct MoveInfo
{
GTimer *timer;
gboolean finished;
Model *model;
MetaScreen *screen;
CmDrawableNode *node;
gdouble last_time;
};
#endif
#ifdef HAVE_COMPOSITE_EXTENSIONS
static gboolean
wobble (gpointer data)
{
MoveInfo *info = data;
double t = g_timer_elapsed (info->timer, NULL);
if (info->finished && model_is_calm (info->model))
{
cm_drawable_node_unset_geometry (info->node);
g_free (info);
info = NULL;
return FALSE;
}
else
{
int i;
int n_steps;
n_steps = floor ((t - info->last_time) * 75);
for (i = 0; i < n_steps; ++i)
model_step (info->model);
if (i > 0)
info->last_time = t;
set_patch (info->node, info->model, 0.0, NULL);
queue_repaint (info->node, info->screen);
return TRUE;
}
}
#endif
static void
compute_window_rect (MetaWindow *window,
MetaRectangle *rect)
{
/* FIXME: does metacity include this function somewhere? */
if (window->frame)
{
*rect = window->frame->rect;
}
else
{
*rect = window->user_rect;
}
}
void
meta_compositor_begin_move (MetaCompositor *compositor,
MetaWindow *window,
MetaRectangle *initial,
int grab_x, int grab_y)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
MetaRectangle rect;
compositor->move_info = g_new0 (MoveInfo, 1);
compositor->move_info->last_time = 0.0;
compositor->move_info->timer = g_timer_new ();
compute_window_rect (window, &rect);
#if 0
g_print ("init: %d %d\n", initial->x, initial->y);
g_print ("window: %d %d\n", window->rect.x, window->rect.y);
g_print ("frame: %d %d\n", rect.x, rect.y);
g_print ("grab: %d %d\n", grab_x, grab_y);
#endif
compositor->move_info->model = model_new (&rect, TRUE);
compositor->move_info->node = window_to_node (window->display->compositor, window);
compositor->move_info->screen = window->screen;
model_begin_move (compositor->move_info->model, grab_x, grab_y);
g_idle_add (wobble, compositor->move_info);
#endif
}
void
meta_compositor_update_move (MetaCompositor *compositor,
MetaWindow *window,
int x, int y)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
model_update_move (compositor->move_info->model, x, y);
#endif
}
void
meta_compositor_end_move (MetaCompositor *compositor,
MetaWindow *window)
{
#ifdef HAVE_COMPOSITE_EXTENSIONS
compositor->move_info->finished = TRUE;
compositor->move_info = NULL;
#endif
}