| /* |
| * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. |
| * Author: Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com> |
| * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> |
| * |
| * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Additional permission under GNU GPL version 3 section 7: |
| * |
| * If you modify this program, or any covered work, by linking or |
| * combining it with the OpenSSL project's OpenSSL library (or a |
| * modified version of that library), containing parts covered by the |
| * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. |
| * grants you additional permission to convey the resulting work. |
| * Corresponding Source for a non-source form of such a combination |
| * shall include the source code for the parts of OpenSSL used as well |
| * as that of the covered work. |
| */ |
| |
| #include "video_widget.h" |
| |
| #include <clutter/clutter.h> |
| #include <clutter-gtk/clutter-gtk.h> |
| #include <video/renderer.h> |
| |
| struct _VideoWidgetClass { |
| GtkBinClass parent_class; |
| }; |
| |
| struct _VideoWidget { |
| GtkBin parent; |
| }; |
| |
| typedef struct _VideoWidgetPrivate VideoWidgetPrivate; |
| |
| struct _VideoWidgetPrivate { |
| GtkWidget *clutter_widget; |
| ClutterActor *stage; |
| ClutterActor *video_container; |
| |
| /* remote peer data */ |
| ClutterActor *actor_remote; |
| Video::Renderer *renderer_remote; |
| QMetaObject::Connection remote_frame_update; |
| QMetaObject::Connection remote_render_stop; |
| QMetaObject::Connection remote_render_start; |
| |
| /* local peer data */ |
| ClutterActor *actor_local; |
| Video::Renderer *renderer_local; |
| QMetaObject::Connection local_frame_update; |
| }; |
| |
| G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN); |
| |
| #define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate)) |
| |
| typedef struct _FrameInfo |
| { |
| ClutterActor *image_actor; |
| ClutterContent *image; |
| guchar *data; |
| gint data_size; |
| gint width; |
| gint height; |
| } FrameInfo; |
| |
| /* |
| * video_widget_dispose() |
| * |
| * The dispose function for the video_widget class. |
| */ |
| static void |
| video_widget_dispose(GObject *gobject) |
| { |
| VideoWidget *self = VIDEO_WIDGET(gobject); |
| VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self); |
| |
| QObject::disconnect(priv->remote_frame_update); |
| QObject::disconnect(priv->remote_render_stop); |
| QObject::disconnect(priv->remote_render_start); |
| |
| G_OBJECT_CLASS(video_widget_parent_class)->dispose(gobject); |
| } |
| |
| |
| /* |
| * video_widget_finalize() |
| * |
| * The finalize function for the video_widget class. |
| */ |
| static void |
| video_widget_finalize(GObject *object) |
| { |
| G_OBJECT_CLASS(video_widget_parent_class)->finalize(object); |
| } |
| |
| |
| /* |
| * video_widget_class_init() |
| * |
| * This function init the video_widget_class. |
| */ |
| static void |
| video_widget_class_init(VideoWidgetClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS(klass); |
| |
| /* override method */ |
| object_class->dispose = video_widget_dispose; |
| object_class->finalize = video_widget_finalize; |
| } |
| |
| |
| /* |
| * video_widget_init() |
| * |
| * This function init the video_widget. |
| * - init clutter |
| * - init all the widget members |
| */ |
| static void |
| video_widget_init(VideoWidget *self) |
| { |
| VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self); |
| |
| /* init clutter widget */ |
| priv->clutter_widget = gtk_clutter_embed_new(); |
| /* add it to the video_widget */ |
| gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget); |
| /* get the stage */ |
| priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget)); |
| |
| /* layout manager is used to arrange children in space, here we ask clutter |
| * to align children to fill the space when resizing the window */ |
| clutter_actor_set_layout_manager(priv->stage, |
| clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL)); |
| |
| /* add a scene container where we can add and remove our actors */ |
| priv->video_container = clutter_actor_new(); |
| clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black); |
| clutter_actor_add_child(priv->stage, priv->video_container); |
| |
| /* TODO: handle button event in screen */ |
| // g_signal_connect(screen, "button-press-event", |
| // G_CALLBACK(on_button_press_in_screen_event_cb), |
| // self); |
| |
| /* TODO: init drag & drop images */ |
| // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE)); |
| // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self)); |
| // g_signal_connect(GTK_WIDGET(self), "drag-data-received", |
| // G_CALLBACK(on_drag_data_received_cb), NULL); |
| } |
| |
| static FrameInfo * |
| prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor) |
| { |
| const QByteArray& data = renderer->currentFrame(); |
| QSize res = renderer->size(); |
| |
| /* copy frame data */ |
| gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size()); |
| |
| FrameInfo *frame = g_new0(FrameInfo, 1); |
| |
| frame->image_actor = image_actor; |
| frame->data = (guchar *)frame_data; |
| frame->data_size = data.size(); |
| frame->width = res.width(); |
| frame->height = res.height(); |
| |
| return frame; |
| } |
| |
| static void |
| free_framedata(gpointer data) |
| { |
| FrameInfo *frame = (FrameInfo *)data; |
| g_free(frame->data); |
| g_free(frame); |
| } |
| |
| static gboolean |
| clutter_render_image(FrameInfo *frame) |
| { |
| g_return_val_if_fail(CLUTTER_IS_ACTOR(frame->image_actor), FALSE); |
| |
| ClutterContent * image_new = clutter_image_new(); |
| |
| const gint BPP = 4; |
| const gint ROW_STRIDE = BPP * frame->width; |
| |
| GError *error = NULL; |
| clutter_image_set_data( |
| CLUTTER_IMAGE(image_new), |
| frame->data, |
| COGL_PIXEL_FORMAT_BGRA_8888, |
| frame->width, |
| frame->height, |
| ROW_STRIDE, |
| &error); |
| if (error) { |
| g_warning("error rendering image to clutter: %s", error->message); |
| g_error_free(error); |
| } |
| |
| clutter_actor_set_content(frame->image_actor, image_new); |
| g_object_unref (image_new); |
| /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure |
| * that the aspect ratio is correct |
| */ |
| clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT); |
| |
| return FALSE; /* we do not want this function to be called again */ |
| } |
| |
| static gboolean |
| renderer_stop(VideoWidget *self) |
| { |
| g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE); |
| |
| VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self); |
| |
| QObject::disconnect(priv->remote_frame_update); |
| |
| return FALSE; /* don't call again */ |
| } |
| |
| static gboolean |
| renderer_start(VideoWidget *self) |
| { |
| g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE); |
| |
| VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self); |
| |
| priv->remote_frame_update = QObject::connect( |
| priv->renderer_remote, |
| &Video::Renderer::frameUpdated, |
| [=]() { |
| |
| /* this callback comes from another thread; |
| * rendering must be done in the main loop; |
| * copy the frame and add it as a task on the main loop |
| */ |
| |
| /* for now use the video container for the remote image */ |
| FrameInfo *frame = prepare_framedata(priv->renderer_remote, |
| priv->video_container); |
| |
| g_idle_add_full(G_PRIORITY_HIGH_IDLE, |
| (GSourceFunc)clutter_render_image, |
| frame, |
| (GDestroyNotify)free_framedata); |
| } |
| ); |
| |
| return FALSE; /* don't call again */ |
| } |
| |
| |
| /* |
| * video_widget_new() |
| * |
| * The function use to create a new video_widget |
| */ |
| GtkWidget* |
| video_widget_new(void) |
| { |
| GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL); |
| return self; |
| } |
| |
| void |
| video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new) |
| { |
| g_return_if_fail(IS_VIDEO_WIDGET(self)); |
| if (renderer_remote_new == NULL) return; |
| |
| VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self); |
| |
| /* update the renderer */ |
| QObject::disconnect(priv->remote_frame_update); |
| QObject::disconnect(priv->remote_render_stop); |
| QObject::disconnect(priv->remote_render_start); |
| priv->renderer_remote = renderer_remote_new; |
| |
| renderer_start(self); |
| |
| priv->remote_render_stop = QObject::connect( |
| renderer_remote_new, |
| &Video::Renderer::stopped, |
| [=]() { |
| g_idle_add_full(G_PRIORITY_HIGH_IDLE, |
| (GSourceFunc)renderer_stop, |
| self, |
| NULL); |
| } |
| ); |
| |
| priv->remote_render_start = QObject::connect( |
| renderer_remote_new, |
| &Video::Renderer::started, |
| [=]() { |
| g_idle_add_full(G_PRIORITY_HIGH_IDLE, |
| (GSourceFunc)renderer_start, |
| self, |
| NULL); |
| } |
| ); |
| } |