Compare commits
23 Commits
matthiasc/
...
wip/otte/p
Author | SHA1 | Date | |
---|---|---|---|
|
c45ec8e0d2 | ||
|
fa62485cf0 | ||
|
8dde331bbc | ||
|
08bf8a60e8 | ||
|
50373a91a2 | ||
|
3797b7e998 | ||
|
01158f4438 | ||
|
22e729877f | ||
|
b9b84b3644 | ||
|
433c71435a | ||
|
4fc1ea9d12 | ||
|
074c829736 | ||
|
2a8d7f8d6a | ||
|
7e22264918 | ||
|
98bfd8eca0 | ||
|
ffe4b66699 | ||
|
a09667d4b9 | ||
|
bd25c4110c | ||
|
427bce6c54 | ||
|
939e466eb4 | ||
|
10e1520162 | ||
|
4bef77abd5 | ||
|
e30ba1dced |
@@ -41,6 +41,9 @@
|
||||
/* Define to 1 if you have the <dlfcn.h> header file. */
|
||||
#mesondefine HAVE_DLFCN_H
|
||||
|
||||
/* Have the ffmpeg library */
|
||||
#mesondefine HAVE_FFMPEG
|
||||
|
||||
/* Define to 1 if you have the <ftw.h> header file. */
|
||||
#mesondefine HAVE_FTW_H
|
||||
|
||||
@@ -53,6 +56,9 @@
|
||||
/* Define if gio-unix is available */
|
||||
#mesondefine HAVE_GIO_UNIX
|
||||
|
||||
/* Define if GStreamer support is available */
|
||||
#mesondefine HAVE_GSTREAMER
|
||||
|
||||
/* Define to 1 if you have the `httpGetAuthString' function. */
|
||||
#mesondefine HAVE_HTTPGETAUTHSTRING
|
||||
|
||||
|
@@ -121,6 +121,7 @@
|
||||
<gresource prefix="/images">
|
||||
<file>alphatest.png</file>
|
||||
<file>floppybuddy.gif</file>
|
||||
<file>gtk-logo.webm</file>
|
||||
</gresource>
|
||||
<gresource prefix="/pixbufs">
|
||||
<file>apple-red.png</file>
|
||||
@@ -181,6 +182,9 @@
|
||||
<file>overlay.c</file>
|
||||
<file>overlay2.c</file>
|
||||
<file>pagesetup.c</file>
|
||||
<file>paintable.c</file>
|
||||
<file>paintable_animated.c</file>
|
||||
<file>paintable_mediastream.c</file>
|
||||
<file>panes.c</file>
|
||||
<file>pickers.c</file>
|
||||
<file>pixbufs.c</file>
|
||||
@@ -204,6 +208,7 @@
|
||||
<file>transparent.c</file>
|
||||
<file>tree_store.c</file>
|
||||
<file>textmask.c</file>
|
||||
<file>video_player.c</file>
|
||||
</gresource>
|
||||
<gresource prefix="/textview">
|
||||
<file>floppybuddy.gif</file>
|
||||
|
BIN
demos/gtk-demo/gtk-logo.webm
Normal file
BIN
demos/gtk-demo/gtk-logo.webm
Normal file
Binary file not shown.
@@ -317,12 +317,15 @@ toggle_sensitivity_callback (GtkWidget *togglebutton,
|
||||
GtkWidget *
|
||||
do_images (GtkWidget *do_widget)
|
||||
{
|
||||
GtkWidget *video;
|
||||
GtkWidget *frame;
|
||||
GtkWidget *vbox;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *base_vbox;
|
||||
GtkWidget *image;
|
||||
GtkWidget *label;
|
||||
GtkWidget *button;
|
||||
GIcon *gicon;
|
||||
GIcon *gicon;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
@@ -336,9 +339,15 @@ do_images (GtkWidget *do_widget)
|
||||
g_signal_connect (window, "destroy",
|
||||
G_CALLBACK (cleanup_callback), NULL);
|
||||
|
||||
base_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
|
||||
g_object_set (base_vbox, "margin", 16, NULL);
|
||||
gtk_container_add (GTK_CONTAINER (window), base_vbox);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
|
||||
gtk_container_add (GTK_CONTAINER (base_vbox), hbox);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
|
||||
g_object_set (vbox, "margin", 16, NULL);
|
||||
gtk_container_add (GTK_CONTAINER (window), vbox);
|
||||
gtk_container_add (GTK_CONTAINER (hbox), vbox);
|
||||
|
||||
label = gtk_label_new (NULL);
|
||||
gtk_label_set_markup (GTK_LABEL (label),
|
||||
@@ -395,6 +404,8 @@ do_images (GtkWidget *do_widget)
|
||||
|
||||
|
||||
/* Progressive */
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_add (GTK_CONTAINER (hbox), vbox);
|
||||
|
||||
label = gtk_label_new (NULL);
|
||||
gtk_label_set_markup (GTK_LABEL (label),
|
||||
@@ -415,13 +426,35 @@ do_images (GtkWidget *do_widget)
|
||||
|
||||
start_progressive_loading (image);
|
||||
|
||||
/* Video */
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_add (GTK_CONTAINER (hbox), vbox);
|
||||
|
||||
label = gtk_label_new (NULL);
|
||||
gtk_label_set_markup (GTK_LABEL (label),
|
||||
"<u>Displaying video</u>");
|
||||
gtk_box_pack_start (GTK_BOX (vbox), label);
|
||||
|
||||
frame = gtk_frame_new (NULL);
|
||||
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
||||
gtk_widget_set_halign (frame, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), frame);
|
||||
|
||||
video = gtk_video_new_for_resource ("/images/gtk-logo.webm");
|
||||
gtk_media_stream_set_loop (gtk_video_get_media_stream (GTK_VIDEO (video)), TRUE);
|
||||
gtk_container_add (GTK_CONTAINER (frame), video);
|
||||
gtk_style_context_add_paint_for_display (gdk_display_get_default (),
|
||||
"video",
|
||||
GDK_PAINTABLE (gtk_video_get_media_stream (GTK_VIDEO (video))));
|
||||
|
||||
/* Sensitivity control */
|
||||
button = gtk_toggle_button_new_with_mnemonic ("_Insensitive");
|
||||
gtk_box_pack_start (GTK_BOX (vbox), button);
|
||||
gtk_box_pack_start (GTK_BOX (base_vbox), button);
|
||||
|
||||
g_signal_connect (button, "toggled",
|
||||
G_CALLBACK (toggle_sensitivity_callback),
|
||||
vbox);
|
||||
base_vbox);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
|
@@ -45,6 +45,9 @@ demos = files([
|
||||
'modelbutton.c',
|
||||
'overlay.c',
|
||||
'overlay2.c',
|
||||
'paintable.c',
|
||||
'paintable_animated.c',
|
||||
'paintable_mediastream.c',
|
||||
'panes.c',
|
||||
'pickers.c',
|
||||
'pixbufs.c',
|
||||
@@ -68,6 +71,7 @@ demos = files([
|
||||
'theming_style_classes.c',
|
||||
'transparent.c',
|
||||
'tree_store.c',
|
||||
'video_player.c',
|
||||
])
|
||||
|
||||
gtkdemo_deps = [ libgtk_dep, ]
|
||||
|
179
demos/gtk-demo/paintable.c
Normal file
179
demos/gtk-demo/paintable.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/* Paintable/A simple paintable
|
||||
*
|
||||
* GdkPaintable is an interface used by GTK for drawings of any sort
|
||||
* that do not require layouting or positioning.
|
||||
*
|
||||
* This demo code gives a simple example on how a paintable can
|
||||
* be created.
|
||||
*
|
||||
* Paintables can be used in many places inside GTK widgets, but the
|
||||
* most common usage is inside GtkImage and that's what we're going
|
||||
* to do here.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
/* First, add the boilerplate for the object itself.
|
||||
* This part would normally go in the header.
|
||||
*/
|
||||
#define GTK_TYPE_NUCLEAR_ICON (gtk_nuclear_icon_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkNuclearIcon, gtk_nuclear_icon, GTK, NUCLEAR_ICON, GObject)
|
||||
|
||||
/* Declare the struct. */
|
||||
struct _GtkNuclearIcon
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
/* We store this rotation value here.
|
||||
* We are not doing with it here, but it will come in
|
||||
* very useful in the followup demos.
|
||||
*/
|
||||
double rotation;
|
||||
};
|
||||
|
||||
struct _GtkNuclearIconClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
/* This is the function that draws the actual icon.
|
||||
* We make it a custom function and define it in the paintable.h header
|
||||
* so that it can be called from all the other demos, too.
|
||||
*/
|
||||
void
|
||||
gtk_nuclear_snapshot (GtkSnapshot *snapshot,
|
||||
double width,
|
||||
double height,
|
||||
double rotation)
|
||||
{
|
||||
#define RADIUS 0.3
|
||||
cairo_t *cr;
|
||||
double size;
|
||||
|
||||
gtk_snapshot_append_color (snapshot,
|
||||
&(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 },
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height),
|
||||
"Yellow background");
|
||||
|
||||
size = MIN (width, height);
|
||||
cr = gtk_snapshot_append_cairo (snapshot,
|
||||
&GRAPHENE_RECT_INIT ((width - size) / 2.0,
|
||||
(height - size) / 2.0,
|
||||
size, size),
|
||||
"Radioactive Icon");
|
||||
cairo_translate (cr, width / 2.0, height / 2.0);
|
||||
cairo_scale (cr, size, size);
|
||||
cairo_rotate (cr, rotation);
|
||||
|
||||
cairo_arc (cr, 0, 0, 0.1, - G_PI, G_PI);
|
||||
cairo_fill (cr);
|
||||
|
||||
cairo_set_line_width (cr, RADIUS);
|
||||
cairo_set_dash (cr, (double[1]) { RADIUS * G_PI / 3 }, 1, 0.0);
|
||||
cairo_arc (cr, 0, 0, RADIUS, - G_PI, G_PI);
|
||||
cairo_stroke (cr);
|
||||
|
||||
cairo_destroy (cr);
|
||||
}
|
||||
|
||||
/* Here, we implement the functionality required by the GdkPaintable interface */
|
||||
static void
|
||||
gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkNuclearIcon *nuclear = GTK_NUCLEAR_ICON (paintable);
|
||||
|
||||
/* The snapshot function is the only function we need to implement.
|
||||
* It does the actual drawing of the paintable.
|
||||
*/
|
||||
|
||||
gtk_nuclear_snapshot (snapshot,
|
||||
width, height,
|
||||
nuclear->rotation);
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_nuclear_icon_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
/* The flags are very useful to let GTK know that this image
|
||||
* is never going to change.
|
||||
* This allows many optimizations and should therefore always
|
||||
* be set.
|
||||
*/
|
||||
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_icon_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_nuclear_icon_snapshot;
|
||||
iface->get_flags = gtk_nuclear_icon_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkNuclearIcon, gtk_nuclear_icon, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_nuclear_icon_paintable_init))
|
||||
|
||||
/* Here's the boilerplate for the GObject declaration.
|
||||
* We don't need to do anything special here, because we keep no
|
||||
* data of our own.
|
||||
*/
|
||||
static void
|
||||
gtk_nuclear_icon_class_init (GtkNuclearIconClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_icon_init (GtkNuclearIcon *nuclear)
|
||||
{
|
||||
}
|
||||
|
||||
/* And finally, we add a simple constructor.
|
||||
* It is declared in the header so that the other examples
|
||||
* can use it.
|
||||
*/
|
||||
GdkPaintable *
|
||||
gtk_nuclear_icon_new (double rotation)
|
||||
{
|
||||
GtkNuclearIcon *nuclear;
|
||||
|
||||
nuclear = g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL);
|
||||
nuclear->rotation = rotation;
|
||||
|
||||
return GDK_PAINTABLE (nuclear);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_paintable (GtkWidget *do_widget)
|
||||
{
|
||||
GdkPaintable *nuclear;
|
||||
GtkWidget *image;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Nuclear Icon");
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
|
||||
|
||||
nuclear = gtk_nuclear_icon_new (0.0);
|
||||
image = gtk_image_new_from_paintable (nuclear);
|
||||
gtk_container_add (GTK_CONTAINER (window), image);
|
||||
g_object_unref (nuclear);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_widget_show (window);
|
||||
else
|
||||
gtk_widget_destroy (window);
|
||||
|
||||
return window;
|
||||
}
|
15
demos/gtk-demo/paintable.h
Normal file
15
demos/gtk-demo/paintable.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef __PAINTABLE_H__
|
||||
#define __PAINTABLE_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
void gtk_nuclear_snapshot (GtkSnapshot *snapshot,
|
||||
double width,
|
||||
double height,
|
||||
double rotation);
|
||||
|
||||
GdkPaintable * gtk_nuclear_icon_new (double rotation);
|
||||
GdkPaintable * gtk_nuclear_animation_new (void);
|
||||
GtkMediaStream *gtk_nuclear_media_stream_new (void);
|
||||
|
||||
#endif /* __PAINTABLE_H__ */
|
209
demos/gtk-demo/paintable_animated.c
Normal file
209
demos/gtk-demo/paintable_animated.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/* Paintable/An animated paintable
|
||||
*
|
||||
* GdkPaintable also allows paintables to change.
|
||||
*
|
||||
* This demo code gives an example of how this could work. It builds
|
||||
* on the previous simple example.
|
||||
*
|
||||
* Paintables can also change their size, this works similarly, but
|
||||
* we will not demonstrate this here as our icon does not have any size.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
/* First, add the boilerplate for the object itself.
|
||||
* This part would normally go in the header.
|
||||
*/
|
||||
#define GTK_TYPE_NUCLEAR_ANIMATION (gtk_nuclear_animation_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkNuclearAnimation, gtk_nuclear_animation, GTK, NUCLEAR_ANIMATION, GObject)
|
||||
|
||||
/* Do a full rotation in 5 seconds.
|
||||
* We will register the timeout for doing a single step to
|
||||
* be executed every 10ms, which means after 1000 steps
|
||||
* 10s will have elapsed.
|
||||
*/
|
||||
#define MAX_PROGRESS 500
|
||||
|
||||
/* Declare the struct. */
|
||||
struct _GtkNuclearAnimation
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
/* This variable stores the progress of our animation.
|
||||
* We just count upwards until we hit MAX_PROGRESS and
|
||||
* then start from scratch.
|
||||
*/
|
||||
int progress;
|
||||
|
||||
/* This variable holds the ID of the timer that updates
|
||||
* our progress variable.
|
||||
* We need to keep track of it so that we can remove it
|
||||
* again.
|
||||
*/
|
||||
guint source_id;
|
||||
};
|
||||
|
||||
struct _GtkNuclearAnimationClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
/* Again, we implement the functionality required by the GdkPaintable interface */
|
||||
static void
|
||||
gtk_nuclear_animation_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
|
||||
|
||||
/* We call the function from the previous example here. */
|
||||
gtk_nuclear_snapshot (snapshot,
|
||||
width, height,
|
||||
2 * G_PI * nuclear->progress / MAX_PROGRESS);
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_nuclear_animation_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
|
||||
|
||||
/* For non-static paintables, this function needs to be implemented.
|
||||
* It must return a static paintable with the same contents
|
||||
* as this one currently has.
|
||||
*
|
||||
* Luckily we added the rotation property to the nuclear icon
|
||||
* object previously, so we can just return an instance of that one.
|
||||
*/
|
||||
return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / MAX_PROGRESS);
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_nuclear_animation_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
/* This time, we cannot set the static contents flag because our animation
|
||||
* changes the contents.
|
||||
* However, our size still doesn't change, so report that flag.
|
||||
*/
|
||||
return GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_animation_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_nuclear_animation_snapshot;
|
||||
iface->get_current_image = gtk_nuclear_animation_get_current_image;
|
||||
iface->get_flags = gtk_nuclear_animation_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkNuclearAnimation, gtk_nuclear_animation, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_nuclear_animation_paintable_init))
|
||||
|
||||
/* This time, we need to implement the finalize function,
|
||||
*/
|
||||
static void
|
||||
gtk_nuclear_animation_finalize (GObject *object)
|
||||
{
|
||||
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (object);
|
||||
|
||||
/* Remove the timeout we registered when constructing
|
||||
* the object.
|
||||
*/
|
||||
g_source_remove (nuclear->source_id);
|
||||
|
||||
/* Don't forget to chain up to the parent class' implementation
|
||||
* of the finalize function.
|
||||
*/
|
||||
G_OBJECT_CLASS (gtk_nuclear_animation_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
/* In the class declaration, we need to add our finalize function.
|
||||
*/
|
||||
static void
|
||||
gtk_nuclear_animation_class_init (GtkNuclearAnimationClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = gtk_nuclear_animation_finalize;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_nuclear_animation_step (gpointer data)
|
||||
{
|
||||
GtkNuclearAnimation *nuclear = data;
|
||||
|
||||
/* Add 1 to the progress and reset it when we've reached
|
||||
* the maximum value.
|
||||
* The animation will rotate by 360 degrees at MAX_PROGRESS
|
||||
* so it will be identical to the original unrotated one.
|
||||
*/
|
||||
nuclear->progress = (nuclear->progress + 1) % MAX_PROGRESS;
|
||||
|
||||
/* Now we need to tell all listeners that we've changed out contents
|
||||
* so that they can redraw this paintable.
|
||||
*/
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
|
||||
|
||||
/* We want this timeout function to be called repeatedly,
|
||||
* so we return this value here.
|
||||
* If this was a single-shot timeout, we could also
|
||||
* return G_SOURCE_REMOVE here to get rid of it.
|
||||
*/
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_animation_init (GtkNuclearAnimation *nuclear)
|
||||
{
|
||||
/* Add a timer here that constantly updates our animations.
|
||||
* We want to update it often enough to guarantee a smooth animation.
|
||||
*
|
||||
* Ideally, we'd attach to the frame clock, but because we do
|
||||
* not have it available here, we just use a regular timeout
|
||||
* that hopefully triggers often enough to be smooth.
|
||||
*/
|
||||
nuclear->source_id = g_timeout_add (10,
|
||||
gtk_nuclear_animation_step,
|
||||
nuclear);
|
||||
}
|
||||
|
||||
/* And finally, we add the simple constructor we declared in the header. */
|
||||
GdkPaintable *
|
||||
gtk_nuclear_animation_new (void)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_NUCLEAR_ANIMATION, NULL);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_paintable_animated (GtkWidget *do_widget)
|
||||
{
|
||||
GdkPaintable *nuclear;
|
||||
GtkWidget *image;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Nuclear Animation");
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
|
||||
|
||||
nuclear = gtk_nuclear_animation_new ();
|
||||
image = gtk_image_new_from_paintable (nuclear);
|
||||
gtk_container_add (GTK_CONTAINER (window), image);
|
||||
g_object_unref (nuclear);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_widget_show (window);
|
||||
else
|
||||
gtk_widget_destroy (window);
|
||||
|
||||
return window;
|
||||
}
|
312
demos/gtk-demo/paintable_mediastream.c
Normal file
312
demos/gtk-demo/paintable_mediastream.c
Normal file
@@ -0,0 +1,312 @@
|
||||
/* Paintable/A media stream
|
||||
*
|
||||
* GdkPaintable is also used by the GtkMediaStream class.
|
||||
*
|
||||
* This demo code turns the nuclear media_stream into the object
|
||||
* GTK uses for videos. This allows treating the icon like a
|
||||
* regular video, so we can for example attach controls to it.
|
||||
*
|
||||
* After all, what good is an media_stream if one cannot pause
|
||||
* it.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
/* First, add the boilerplate for the object itself.
|
||||
* This part would normally go in the header.
|
||||
*/
|
||||
#define GTK_TYPE_NUCLEAR_MEDIA_STREAM (gtk_nuclear_media_stream_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK, NUCLEAR_MEDIA_STREAM, GtkMediaStream)
|
||||
|
||||
/* Do a full rotation in 5 seconds.
|
||||
*
|
||||
* We do not save steps here but real timestamps.
|
||||
* GtkMediaStream uses microseconds, so we will do so, too.
|
||||
*/
|
||||
#define DURATION (5 * G_USEC_PER_SEC)
|
||||
|
||||
/* Declare the struct. */
|
||||
struct _GtkNuclearMediaStream
|
||||
{
|
||||
/* We now inherit from the media stream object. */
|
||||
GtkMediaStream parent_instance;
|
||||
|
||||
/* This variable stores the progress of our video.
|
||||
*/
|
||||
gint64 progress;
|
||||
|
||||
/* This variable stores the timestamp of the last
|
||||
* time we updated the progress variable when the
|
||||
* video is currently playing.
|
||||
* This is so that we can always accurately compute the
|
||||
* progress we've had, even if the timeout does not
|
||||
* exactly work.
|
||||
*/
|
||||
gint64 last_time;
|
||||
|
||||
/* This variable again holds the ID of the timer that
|
||||
* updates our progress variable. Nothing changes about
|
||||
* how this works compared to the previous example.
|
||||
*/
|
||||
guint source_id;
|
||||
};
|
||||
|
||||
struct _GtkNuclearMediaStreamClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
/* GtkMediaStream is a GdkPaintable. So when we want to display video,
|
||||
* we have to implement the interface, just like in the animation example.
|
||||
*/
|
||||
static void
|
||||
gtk_nuclear_media_stream_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
|
||||
|
||||
/* We call the function from the previous example here. */
|
||||
gtk_nuclear_snapshot (snapshot,
|
||||
width, height,
|
||||
2 * G_PI * nuclear->progress / DURATION);
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_nuclear_media_stream_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
|
||||
|
||||
/* Same thing as with the animation */
|
||||
return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / DURATION);
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_nuclear_media_stream_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
/* And same thing as with the animation over here, too. */
|
||||
return GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_media_stream_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_nuclear_media_stream_snapshot;
|
||||
iface->get_current_image = gtk_nuclear_media_stream_get_current_image;
|
||||
iface->get_flags = gtk_nuclear_media_stream_get_flags;
|
||||
}
|
||||
|
||||
/* This time, we inherit from GTK_TYPE_MEDIA_STREAM */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK_TYPE_MEDIA_STREAM,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_nuclear_media_stream_paintable_init))
|
||||
|
||||
static gboolean
|
||||
gtk_nuclear_media_stream_step (gpointer data)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = data;
|
||||
gint64 current_time;
|
||||
|
||||
/* Compute the time that has elapsed since the last time we were called
|
||||
* and add it to our current progress.
|
||||
*/
|
||||
current_time = g_source_get_time (g_main_current_source ());
|
||||
nuclear->progress += current_time - nuclear->last_time;
|
||||
|
||||
/* Check if we've ended */
|
||||
if (nuclear->progress > DURATION)
|
||||
{
|
||||
if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (nuclear)))
|
||||
{
|
||||
/* We're looping. So make the progress loop using modulo */
|
||||
nuclear->progress %= DURATION;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Just make sure we don't overflow */
|
||||
nuclear->progress = DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the last time to the current timestamp. */
|
||||
nuclear->last_time = current_time;
|
||||
|
||||
/* Update the timestamp of the media stream */
|
||||
gtk_media_stream_update (GTK_MEDIA_STREAM (nuclear), nuclear->progress);
|
||||
|
||||
/* We also need to invalidate our contents again.
|
||||
* After all, we are a video and not just an audio stream.
|
||||
*/
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
|
||||
|
||||
/* Now check if we have finished playing and if so,
|
||||
* tell the media stream. The media stream will then
|
||||
* call our pause function to pause the stream.
|
||||
*/
|
||||
if (nuclear->progress >= DURATION)
|
||||
gtk_media_stream_ended (GTK_MEDIA_STREAM (nuclear));
|
||||
|
||||
/* The timeout function is removed by the pause function,
|
||||
* so we can just always return this value.
|
||||
*/
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_nuclear_media_stream_play (GtkMediaStream *stream)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
|
||||
|
||||
/* If we're already at the end of the stream, we don't want
|
||||
* to start playing and exit early.
|
||||
*/
|
||||
if (nuclear->progress >= DURATION)
|
||||
return FALSE;
|
||||
|
||||
/* This time, we add the source only when we start playing.
|
||||
*/
|
||||
nuclear->source_id = g_timeout_add (10,
|
||||
gtk_nuclear_media_stream_step,
|
||||
nuclear);
|
||||
|
||||
/* We also want to initialize our time, so that we can
|
||||
* do accurate updates.
|
||||
*/
|
||||
nuclear->last_time = g_get_monotonic_time ();
|
||||
|
||||
/* We successfully started playing, so we return TRUE here. */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_media_stream_pause (GtkMediaStream *stream)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
|
||||
|
||||
/* This function will be called when a playing stream
|
||||
* gets paused.
|
||||
* So we remove the updating source here and set it
|
||||
* back to 0 so that the finalize function doesn't try
|
||||
* to remove it again.
|
||||
*/
|
||||
g_source_remove (nuclear->source_id);
|
||||
nuclear->source_id = 0;
|
||||
nuclear->last_time = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_media_stream_seek (GtkMediaStream *stream,
|
||||
gint64 timestamp)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
|
||||
|
||||
/* This is optional functionality for media streams,
|
||||
* but not being able to seek is kinda boring.
|
||||
* And it's trivial to implement, so let's go for it.
|
||||
*/
|
||||
nuclear->progress = timestamp;
|
||||
|
||||
/* Media streams are asynchronous, so seeking can take a while.
|
||||
* We however don't need that functionality, so we can just
|
||||
* report success.
|
||||
*/
|
||||
gtk_media_stream_seek_success (stream);
|
||||
|
||||
/* We also have to update our timestamp and tell the
|
||||
* paintable interface abbout the seek
|
||||
*/
|
||||
gtk_media_stream_update (stream, nuclear->progress);
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
|
||||
}
|
||||
|
||||
/* Again, we need to implement the finalize function.
|
||||
*/
|
||||
static void
|
||||
gtk_nuclear_media_stream_finalize (GObject *object)
|
||||
{
|
||||
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (object);
|
||||
|
||||
/* This time, we need to check if the source exists before
|
||||
* removing it as it only exists while we are playing.
|
||||
*/
|
||||
if (nuclear->source_id > 0)
|
||||
g_source_remove (nuclear->source_id);
|
||||
|
||||
/* Don't forget to chain up to the parent class' implementation
|
||||
* of the finalize function.
|
||||
*/
|
||||
G_OBJECT_CLASS (gtk_nuclear_media_stream_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
/* In the class declaration, we need to implement the media stream */
|
||||
static void
|
||||
gtk_nuclear_media_stream_class_init (GtkNuclearMediaStreamClass *klass)
|
||||
{
|
||||
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
stream_class->play = gtk_nuclear_media_stream_play;
|
||||
stream_class->pause = gtk_nuclear_media_stream_pause;
|
||||
stream_class->seek = gtk_nuclear_media_stream_seek;
|
||||
|
||||
gobject_class->finalize = gtk_nuclear_media_stream_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_nuclear_media_stream_init (GtkNuclearMediaStream *nuclear)
|
||||
{
|
||||
/* This time, we don't have to add a timer here, because media
|
||||
* streams start paused.
|
||||
*
|
||||
* However, media streams need to tell GTK once they are intialized,
|
||||
* so we do that here.
|
||||
*/
|
||||
gtk_media_stream_prepared (GTK_MEDIA_STREAM (nuclear),
|
||||
FALSE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
DURATION);
|
||||
}
|
||||
|
||||
/* And finally, we add the simple constructor we declared in the header. */
|
||||
GtkMediaStream *
|
||||
gtk_nuclear_media_stream_new (void)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_paintable_mediastream (GtkWidget *do_widget)
|
||||
{
|
||||
GtkMediaStream *nuclear;
|
||||
GtkWidget *video;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Nuclear MediaStream");
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
|
||||
|
||||
nuclear = gtk_nuclear_media_stream_new ();
|
||||
gtk_media_stream_set_loop (GTK_MEDIA_STREAM (nuclear), TRUE);
|
||||
|
||||
video = gtk_video_new_for_media_stream (nuclear);
|
||||
gtk_container_add (GTK_CONTAINER (window), video);
|
||||
|
||||
g_object_unref (nuclear);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_widget_show (window);
|
||||
else
|
||||
gtk_widget_destroy (window);
|
||||
|
||||
return window;
|
||||
}
|
96
demos/gtk-demo/video_player.c
Normal file
96
demos/gtk-demo/video_player.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/* Video Player
|
||||
*
|
||||
* This is a simple video player using just GTK widgets.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
static void
|
||||
open_dialog_response_cb (GtkWidget *dialog,
|
||||
int response,
|
||||
GtkWidget *video)
|
||||
{
|
||||
gtk_widget_hide (dialog);
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
||||
gtk_video_set_file (GTK_VIDEO (video), file);
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
gtk_widget_destroy (dialog);
|
||||
}
|
||||
|
||||
static void
|
||||
open_clicked_cb (GtkWidget *button,
|
||||
GtkWidget *video)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new ("Select a video",
|
||||
GTK_WINDOW (gtk_widget_get_toplevel (button)),
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
||||
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
|
||||
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response_cb), video);
|
||||
gtk_widget_show (dialog);
|
||||
}
|
||||
|
||||
static void
|
||||
fullscreen_clicked_cb (GtkWidget *button,
|
||||
gpointer unused)
|
||||
{
|
||||
GtkWidget *window = gtk_widget_get_toplevel (button);
|
||||
|
||||
gtk_window_fullscreen (GTK_WINDOW (window));
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_video_player (GtkWidget *do_widget)
|
||||
{
|
||||
GtkWidget *title;
|
||||
GtkWidget *video;
|
||||
GtkWidget *open_button;
|
||||
GtkWidget *fullscreen_button;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Video Player");
|
||||
g_signal_connect (window, "destroy",
|
||||
G_CALLBACK (gtk_widget_destroyed), &window);
|
||||
|
||||
video = gtk_video_new ();
|
||||
gtk_container_add (GTK_CONTAINER (window), video);
|
||||
|
||||
title = gtk_header_bar_new ();
|
||||
gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (title), TRUE);
|
||||
gtk_header_bar_set_title (GTK_HEADER_BAR (title), "Video Player");
|
||||
gtk_window_set_titlebar (GTK_WINDOW (window), title);
|
||||
|
||||
open_button = gtk_button_new_with_mnemonic ("_Open");
|
||||
g_signal_connect (open_button, "clicked", G_CALLBACK (open_clicked_cb), video);
|
||||
gtk_header_bar_pack_start (GTK_HEADER_BAR (title), open_button);
|
||||
|
||||
fullscreen_button = gtk_button_new_from_icon_name ("view-fullscreen-symbolic");
|
||||
g_signal_connect (fullscreen_button, "clicked", G_CALLBACK (fullscreen_clicked_cb), NULL);
|
||||
gtk_header_bar_pack_end (GTK_HEADER_BAR (title), fullscreen_button);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_widget_show (window);
|
||||
else
|
||||
gtk_widget_destroy (window);
|
||||
|
||||
return window;
|
||||
}
|
@@ -81,6 +81,18 @@ create_label (void)
|
||||
return w;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_video (void)
|
||||
{
|
||||
GtkMediaStream *stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
|
||||
GtkWidget *w = gtk_image_new_from_paintable (GDK_PAINTABLE (stream));
|
||||
gtk_media_stream_set_loop (stream, TRUE);
|
||||
gtk_media_stream_play (stream);
|
||||
g_object_unref (stream);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_gears (void)
|
||||
{
|
||||
@@ -102,6 +114,7 @@ static const struct {
|
||||
{ "Label" , create_label },
|
||||
{ "Spinner" , create_spinner },
|
||||
{ "Spinbutton", create_spinbutton },
|
||||
{ "Video", create_video },
|
||||
{ "Gears", create_gears },
|
||||
};
|
||||
|
||||
|
@@ -733,13 +733,14 @@ gdk_paintable_invalidate_size
|
||||
<TITLE>Textures</TITLE>
|
||||
<FILE>textures</FILE>
|
||||
GdkTexture
|
||||
gdk_texture_new_for_data
|
||||
gdk_texture_new_for_pixbuf
|
||||
gdk_texture_new_from_resource
|
||||
gdk_texture_new_from_file
|
||||
gdk_texture_get_width
|
||||
gdk_texture_get_height
|
||||
gdk_texture_download
|
||||
GdkMemoryFormat
|
||||
GDK_MEMORY_FORMAT_DEFAULT
|
||||
gdk_memory_texture_new
|
||||
gdk_gl_texture_new
|
||||
gdk_gl_texture_release
|
||||
@@ -750,6 +751,16 @@ gdk_texture_get_type
|
||||
GDK_TYPE_TEXTURE
|
||||
GDK_IS_TEXTURE
|
||||
GDK_TEXTURE
|
||||
GdkGLTextureClass
|
||||
gdk_gl_texture_get_type
|
||||
GDK_TYPE_GL_TEXTURE
|
||||
GDK_IS_GL_TEXTURE
|
||||
GDK_GL_TEXTURE
|
||||
GdkMemoryTextureClass
|
||||
gdk_memory_texture_get_type
|
||||
GDK_TYPE_MEMORY_TEXTURE
|
||||
GDK_IS_MEMORY_TEXTURE
|
||||
GDK_MEMORY_TEXTURE
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
|
@@ -133,28 +133,26 @@ gdk_gl_texture_get_id (GdkGLTexture *self)
|
||||
|
||||
/**
|
||||
* gdk_gl_texture_release:
|
||||
* @texture: a #GdkTexture wrapping a GL texture
|
||||
* @self: a #GdkTexture wrapping a GL texture
|
||||
*
|
||||
* Releases the GL resources held by a #GdkTexture that
|
||||
* was created with gdk_texture_new_for_gl().
|
||||
* Releases the GL resources held by a #GdkGLTexture that
|
||||
* was created with gdk_gl_texture_new().
|
||||
*
|
||||
* The texture contents are still available via the
|
||||
* gdk_texture_download() function, after this function
|
||||
* has been called.
|
||||
*/
|
||||
void
|
||||
gdk_gl_texture_release (GdkTexture *texture)
|
||||
gdk_gl_texture_release (GdkGLTexture *self)
|
||||
{
|
||||
GdkGLTexture *self;
|
||||
GdkWindow *window;
|
||||
GdkTexture *texture;
|
||||
cairo_t *cr;
|
||||
|
||||
g_return_if_fail (GDK_IS_GL_TEXTURE (texture));
|
||||
|
||||
self = GDK_GL_TEXTURE (texture);
|
||||
|
||||
g_return_if_fail (GDK_IS_GL_TEXTURE (self));
|
||||
g_return_if_fail (self->saved == NULL);
|
||||
|
||||
texture = GDK_TEXTURE (self);
|
||||
self->saved = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
texture->width, texture->height);
|
||||
|
||||
@@ -178,7 +176,7 @@ gdk_gl_texture_release (GdkTexture *texture)
|
||||
}
|
||||
|
||||
/**
|
||||
* gdk_gl_texture_new: (constructor)
|
||||
* gdk_gl_texture_new:
|
||||
* @context: a #GdkGLContext
|
||||
* @id: the ID of a texture that was created with @context
|
||||
* @width: the nominal width of the texture
|
||||
|
@@ -23,11 +23,23 @@
|
||||
#error "Only <gdk/gdk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <gdk/gdktypes.h>
|
||||
#include <gdk/gdkglcontext.h>
|
||||
#include <gdk/gdktexture.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GDK_TYPE_GL_TEXTURE (gdk_gl_texture_get_type ())
|
||||
|
||||
#define GDK_GL_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_GL_TEXTURE, GdkGLTexture))
|
||||
#define GDK_IS_GL_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_GL_TEXTURE))
|
||||
|
||||
typedef struct _GdkGLTexture GdkGLTexture;
|
||||
typedef struct _GdkGLTextureClass GdkGLTextureClass;
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkGLTexture, g_object_unref)
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gdk_gl_texture_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GdkTexture * gdk_gl_texture_new (GdkGLContext *context,
|
||||
@@ -38,7 +50,7 @@ GdkTexture * gdk_gl_texture_new (GdkGLContext
|
||||
gpointer data);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gdk_gl_texture_release (GdkTexture *texture);
|
||||
void gdk_gl_texture_release (GdkGLTexture *texture);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -7,10 +7,6 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GDK_TYPE_GL_TEXTURE (gdk_gl_texture_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GdkGLTexture, gdk_gl_texture, GDK, GL_TEXTURE, GdkTexture)
|
||||
|
||||
GdkGLContext * gdk_gl_texture_get_context (GdkGLTexture *self);
|
||||
guint gdk_gl_texture_get_id (GdkGLTexture *self);
|
||||
|
||||
|
@@ -68,6 +68,38 @@ typedef enum {
|
||||
GDK_MEMORY_N_FORMATS
|
||||
} GdkMemoryFormat;
|
||||
|
||||
/**
|
||||
* GDK_MEMORY_DEFAULT:
|
||||
*
|
||||
* This is the default memory format used by GTK and is the format
|
||||
* provided by gdk_texture_download(). It is equal to
|
||||
* %CAIRO_FORMAT_ARGB32.
|
||||
*
|
||||
* Be aware that unlike the #GdkMemoryFormat values, this format is
|
||||
* different for different endianness.
|
||||
*/
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define GDK_MEMORY_DEFAULT GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
|
||||
#elif G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
#define GDK_MEMORY_DEFAULT GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
|
||||
#else
|
||||
#error "Unknown byte order for GDK_MEMORY_DEFAULT"
|
||||
#endif
|
||||
|
||||
#define GDK_TYPE_MEMORY_TEXTURE (gdk_memory_texture_get_type ())
|
||||
|
||||
#define GDK_MEMORY_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_MEMORY_TEXTURE, GdkMemoryTexture))
|
||||
#define GDK_IS_MEMORY_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_MEMORY_TEXTURE))
|
||||
|
||||
typedef struct _GdkMemoryTexture GdkMemoryTexture;
|
||||
typedef struct _GdkMemoryTextureClass GdkMemoryTextureClass;
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkMemoryTexture, g_object_unref)
|
||||
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gdk_memory_texture_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GdkTexture * gdk_memory_texture_new (int width,
|
||||
int height,
|
||||
|
@@ -29,17 +29,7 @@ G_BEGIN_DECLS
|
||||
#define GDK_MEMORY_GDK_PIXBUF_OPAQUE GDK_MEMORY_R8G8B8
|
||||
#define GDK_MEMORY_GDK_PIXBUF_ALPHA GDK_MEMORY_R8G8B8A8
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
|
||||
#elif G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
|
||||
#else
|
||||
#error "Unknown byte order."
|
||||
#endif
|
||||
|
||||
#define GDK_TYPE_MEMORY_TEXTURE (gdk_memory_texture_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GdkMemoryTexture, gdk_memory_texture, GDK, MEMORY_TEXTURE, GdkTexture)
|
||||
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_DEFAULT
|
||||
|
||||
GdkMemoryFormat gdk_memory_texture_get_format (GdkMemoryTexture *self);
|
||||
const guchar * gdk_memory_texture_get_data (GdkMemoryTexture *self);
|
||||
|
@@ -266,48 +266,6 @@ gdk_texture_init (GdkTexture *self)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* gdk_texture_new_for_data:
|
||||
* @data: (array): the pixel data
|
||||
* @width: the number of pixels in each row
|
||||
* @height: the number of rows
|
||||
* @stride: the distance from the beginning of one row to the next, in bytes
|
||||
*
|
||||
* Creates a new texture object holding the given data.
|
||||
* The data is assumed to be in CAIRO_FORMAT_ARGB32 format.
|
||||
*
|
||||
* Returns: a new #GdkTexture
|
||||
*/
|
||||
GdkTexture *
|
||||
gdk_texture_new_for_data (const guchar *data,
|
||||
int width,
|
||||
int height,
|
||||
int stride)
|
||||
{
|
||||
GdkTexture *texture;
|
||||
cairo_surface_t *original, *copy;
|
||||
cairo_t *cr;
|
||||
|
||||
g_return_val_if_fail (width > 0, NULL);
|
||||
g_return_val_if_fail (height > 0, NULL);
|
||||
|
||||
original = cairo_image_surface_create_for_data ((guchar *) data, CAIRO_FORMAT_ARGB32, width, height, stride);
|
||||
copy = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
||||
|
||||
cr = cairo_create (copy);
|
||||
cairo_set_source_surface (cr, original, 0, 0);
|
||||
cairo_paint (cr);
|
||||
cairo_destroy (cr);
|
||||
|
||||
texture = gdk_texture_new_for_surface (copy);
|
||||
|
||||
cairo_surface_destroy (copy);
|
||||
cairo_surface_finish (original);
|
||||
cairo_surface_destroy (original);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* gdk_texture_new_for_surface:
|
||||
* @surface: a cairo image surface
|
||||
|
@@ -42,11 +42,6 @@ typedef struct _GdkTextureClass GdkTextureClass;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gdk_texture_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GdkTexture * gdk_texture_new_for_data (const guchar *data,
|
||||
int width,
|
||||
int height,
|
||||
int stride);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GdkTexture * gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "gskdebugprivate.h"
|
||||
#include "gskrendererprivate.h"
|
||||
#include "gskroundedrectprivate.h"
|
||||
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
|
||||
static gboolean
|
||||
@@ -729,6 +730,7 @@ gsk_texture_node_deserialize (GVariant *variant,
|
||||
guint32 width, height;
|
||||
GVariant *pixel_variant;
|
||||
gsize n_pixels;
|
||||
GBytes *bytes;
|
||||
|
||||
if (!check_variant_type (variant, GSK_TEXTURE_NODE_VARIANT_TYPE, error))
|
||||
return NULL;
|
||||
@@ -738,9 +740,23 @@ gsk_texture_node_deserialize (GVariant *variant,
|
||||
&width, &height, &pixel_variant);
|
||||
|
||||
/* XXX: Make this work without copying the data */
|
||||
texture = gdk_texture_new_for_data (g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
|
||||
width, height, width * 4);
|
||||
g_variant_unref (pixel_variant);
|
||||
bytes = g_bytes_new_with_free_func (g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
|
||||
width * height * sizeof (guint32),
|
||||
(GDestroyNotify) g_variant_unref,
|
||||
pixel_variant);
|
||||
if (n_pixels != width * height)
|
||||
{
|
||||
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_INVALID_DATA,
|
||||
"Expected %u pixels but got %"G_GSIZE_FORMAT" for %ux%u image",
|
||||
width * height, n_pixels, width, height);
|
||||
g_bytes_unref (bytes);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
texture = gdk_memory_texture_new (width, height,
|
||||
GDK_MEMORY_DEFAULT,
|
||||
bytes,
|
||||
width * 4);
|
||||
|
||||
node = gsk_texture_node_new (texture, &GRAPHENE_RECT_INIT(bounds[0], bounds[1], bounds[2], bounds[3]));
|
||||
|
||||
|
@@ -633,6 +633,7 @@ gsk_vulkan_image_download (GskVulkanImage *self,
|
||||
{
|
||||
GskVulkanBuffer *buffer;
|
||||
GdkTexture *texture;
|
||||
GBytes *bytes;
|
||||
guchar *mem;
|
||||
|
||||
gsk_vulkan_uploader_add_image_barrier (uploader,
|
||||
@@ -671,7 +672,11 @@ gsk_vulkan_image_download (GskVulkanImage *self,
|
||||
GSK_VK_CHECK (vkQueueWaitIdle, gdk_vulkan_context_get_queue (self->vulkan));
|
||||
|
||||
mem = gsk_vulkan_buffer_map (buffer);
|
||||
texture = gdk_texture_new_for_data (mem, self->width, self->height, self->width * 4);
|
||||
bytes = g_bytes_new (mem, self->width * self->height * 4);
|
||||
texture = gdk_memory_texture_new (self->width, self->height,
|
||||
GDK_MEMORY_DEFAULT,
|
||||
bytes,
|
||||
self->width * 4);
|
||||
gsk_vulkan_buffer_unmap (buffer);
|
||||
gsk_vulkan_buffer_free (buffer);
|
||||
|
||||
|
@@ -137,6 +137,9 @@
|
||||
#include <gtk/gtkliststore.h>
|
||||
#include <gtk/gtklockbutton.h>
|
||||
#include <gtk/gtkmain.h>
|
||||
#include <gtk/gtkmediacontrols.h>
|
||||
#include <gtk/gtkmediafile.h>
|
||||
#include <gtk/gtkmediastream.h>
|
||||
#include <gtk/gtkmenu.h>
|
||||
#include <gtk/gtkmenubar.h>
|
||||
#include <gtk/gtkmenubutton.h>
|
||||
@@ -225,6 +228,7 @@
|
||||
#include <gtk/gtktypebuiltins.h>
|
||||
#include <gtk/gtktypes.h>
|
||||
#include <gtk/gtkversion.h>
|
||||
#include <gtk/gtkvideo.h>
|
||||
#include <gtk/gtkviewport.h>
|
||||
#include <gtk/gtkvolumebutton.h>
|
||||
#include <gtk/gtkwidget.h>
|
||||
|
@@ -26,13 +26,14 @@
|
||||
|
||||
/* for the types only */
|
||||
#include "gtk/gtkcssimagecrossfadeprivate.h"
|
||||
#include "gtk/gtkcssimagefallbackprivate.h"
|
||||
#include "gtk/gtkcssimageiconthemeprivate.h"
|
||||
#include "gtk/gtkcssimagelinearprivate.h"
|
||||
#include "gtk/gtkcssimagepaintprivate.h"
|
||||
#include "gtk/gtkcssimageradialprivate.h"
|
||||
#include "gtk/gtkcssimageurlprivate.h"
|
||||
#include "gtk/gtkcssimagescaledprivate.h"
|
||||
#include "gtk/gtkcssimagerecolorprivate.h"
|
||||
#include "gtk/gtkcssimagefallbackprivate.h"
|
||||
#include "gtk/gtkcssimagescaledprivate.h"
|
||||
#include "gtk/gtkcssimageurlprivate.h"
|
||||
#include "gtk/gtkcssimagewin32private.h"
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
|
||||
@@ -501,6 +502,7 @@ gtk_css_image_get_parser_type (GtkCssParser *parser)
|
||||
} image_types[] = {
|
||||
{ "url", _gtk_css_image_url_get_type },
|
||||
{ "-gtk-icontheme", _gtk_css_image_icon_theme_get_type },
|
||||
{ "-gtk-paint", gtk_css_image_paint_get_type },
|
||||
{ "-gtk-scaled", _gtk_css_image_scaled_get_type },
|
||||
{ "-gtk-recolor", _gtk_css_image_recolor_get_type },
|
||||
{ "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },
|
||||
|
147
gtk/gtkcssimagepaint.c
Normal file
147
gtk/gtkcssimagepaint.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gtkcssimagepaintprivate.h"
|
||||
|
||||
#include "gtkcssimageinvalidprivate.h"
|
||||
#include "gtkcssimagepaintableprivate.h"
|
||||
#include "gtkstyleproviderprivate.h"
|
||||
|
||||
G_DEFINE_TYPE (GtkCssImagePaint, gtk_css_image_paint, GTK_TYPE_CSS_IMAGE)
|
||||
|
||||
static void
|
||||
gtk_css_image_paint_snapshot (GtkCssImage *image,
|
||||
GtkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
}
|
||||
|
||||
static GtkCssImage *
|
||||
gtk_css_image_paint_compute (GtkCssImage *image,
|
||||
guint property_id,
|
||||
GtkStyleProvider *provider,
|
||||
GtkCssStyle *style,
|
||||
GtkCssStyle *parent_style)
|
||||
{
|
||||
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
|
||||
GdkPaintable *paintable, *static_paintable;
|
||||
GtkCssImage *result;
|
||||
|
||||
paintable = gtk_style_provider_get_paint (provider, paint->name);
|
||||
if (paintable == NULL)
|
||||
return gtk_css_image_invalid_new ();
|
||||
|
||||
static_paintable = gdk_paintable_get_current_image (paintable);
|
||||
result = gtk_css_image_paintable_new (paintable, static_paintable);
|
||||
g_object_unref (static_paintable);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_css_image_paint_equal (GtkCssImage *image1,
|
||||
GtkCssImage *image2)
|
||||
{
|
||||
GtkCssImagePaint *paint1 = GTK_CSS_IMAGE_PAINT (image1);
|
||||
GtkCssImagePaint *paint2 = GTK_CSS_IMAGE_PAINT (image2);
|
||||
|
||||
return g_str_equal (paint1->name, paint2->name);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_css_image_paint_parse (GtkCssImage *image,
|
||||
GtkCssParser *parser)
|
||||
{
|
||||
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
|
||||
|
||||
if (!_gtk_css_parser_try (parser, "-gtk-paint", TRUE))
|
||||
{
|
||||
_gtk_css_parser_error (parser, "'-gtk-paint'");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!_gtk_css_parser_try (parser, "(", TRUE))
|
||||
{
|
||||
_gtk_css_parser_error (parser, "Expected '(' after '-gtk-paint'");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
paint->name = _gtk_css_parser_try_ident (parser, TRUE);
|
||||
if (paint->name == NULL)
|
||||
{
|
||||
_gtk_css_parser_error (parser, "Expected the name of the paint");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!_gtk_css_parser_try (parser, ")", TRUE))
|
||||
{
|
||||
_gtk_css_parser_error (parser,
|
||||
"Expected ')' at end of '-gtk-paint'");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_css_image_paint_print (GtkCssImage *image,
|
||||
GString *string)
|
||||
{
|
||||
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
|
||||
|
||||
g_string_append (string, "paint (");
|
||||
g_string_append (string, paint->name);
|
||||
g_string_append (string, ")");
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_css_image_paint_dispose (GObject *object)
|
||||
{
|
||||
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (object);
|
||||
|
||||
g_clear_pointer (&paint->name, g_free);
|
||||
|
||||
G_OBJECT_CLASS (gtk_css_image_paint_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_css_image_paint_class_init (GtkCssImagePaintClass *klass)
|
||||
{
|
||||
GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
image_class->compute = gtk_css_image_paint_compute;
|
||||
image_class->snapshot = gtk_css_image_paint_snapshot;
|
||||
image_class->parse = gtk_css_image_paint_parse;
|
||||
image_class->print = gtk_css_image_paint_print;
|
||||
image_class->equal = gtk_css_image_paint_equal;
|
||||
|
||||
object_class->dispose = gtk_css_image_paint_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_css_image_paint_init (GtkCssImagePaint *image_paint)
|
||||
{
|
||||
}
|
||||
|
@@ -122,7 +122,7 @@ gtk_css_image_paintable_is_dynamic (GtkCssImage *image)
|
||||
{
|
||||
GtkCssImagePaintable *paintable = GTK_CSS_IMAGE_PAINTABLE (image);
|
||||
|
||||
return (gdk_paintable_get_flags (paintable->paintable) & GDK_PAINTABLE_IMMUTABLE) == GDK_PAINTABLE_IMMUTABLE;
|
||||
return (gdk_paintable_get_flags (paintable->paintable) & GDK_PAINTABLE_IMMUTABLE) != GDK_PAINTABLE_IMMUTABLE;
|
||||
}
|
||||
|
||||
static GtkCssImage *
|
||||
|
53
gtk/gtkcssimagepaintprivate.h
Normal file
53
gtk/gtkcssimagepaintprivate.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_CSS_IMAGE_PAINT_PRIVATE_H__
|
||||
#define __GTK_CSS_IMAGE_PAINT_PRIVATE_H__
|
||||
|
||||
#include "gtk/gtkcssimageprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_CSS_IMAGE_PAINT (gtk_css_image_paint_get_type ())
|
||||
#define GTK_CSS_IMAGE_PAINT(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaint))
|
||||
#define GTK_CSS_IMAGE_PAINT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaintClass))
|
||||
#define GTK_IS_CSS_IMAGE_PAINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_CSS_IMAGE_PAINT))
|
||||
#define GTK_IS_CSS_IMAGE_PAINT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_CSS_IMAGE_PAINT))
|
||||
#define GTK_CSS_IMAGE_PAINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaintClass))
|
||||
|
||||
typedef struct _GtkCssImagePaint GtkCssImagePaint;
|
||||
typedef struct _GtkCssImagePaintClass GtkCssImagePaintClass;
|
||||
|
||||
struct _GtkCssImagePaint
|
||||
{
|
||||
GtkCssImage parent;
|
||||
|
||||
char *name; /* the name of the paint */
|
||||
};
|
||||
|
||||
struct _GtkCssImagePaintClass
|
||||
{
|
||||
GtkCssImageClass parent_class;
|
||||
};
|
||||
|
||||
GType gtk_css_image_paint_get_type (void) G_GNUC_CONST;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_CSS_IMAGE_PAINT_PRIVATE_H__ */
|
@@ -386,7 +386,7 @@ delete_one_texture (gpointer data)
|
||||
Texture *texture = data;
|
||||
|
||||
if (texture->holder)
|
||||
gdk_gl_texture_release (texture->holder);
|
||||
gdk_gl_texture_release (GDK_GL_TEXTURE (texture->holder));
|
||||
|
||||
if (texture->id != 0)
|
||||
{
|
||||
|
@@ -119,6 +119,7 @@
|
||||
#include "gtkdebugupdatesprivate.h"
|
||||
#include "gtkdndprivate.h"
|
||||
#include "gtkmain.h"
|
||||
#include "gtkmediafileprivate.h"
|
||||
#include "gtkmenu.h"
|
||||
#include "gtkmodulesprivate.h"
|
||||
#include "gtkprivate.h"
|
||||
@@ -632,6 +633,7 @@ default_display_notify_cb (GdkDisplayManager *dm)
|
||||
debug_flags[0].display = gdk_display_get_default ();
|
||||
gtk_print_backends_init ();
|
||||
gtk_im_modules_init ();
|
||||
gtk_media_file_extension_init ();
|
||||
_gtk_accessibility_init ();
|
||||
}
|
||||
|
||||
|
501
gtk/gtkmediacontrols.c
Normal file
501
gtk/gtkmediacontrols.c
Normal file
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmediacontrols.h"
|
||||
|
||||
#include "gtkadjustment.h"
|
||||
#include "gtkbutton.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtklabel.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkmediacontrols
|
||||
* @title: GtkMediaControls
|
||||
* @short_description: A widget showing controls for a media stream
|
||||
*
|
||||
* GtkMediaControls is a widget to show controls for a #GtkMediaStream
|
||||
* and giving users a way to use it.
|
||||
*/
|
||||
|
||||
struct _GtkMediaControls
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GtkMediaStream *stream;
|
||||
|
||||
GtkAdjustment *time_adjustment;
|
||||
GtkAdjustment *volume_adjustment;
|
||||
GtkWidget *box;
|
||||
GtkWidget *play_button;
|
||||
GtkWidget *time_box;
|
||||
GtkWidget *time_label;
|
||||
GtkWidget *seek_scale;
|
||||
GtkWidget *duration_label;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_MEDIA_STREAM,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GtkMediaControls, gtk_media_controls, GTK_TYPE_WIDGET)
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
/* FIXME: Remove
|
||||
* See https://bugzilla.gnome.org/show_bug.cgi?id=679850 */
|
||||
static char *
|
||||
totem_time_to_string (gint64 usecs,
|
||||
gboolean remaining,
|
||||
gboolean force_hour)
|
||||
{
|
||||
int sec, min, hour, _time;
|
||||
|
||||
_time = (int) (usecs / G_USEC_PER_SEC);
|
||||
/* When calculating the remaining time,
|
||||
* we want to make sure that:
|
||||
* current time + time remaining = total run time */
|
||||
if (remaining)
|
||||
_time++;
|
||||
|
||||
sec = _time % 60;
|
||||
_time = _time - sec;
|
||||
min = (_time % (60*60)) / 60;
|
||||
_time = _time - (min * 60);
|
||||
hour = _time / (60*60);
|
||||
|
||||
if (hour > 0 || force_hour) {
|
||||
if (!remaining) {
|
||||
/* hour:minutes:seconds */
|
||||
/* Translators: This is a time format, like "-9:05:02" for 9
|
||||
* hours, 5 minutes, and 2 seconds. You may change ":" to
|
||||
* the separator that your locale uses or use "%Id" instead
|
||||
* of "%d" if your locale uses localized digits.
|
||||
*/
|
||||
return g_strdup_printf (C_("long time format", "%d:%02d:%02d"), hour, min, sec);
|
||||
} else {
|
||||
/* -hour:minutes:seconds */
|
||||
/* Translators: This is a time format, like "-9:05:02" for 9
|
||||
* hours, 5 minutes, and 2 seconds playback remaining. You may
|
||||
* change ":" to the separator that your locale uses or use
|
||||
* "%Id" instead of "%d" if your locale uses localized digits.
|
||||
*/
|
||||
return g_strdup_printf (C_("long time format", "-%d:%02d:%02d"), hour, min, sec);
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining) {
|
||||
/* -minutes:seconds */
|
||||
/* Translators: This is a time format, like "-5:02" for 5
|
||||
* minutes and 2 seconds playback remaining. You may change
|
||||
* ":" to the separator that your locale uses or use "%Id"
|
||||
* instead of "%d" if your locale uses localized digits.
|
||||
*/
|
||||
return g_strdup_printf (C_("short time format", "-%d:%02d"), min, sec);
|
||||
}
|
||||
|
||||
/* minutes:seconds */
|
||||
/* Translators: This is a time format, like "5:02" for 5
|
||||
* minutes and 2 seconds. You may change ":" to the
|
||||
* separator that your locale uses or use "%Id" instead of
|
||||
* "%d" if your locale uses localized digits.
|
||||
*/
|
||||
return g_strdup_printf (C_("short time format", "%d:%02d"), min, sec);
|
||||
}
|
||||
|
||||
static void
|
||||
time_adjustment_changed (GtkAdjustment *adjustment,
|
||||
GtkMediaControls *controls)
|
||||
{
|
||||
if (controls->stream == NULL)
|
||||
return;
|
||||
|
||||
/* We just updated the adjustment and it's correct now */
|
||||
if (gtk_adjustment_get_value (adjustment) == (double) gtk_media_stream_get_timestamp (controls->stream) / G_USEC_PER_SEC)
|
||||
return;
|
||||
|
||||
gtk_media_stream_seek (controls->stream,
|
||||
gtk_adjustment_get_value (adjustment) * G_USEC_PER_SEC + 0.5);
|
||||
}
|
||||
|
||||
static void
|
||||
volume_adjustment_changed (GtkAdjustment *adjustment,
|
||||
GtkMediaControls *controls)
|
||||
{
|
||||
if (controls->stream == NULL)
|
||||
return;
|
||||
|
||||
/* We just updated the adjustment and it's correct now */
|
||||
if (gtk_adjustment_get_value (adjustment) == gtk_media_stream_get_volume (controls->stream))
|
||||
return;
|
||||
|
||||
gtk_media_stream_set_muted (controls->stream, gtk_adjustment_get_value (adjustment) == 0.0);
|
||||
gtk_media_stream_set_volume (controls->stream, gtk_adjustment_get_value (adjustment));
|
||||
}
|
||||
|
||||
static void
|
||||
play_button_clicked (GtkWidget *button,
|
||||
GtkMediaControls *controls)
|
||||
{
|
||||
if (controls->stream == NULL)
|
||||
return;
|
||||
|
||||
gtk_media_stream_set_playing (controls->stream,
|
||||
!gtk_media_stream_get_playing (controls->stream));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
|
||||
|
||||
gtk_widget_measure (controls->box,
|
||||
orientation,
|
||||
for_size,
|
||||
minimum, natural,
|
||||
minimum_baseline, natural_baseline);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_size_allocate (GtkWidget *widget,
|
||||
const GtkAllocation *allocation,
|
||||
int baseline,
|
||||
GtkAllocation *out_clip)
|
||||
{
|
||||
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
|
||||
|
||||
gtk_widget_size_allocate (controls->box, allocation, baseline, out_clip);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_dispose (GObject *object)
|
||||
{
|
||||
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
|
||||
|
||||
gtk_media_controls_set_media_stream (controls, NULL);
|
||||
|
||||
g_clear_pointer (&controls->box, gtk_widget_unparent);
|
||||
|
||||
G_OBJECT_CLASS (gtk_media_controls_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_MEDIA_STREAM:
|
||||
g_value_set_object (value, controls->stream);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_MEDIA_STREAM:
|
||||
gtk_media_controls_set_media_stream (controls, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_class_init (GtkMediaControlsClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
widget_class->measure = gtk_media_controls_measure;
|
||||
widget_class->size_allocate = gtk_media_controls_size_allocate;
|
||||
|
||||
gobject_class->dispose = gtk_media_controls_dispose;
|
||||
gobject_class->get_property = gtk_media_controls_get_property;
|
||||
gobject_class->set_property = gtk_media_controls_set_property;
|
||||
|
||||
/**
|
||||
* GtkMediaControls:media-stream:
|
||||
*
|
||||
* The media-stream managed by this object or %NULL if none.
|
||||
*/
|
||||
properties[PROP_MEDIA_STREAM] =
|
||||
g_param_spec_object ("media-stream",
|
||||
P_("Media Stream"),
|
||||
P_("The media stream managed"),
|
||||
GTK_TYPE_MEDIA_STREAM,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkmediacontrols.ui");
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_adjustment);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, volume_adjustment);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, play_button);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_label);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, seek_scale);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, duration_label);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, play_button_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, time_adjustment_changed);
|
||||
gtk_widget_class_bind_template_callback (widget_class, volume_adjustment_changed);
|
||||
|
||||
gtk_widget_class_set_css_name (widget_class, I_("controls"));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_init (GtkMediaControls *controls)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (controls));
|
||||
gtk_widget_set_has_window (GTK_WIDGET (controls), FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_controls_new:
|
||||
* @stream: (allow-none) (transfer none): a #GtkMediaStream to
|
||||
* manage or %NULL for none.
|
||||
*
|
||||
* Creates a new #GtkMediaControls managing the @stream passed to it.
|
||||
*
|
||||
* Returns: a new #GtkMediaControls
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_media_controls_new (GtkMediaStream *stream)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_MEDIA_CONTROLS,
|
||||
"media-stream", stream,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_controls_get_media_stream:
|
||||
* @controls: a #GtkMediaControls
|
||||
*
|
||||
* Gets the media stream managed by @controls or %NULL if none.
|
||||
*
|
||||
* Returns: (nullable): The media stream managed by @controls
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_controls_get_media_stream (GtkMediaControls *controls)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_MEDIA_CONTROLS (controls), NULL);
|
||||
|
||||
return controls->stream;
|
||||
}
|
||||
|
||||
static void
|
||||
update_timestamp (GtkMediaControls *controls)
|
||||
{
|
||||
gint64 timestamp, duration;
|
||||
char *time_string;
|
||||
|
||||
if (controls->stream)
|
||||
{
|
||||
timestamp = gtk_media_stream_get_timestamp (controls->stream);
|
||||
duration = gtk_media_stream_get_duration (controls->stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
timestamp = 0;
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
time_string = totem_time_to_string (timestamp, FALSE, FALSE);
|
||||
gtk_label_set_text (GTK_LABEL (controls->time_label), time_string);
|
||||
g_free (time_string);
|
||||
|
||||
if (duration > 0)
|
||||
{
|
||||
time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
|
||||
gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
|
||||
g_free (time_string);
|
||||
|
||||
gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_duration (GtkMediaControls *controls)
|
||||
{
|
||||
gint64 timestamp, duration;
|
||||
char *time_string;
|
||||
|
||||
if (controls->stream)
|
||||
{
|
||||
timestamp = gtk_media_stream_get_timestamp (controls->stream);
|
||||
duration = gtk_media_stream_get_duration (controls->stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
timestamp = 0;
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
|
||||
gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
|
||||
gtk_widget_set_visible (controls->duration_label, duration > 0);
|
||||
g_free (time_string);
|
||||
|
||||
gtk_adjustment_set_upper (controls->time_adjustment,
|
||||
gtk_adjustment_get_page_size (controls->time_adjustment)
|
||||
+ (double) duration / G_USEC_PER_SEC);
|
||||
gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
|
||||
}
|
||||
|
||||
static void
|
||||
update_playing (GtkMediaControls *controls)
|
||||
{
|
||||
gboolean playing;
|
||||
const char *icon_name;
|
||||
|
||||
if (controls->stream)
|
||||
playing = gtk_media_stream_get_playing (controls->stream);
|
||||
else
|
||||
playing = FALSE;
|
||||
|
||||
if (playing)
|
||||
icon_name = "media-playback-pause-symbolic";
|
||||
else
|
||||
icon_name = "media-playback-start-symbolic";
|
||||
|
||||
gtk_button_set_icon_name (GTK_BUTTON (controls->play_button), icon_name);
|
||||
}
|
||||
|
||||
static void
|
||||
update_seekable (GtkMediaControls *controls)
|
||||
{
|
||||
gboolean seekable;
|
||||
|
||||
if (controls->stream)
|
||||
seekable = gtk_media_stream_is_seekable (controls->stream);
|
||||
else
|
||||
seekable = FALSE;
|
||||
|
||||
gtk_widget_set_sensitive (controls->seek_scale, seekable);
|
||||
}
|
||||
|
||||
static void
|
||||
update_volume (GtkMediaControls *controls)
|
||||
{
|
||||
double volume;
|
||||
|
||||
if (controls->stream == NULL)
|
||||
volume = 1.0;
|
||||
else if (gtk_media_stream_get_muted (controls->stream))
|
||||
volume = 0.0;
|
||||
else
|
||||
volume = gtk_media_stream_get_volume (controls->stream);
|
||||
|
||||
gtk_adjustment_set_value (controls->volume_adjustment, volume);
|
||||
}
|
||||
|
||||
static void
|
||||
update_all (GtkMediaControls *controls)
|
||||
{
|
||||
update_timestamp (controls);
|
||||
update_duration (controls);
|
||||
update_playing (controls);
|
||||
update_seekable (controls);
|
||||
update_volume (controls);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_controls_notify_cb (GtkMediaStream *stream,
|
||||
GParamSpec *pspec,
|
||||
GtkMediaControls *controls)
|
||||
{
|
||||
if (g_str_equal (pspec->name, "timestamp"))
|
||||
update_timestamp (controls);
|
||||
else if (g_str_equal (pspec->name, "duration"))
|
||||
update_duration (controls);
|
||||
else if (g_str_equal (pspec->name, "playing"))
|
||||
update_playing (controls);
|
||||
else if (g_str_equal (pspec->name, "seekable"))
|
||||
update_seekable (controls);
|
||||
else if (g_str_equal (pspec->name, "muted"))
|
||||
update_volume (controls);
|
||||
else if (g_str_equal (pspec->name, "volume"))
|
||||
update_volume (controls);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_media_controls_set_media_stream (GtkMediaControls *controls,
|
||||
GtkMediaStream *stream)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_MEDIA_CONTROLS (controls));
|
||||
g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
|
||||
|
||||
if (controls->stream == stream)
|
||||
return;
|
||||
|
||||
if (controls->stream)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (controls->stream,
|
||||
gtk_media_controls_notify_cb,
|
||||
controls);
|
||||
g_object_unref (controls->stream);
|
||||
controls->stream = NULL;
|
||||
}
|
||||
|
||||
if (stream)
|
||||
{
|
||||
controls->stream = g_object_ref (stream);
|
||||
g_signal_connect (controls->stream,
|
||||
"notify",
|
||||
G_CALLBACK (gtk_media_controls_notify_cb),
|
||||
controls);
|
||||
}
|
||||
|
||||
update_all (controls);
|
||||
gtk_widget_set_sensitive (controls->box, stream != NULL);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (controls), properties[PROP_MEDIA_STREAM]);
|
||||
}
|
||||
|
45
gtk/gtkmediacontrols.h
Normal file
45
gtk/gtkmediacontrols.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MEDIA_CONTROLS_H__
|
||||
#define __GTK_MEDIA_CONTROLS_H__
|
||||
|
||||
#include <gtk/gtkmediastream.h>
|
||||
#include <gtk/gtkwidget.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_MEDIA_CONTROLS (gtk_media_controls_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_FINAL_TYPE (GtkMediaControls, gtk_media_controls, GTK, MEDIA_CONTROLS, GtkWidget)
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget *gtk_media_controls_new (GtkMediaStream *stream);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream *gtk_media_controls_get_media_stream (GtkMediaControls *controls);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_controls_set_media_stream (GtkMediaControls *controls,
|
||||
GtkMediaStream *stream);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_MEDIA_CONTROLS_H__ */
|
629
gtk/gtkmediafile.c
Normal file
629
gtk/gtkmediafile.c
Normal file
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmediafileprivate.h"
|
||||
|
||||
#include "gtkdebug.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtkmodulesprivate.h"
|
||||
#include "gtknomediafileprivate.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkmediafile
|
||||
* @Short_description: Open media files for use in GTK
|
||||
* @Title: GtkMediaFile
|
||||
* @See_also: #GtkMediaStream
|
||||
*
|
||||
* #GtkMediaFile is the implementation for media file usage with #GtkMediaStream.
|
||||
*
|
||||
* This provides a simple way to play back video files with GTK.
|
||||
*/
|
||||
|
||||
typedef struct _GtkMediaFilePrivate GtkMediaFilePrivate;
|
||||
|
||||
struct _GtkMediaFilePrivate
|
||||
{
|
||||
GFile *file;
|
||||
GInputStream *input_stream;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_FILE,
|
||||
PROP_INPUT_STREAM,
|
||||
|
||||
N_PROPS,
|
||||
};
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMediaFile, gtk_media_file, GTK_TYPE_MEDIA_STREAM,
|
||||
G_ADD_PRIVATE (GtkMediaFile))
|
||||
|
||||
#define GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
|
||||
g_critical ("Media file of type '%s' does not implement GtkMediaFile::" # method, G_OBJECT_TYPE_NAME (obj))
|
||||
|
||||
static void
|
||||
gtk_media_file_default_open (GtkMediaFile *self)
|
||||
{
|
||||
GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD (self, open);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_default_close (GtkMediaFile *self)
|
||||
{
|
||||
gtk_media_stream_unprepared (GTK_MEDIA_STREAM (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkMediaFile *self = GTK_MEDIA_FILE (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_FILE:
|
||||
gtk_media_file_set_file (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
case PROP_INPUT_STREAM:
|
||||
gtk_media_file_set_input_stream (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMediaFile *self = GTK_MEDIA_FILE (object);
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_FILE:
|
||||
g_value_set_object (value, priv->file);
|
||||
break;
|
||||
|
||||
case PROP_INPUT_STREAM:
|
||||
g_value_set_object (value, priv->input_stream);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_dispose (GObject *object)
|
||||
{
|
||||
GtkMediaFile *self = GTK_MEDIA_FILE (object);
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_clear_object (&priv->file);
|
||||
g_clear_object (&priv->input_stream);
|
||||
|
||||
G_OBJECT_CLASS (gtk_media_file_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_class_init (GtkMediaFileClass *class)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
||||
|
||||
class->open = gtk_media_file_default_open;
|
||||
class->close = gtk_media_file_default_close;
|
||||
|
||||
gobject_class->set_property = gtk_media_file_set_property;
|
||||
gobject_class->get_property = gtk_media_file_get_property;
|
||||
gobject_class->dispose = gtk_media_file_dispose;
|
||||
|
||||
/**
|
||||
* GtkMediaFile:file:
|
||||
*
|
||||
* The file being played back or %NULL if not playing a file.
|
||||
*/
|
||||
properties[PROP_FILE] =
|
||||
g_param_spec_object ("file",
|
||||
P_("File"),
|
||||
P_("File being played back"),
|
||||
G_TYPE_FILE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* GtkMediaFile:input-stream:
|
||||
*
|
||||
* The stream being played back or %NULL if not playing a stream, like when playing a file.
|
||||
*/
|
||||
properties[PROP_INPUT_STREAM] =
|
||||
g_param_spec_object ("input-stream",
|
||||
P_("Input stream"),
|
||||
P_("Input stream being played back"),
|
||||
G_TYPE_INPUT_STREAM,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_media_file_init (GtkMediaFile *self)
|
||||
{
|
||||
}
|
||||
|
||||
static GType
|
||||
gtk_media_file_get_impl_type (void)
|
||||
{
|
||||
static GType impl_type = G_TYPE_NONE;
|
||||
const char *extension_name;
|
||||
GIOExtension *e;
|
||||
GIOExtensionPoint *ep;
|
||||
|
||||
if (G_LIKELY (impl_type != G_TYPE_NONE))
|
||||
return impl_type;
|
||||
|
||||
GTK_NOTE (MODULES, g_print ("Looking up MediaFile extension\n"));
|
||||
|
||||
ep = g_io_extension_point_lookup (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
|
||||
e = NULL;
|
||||
|
||||
extension_name = g_getenv ("GTK_MEDIA");
|
||||
if (extension_name)
|
||||
{
|
||||
if (g_str_equal (extension_name, "help"))
|
||||
{
|
||||
GList *l;
|
||||
|
||||
g_print ("Supported arguments for GTK_MEDIA environment variable:\n");
|
||||
|
||||
for (l = g_io_extension_point_get_extensions (ep); l; l = l->next)
|
||||
{
|
||||
e = l->data;
|
||||
|
||||
g_print ("%10s - %d\n", g_io_extension_get_name (e), g_io_extension_get_priority (e));
|
||||
}
|
||||
|
||||
e = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
e = g_io_extension_point_get_extension_by_name (ep, extension_name);
|
||||
if (e == NULL)
|
||||
{
|
||||
g_warning ("Media extension \"%s\" from GTK_MEDIA environment variable not found.", extension_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e == NULL)
|
||||
{
|
||||
GList *l = g_io_extension_point_get_extensions (ep);
|
||||
|
||||
if (l == NULL)
|
||||
{
|
||||
g_error ("GTK was run without any GtkMediaFile extension being present. This must not happen.");
|
||||
}
|
||||
|
||||
e = l->data;
|
||||
}
|
||||
|
||||
impl_type = g_io_extension_get_type (e);
|
||||
|
||||
GTK_NOTE (MODULES, g_print ("Using %s from \"%s\" extension\n", g_type_name (impl_type), g_io_extension_get_name (e)));
|
||||
|
||||
return impl_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_new:
|
||||
*
|
||||
* Creates a new empty media file.
|
||||
*
|
||||
* Returns: a new #GtkMediaFile
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_file_new (void)
|
||||
{
|
||||
return g_object_new (gtk_media_file_get_impl_type (), NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_new_for_filename:
|
||||
* @filename: filename to open
|
||||
*
|
||||
* This is a utility function that converts the given @filename
|
||||
* to a #GFile and calls gtk_media_file_new_for_file().
|
||||
*
|
||||
* Returns: a new #GtkMediaFile playing @filename
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_file_new_for_filename (const char *filename)
|
||||
{
|
||||
GtkMediaStream *result;
|
||||
GFile *file;
|
||||
|
||||
if (filename)
|
||||
file = g_file_new_for_path (filename);
|
||||
else
|
||||
file = NULL;
|
||||
|
||||
result = gtk_media_file_new_for_file (file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_new_for_resource:
|
||||
* @resource_path: resource path to open
|
||||
*
|
||||
* This is a utility function that converts the given @resource
|
||||
* to a #GFile and calls gtk_media_file_new_for_file().
|
||||
*
|
||||
* Returns: a new #GtkMediaFile playing @resource_path
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_file_new_for_resource (const char *resource_path)
|
||||
{
|
||||
GtkMediaStream *result;
|
||||
GFile *file;
|
||||
|
||||
if (resource_path)
|
||||
{
|
||||
char *uri, *escaped;
|
||||
|
||||
escaped = g_uri_escape_string (resource_path,
|
||||
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
|
||||
uri = g_strconcat ("resource://", escaped, NULL);
|
||||
g_free (escaped);
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
g_free (uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
result = gtk_media_file_new_for_file (file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_new_for_file:
|
||||
* @file: (allow-none): The file to play
|
||||
*
|
||||
* Creates a new media file to play @file.
|
||||
*
|
||||
* Returns: a new #GtkMediaFile playing @file
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_file_new_for_file (GFile *file)
|
||||
{
|
||||
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
|
||||
|
||||
return g_object_new (gtk_media_file_get_impl_type (),
|
||||
"file", file,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_new_for_input_stream:
|
||||
* @stream: (allow-none): The stream to play
|
||||
*
|
||||
* Creates a new media file to play @stream. If you want the
|
||||
* resulting media to be seekable, the stream should implement
|
||||
* the #GSeekable interface.
|
||||
*
|
||||
* Returns: a new #GtkMediaFile
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_media_file_new_for_input_stream (GInputStream *stream)
|
||||
{
|
||||
g_return_val_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream), NULL);
|
||||
|
||||
return g_object_new (gtk_media_file_get_impl_type (),
|
||||
"input-stream", stream,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_media_file_is_open (GtkMediaFile *self)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
return priv->file || priv->input_stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_clear:
|
||||
* @self: a #GtkMediaFile
|
||||
*
|
||||
* Resets the media file to be empty.
|
||||
**/
|
||||
void
|
||||
gtk_media_file_clear (GtkMediaFile *self)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
|
||||
|
||||
if (!gtk_media_file_is_open (self))
|
||||
return;
|
||||
|
||||
GTK_MEDIA_FILE_GET_CLASS (self)->close (self);
|
||||
|
||||
if (priv->input_stream)
|
||||
{
|
||||
g_clear_object (&priv->input_stream);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPUT_STREAM]);
|
||||
}
|
||||
if (priv->file)
|
||||
{
|
||||
g_clear_object (&priv->file);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_set_filename:
|
||||
* @self: a #GtkMediaFile
|
||||
* @filename: (allow-none): name of file to play
|
||||
*
|
||||
* This is a utility function that converts the given @filename
|
||||
* to a #GFile and calls gtk_media_file_set_file().
|
||||
**/
|
||||
void
|
||||
gtk_media_file_set_filename (GtkMediaFile *self,
|
||||
const char *filename)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
|
||||
|
||||
if (filename)
|
||||
file = g_file_new_for_path (filename);
|
||||
else
|
||||
file = NULL;
|
||||
|
||||
gtk_media_file_set_file (self, file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_set_resource:
|
||||
* @self: a #GtkMediaFile
|
||||
* @resource_path: (allow-none): path to resource to play
|
||||
*
|
||||
* This is a utility function that converts the given @resource_path
|
||||
* to a #GFile and calls gtk_media_file_set_file().
|
||||
**/
|
||||
void
|
||||
gtk_media_file_set_resource (GtkMediaFile *self,
|
||||
const char *resource_path)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
|
||||
|
||||
if (resource_path)
|
||||
{
|
||||
char *uri, *escaped;
|
||||
|
||||
escaped = g_uri_escape_string (resource_path,
|
||||
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
|
||||
uri = g_strconcat ("resource://", escaped, NULL);
|
||||
g_free (escaped);
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
g_free (uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
|
||||
gtk_media_file_set_file (self, file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_set_file:
|
||||
* @self: a #GtkMediaFile
|
||||
* @file: (allow-none): the file to play
|
||||
*
|
||||
* If any file is still playing, stop playing it.
|
||||
*
|
||||
* Then start playing the given @file.
|
||||
**/
|
||||
void
|
||||
gtk_media_file_set_file (GtkMediaFile *self,
|
||||
GFile *file)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
|
||||
g_return_if_fail (file == NULL || G_IS_FILE (file));
|
||||
|
||||
if (file)
|
||||
g_object_ref (file);
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
gtk_media_file_clear (self);
|
||||
|
||||
if (file)
|
||||
{
|
||||
priv->file = file;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
|
||||
|
||||
GTK_MEDIA_FILE_GET_CLASS (self)->open (self);
|
||||
}
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_get_file:
|
||||
* @self: a #GtkMediaFile
|
||||
*
|
||||
* Returns the file that @self is currently playing from.
|
||||
*
|
||||
* When @self is not playing or not playing from a file,
|
||||
* %NULL is returned.
|
||||
*
|
||||
* Returns: (nullable): The currently playing file or %NULL if not
|
||||
* playing from a file.
|
||||
**/
|
||||
GFile *
|
||||
gtk_media_file_get_file (GtkMediaFile *self)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
|
||||
|
||||
return priv->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_set_input_stream:
|
||||
* @self: a #GtkMediaFile
|
||||
* @stream: (allow-none): the stream to play from
|
||||
*
|
||||
* If anything is still playing, stop playing it. Then start
|
||||
* playing the given @stream.
|
||||
*
|
||||
* Full control about the @stream is assumed for the duration of
|
||||
* playback. The stream will not bt be closed.
|
||||
**/
|
||||
void
|
||||
gtk_media_file_set_input_stream (GtkMediaFile *self,
|
||||
GInputStream *stream)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
|
||||
g_return_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream));
|
||||
|
||||
if (stream)
|
||||
g_object_ref (stream);
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
gtk_media_file_clear (self);
|
||||
|
||||
if (stream)
|
||||
{
|
||||
priv->input_stream = stream;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPUT_STREAM]);
|
||||
|
||||
GTK_MEDIA_FILE_GET_CLASS (self)->open (self);
|
||||
}
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_media_file_get_input_stream:
|
||||
* @self: a #GtkMediaFile
|
||||
*
|
||||
* Returns the stream that @self is currently playing from.
|
||||
*
|
||||
* When @self is not playing or not playing from a stream,
|
||||
* %NULL is returned.
|
||||
*
|
||||
* Returns: (nullable): The currently playing stream or %NULL if not
|
||||
* playing from a stream.
|
||||
**/
|
||||
GInputStream *
|
||||
gtk_media_file_get_input_stream (GtkMediaFile *self)
|
||||
{
|
||||
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
|
||||
|
||||
return priv->input_stream;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_media_file_extension_init (void)
|
||||
{
|
||||
GIOExtensionPoint *ep;
|
||||
GIOModuleScope *scope;
|
||||
char **paths;
|
||||
int i;
|
||||
|
||||
GTK_NOTE (MODULES,
|
||||
g_print ("Registering extension point %s\n", GTK_MEDIA_FILE_EXTENSION_POINT_NAME));
|
||||
|
||||
ep = g_io_extension_point_register (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
|
||||
g_io_extension_point_set_required_type (ep, GTK_TYPE_MEDIA_FILE);
|
||||
|
||||
g_type_ensure (GTK_TYPE_NO_MEDIA_FILE);
|
||||
|
||||
scope = g_io_module_scope_new (G_IO_MODULE_SCOPE_BLOCK_DUPLICATES);
|
||||
|
||||
paths = _gtk_get_module_path ("media");
|
||||
for (i = 0; paths[i]; i++)
|
||||
{
|
||||
GTK_NOTE (MODULES,
|
||||
g_print ("Scanning io modules in %s\n", paths[i]));
|
||||
g_io_modules_scan_all_in_directory_with_scope (paths[i], scope);
|
||||
}
|
||||
g_strfreev (paths);
|
||||
|
||||
g_io_module_scope_free (scope);
|
||||
|
||||
if (GTK_DEBUG_CHECK (MODULES))
|
||||
{
|
||||
GList *list, *l;
|
||||
|
||||
list = g_io_extension_point_get_extensions (ep);
|
||||
for (l = list; l; l = l->next)
|
||||
{
|
||||
GIOExtension *ext = l->data;
|
||||
g_print ("extension: %s: type %s\n",
|
||||
g_io_extension_get_name (ext),
|
||||
g_type_name (g_io_extension_get_type (ext)));
|
||||
}
|
||||
}
|
||||
}
|
86
gtk/gtkmediafile.h
Normal file
86
gtk/gtkmediafile.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MEDIA_FILE_H__
|
||||
#define __GTK_MEDIA_FILE_H__
|
||||
|
||||
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gtk/gtk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <gtk/gtkmediastream.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_MEDIA_FILE_EXTENSION_POINT_NAME "gtk-media-file"
|
||||
|
||||
#define GTK_TYPE_MEDIA_FILE (gtk_media_file_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_DERIVABLE_TYPE (GtkMediaFile, gtk_media_file, GTK, MEDIA_FILE, GtkMediaStream)
|
||||
|
||||
struct _GtkMediaFileClass
|
||||
{
|
||||
GtkMediaStreamClass parent_class;
|
||||
|
||||
void (* open) (GtkMediaFile *self);
|
||||
void (* close) (GtkMediaFile *self);
|
||||
|
||||
/* Padding for future expansion */
|
||||
void (*_gtk_reserved1) (void);
|
||||
void (*_gtk_reserved2) (void);
|
||||
void (*_gtk_reserved3) (void);
|
||||
void (*_gtk_reserved4) (void);
|
||||
};
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream * gtk_media_file_new (void);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream * gtk_media_file_new_for_filename (const char *filename);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream * gtk_media_file_new_for_resource (const char *resource_path);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream * gtk_media_file_new_for_file (GFile *file);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream * gtk_media_file_new_for_input_stream (GInputStream *stream);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_file_clear (GtkMediaFile *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_file_set_filename (GtkMediaFile *self,
|
||||
const char *filename);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_file_set_resource (GtkMediaFile *self,
|
||||
const char *resource_path);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_file_set_file (GtkMediaFile *self,
|
||||
GFile *file);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GFile * gtk_media_file_get_file (GtkMediaFile *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_file_set_input_stream (GtkMediaFile *self,
|
||||
GInputStream *stream);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GInputStream * gtk_media_file_get_input_stream (GtkMediaFile *self);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_MEDIA_FILE_H__ */
|
29
gtk/gtkmediafileprivate.h
Normal file
29
gtk/gtkmediafileprivate.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MEDIA_FILE_PRIVATE_H__
|
||||
#define __GTK_MEDIA_FILE_PRIVATE_H__
|
||||
|
||||
#include "gtkmediafile.h"
|
||||
|
||||
|
||||
void gtk_media_file_extension_init (void);
|
||||
|
||||
|
||||
#endif /* __GTK_MEDIA_FILE_PRIVATE_H__ */
|
1260
gtk/gtkmediastream.c
Normal file
1260
gtk/gtkmediastream.c
Normal file
File diff suppressed because it is too large
Load Diff
155
gtk/gtkmediastream.h
Normal file
155
gtk/gtkmediastream.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MEDIA_STREAM_H__
|
||||
#define __GTK_MEDIA_STREAM_H__
|
||||
|
||||
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gtk/gtk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_MEDIA_STREAM (gtk_media_stream_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_DERIVABLE_TYPE (GtkMediaStream, gtk_media_stream, GTK, MEDIA_STREAM, GObject)
|
||||
|
||||
struct _GtkMediaStreamClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
gboolean (* play) (GtkMediaStream *self);
|
||||
void (* pause) (GtkMediaStream *self);
|
||||
void (* seek) (GtkMediaStream *self,
|
||||
gint64 timestamp);
|
||||
void (* update_audio) (GtkMediaStream *self,
|
||||
gboolean muted,
|
||||
double volume);
|
||||
void (* realize) (GtkMediaStream *self,
|
||||
GdkWindow *window);
|
||||
void (* unrealize) (GtkMediaStream *self,
|
||||
GdkWindow *window);
|
||||
|
||||
/* Padding for future expansion */
|
||||
void (*_gtk_reserved1) (void);
|
||||
void (*_gtk_reserved2) (void);
|
||||
void (*_gtk_reserved3) (void);
|
||||
void (*_gtk_reserved4) (void);
|
||||
void (*_gtk_reserved5) (void);
|
||||
void (*_gtk_reserved6) (void);
|
||||
void (*_gtk_reserved7) (void);
|
||||
void (*_gtk_reserved8) (void);
|
||||
};
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_is_prepared (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const GError * gtk_media_stream_get_error (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_has_audio (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_has_video (GtkMediaStream *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_play (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_pause (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_get_playing (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_set_playing (GtkMediaStream *self,
|
||||
gboolean playing);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_get_ended (GtkMediaStream *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gint64 gtk_media_stream_get_timestamp (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gint64 gtk_media_stream_get_duration (GtkMediaStream *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_is_seekable (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_is_seeking (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_seek (GtkMediaStream *self,
|
||||
gint64 timestamp);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_get_loop (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_set_loop (GtkMediaStream *self,
|
||||
gboolean loop);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_media_stream_get_muted (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_set_muted (GtkMediaStream *self,
|
||||
gboolean muted);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
double gtk_media_stream_get_volume (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_set_volume (GtkMediaStream *self,
|
||||
double volume);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_realize (GtkMediaStream *self,
|
||||
GdkWindow *window);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_unrealize (GtkMediaStream *self,
|
||||
GdkWindow *window);
|
||||
|
||||
/* for implementations only */
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_prepared (GtkMediaStream *self,
|
||||
gboolean has_audio,
|
||||
gboolean has_video,
|
||||
gboolean seekable,
|
||||
gint64 duration);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_unprepared (GtkMediaStream *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_update (GtkMediaStream *self,
|
||||
gint64 timestamp);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_ended (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_seek_success (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_seek_failed (GtkMediaStream *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_gerror (GtkMediaStream *self,
|
||||
GError *error);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_error (GtkMediaStream *self,
|
||||
GQuark domain,
|
||||
gint code,
|
||||
const gchar *format,
|
||||
...) G_GNUC_PRINTF (4, 5);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_media_stream_error_valist (GtkMediaStream *self,
|
||||
GQuark domain,
|
||||
gint code,
|
||||
const gchar *format,
|
||||
va_list args) G_GNUC_PRINTF (4, 0);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_MEDIA_STREAM_H__ */
|
63
gtk/gtknomediafile.c
Normal file
63
gtk/gtknomediafile.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtknomediafileprivate.h"
|
||||
|
||||
#include "gtkintl.h"
|
||||
|
||||
struct _GtkNoMediaFile
|
||||
{
|
||||
GtkMediaFile parent_instance;
|
||||
};
|
||||
|
||||
struct _GtkNoMediaFileClass
|
||||
{
|
||||
GtkMediaFileClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkNoMediaFile, gtk_no_media_file, GTK_TYPE_MEDIA_FILE,
|
||||
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
|
||||
g_define_type_id,
|
||||
"none",
|
||||
G_MININT);)
|
||||
|
||||
static void
|
||||
gtk_no_media_file_open (GtkMediaFile *file)
|
||||
{
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (file),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
_("GTK has been compiled without media support."));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_no_media_file_class_init (GtkNoMediaFileClass *klass)
|
||||
{
|
||||
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
|
||||
|
||||
file_class->open = gtk_no_media_file_open;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_no_media_file_init (GtkNoMediaFile *video)
|
||||
{
|
||||
}
|
||||
|
33
gtk/gtknomediafileprivate.h
Normal file
33
gtk/gtknomediafileprivate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_NO_MEDIA_FILE_H__
|
||||
#define __GTK_NO_MEDIA_FILE_H__
|
||||
|
||||
#include <gtk/gtkmediafile.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_NO_MEDIA_FILE (gtk_no_media_file_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkNoMediaFile, gtk_no_media_file, GTK, NO_MEDIA_FILE, GtkMediaFile)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_NO_MEDIA_FILE_H__ */
|
@@ -155,6 +155,42 @@ gtk_style_cascade_get_color (GtkStyleProvider *provider,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_style_cascade_get_paint (GtkStyleProvider *provider,
|
||||
const char *name)
|
||||
{
|
||||
GtkStyleCascade *cascade = GTK_STYLE_CASCADE (provider);
|
||||
GtkStyleCascadeIter iter;
|
||||
GdkPaintable *paintable;
|
||||
GtkStyleProvider *item;
|
||||
|
||||
for (item = gtk_style_cascade_iter_init (cascade, &iter);
|
||||
item;
|
||||
item = gtk_style_cascade_iter_next (cascade, &iter))
|
||||
{
|
||||
if (GTK_IS_STYLE_PROVIDER (item))
|
||||
{
|
||||
paintable = gtk_style_provider_get_paint (GTK_STYLE_PROVIDER (item), name);
|
||||
if (paintable)
|
||||
{
|
||||
gtk_style_cascade_iter_clear (&iter);
|
||||
return paintable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If somebody hits this code path, shout at them */
|
||||
}
|
||||
}
|
||||
|
||||
gtk_style_cascade_iter_clear (&iter);
|
||||
|
||||
if (cascade->paints == NULL)
|
||||
return NULL;
|
||||
|
||||
return g_hash_table_lookup (cascade->paints, name);
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_style_cascade_get_scale (GtkStyleProvider *provider)
|
||||
{
|
||||
@@ -227,6 +263,7 @@ static void
|
||||
gtk_style_cascade_provider_iface_init (GtkStyleProviderInterface *iface)
|
||||
{
|
||||
iface->get_color = gtk_style_cascade_get_color;
|
||||
iface->get_paint = gtk_style_cascade_get_paint;
|
||||
iface->get_settings = gtk_style_cascade_get_settings;
|
||||
iface->get_scale = gtk_style_cascade_get_scale;
|
||||
iface->get_keyframes = gtk_style_cascade_get_keyframes;
|
||||
@@ -244,6 +281,7 @@ gtk_style_cascade_dispose (GObject *object)
|
||||
|
||||
_gtk_style_cascade_set_parent (cascade, NULL);
|
||||
g_array_unref (cascade->providers);
|
||||
g_clear_pointer (&cascade->paints, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (_gtk_style_cascade_parent_class)->dispose (object);
|
||||
}
|
||||
@@ -386,3 +424,39 @@ _gtk_style_cascade_get_scale (GtkStyleCascade *cascade)
|
||||
|
||||
return cascade->scale;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_style_cascade_add_paint (GtkStyleCascade *cascade,
|
||||
const char *name,
|
||||
GdkPaintable *paintable)
|
||||
{
|
||||
if (cascade->paints == NULL)
|
||||
{
|
||||
cascade->paints = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_hash_table_contains (cascade->paints, name))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_hash_table_insert (cascade->paints, g_strdup (name), g_object_ref (paintable));
|
||||
gtk_style_provider_changed (GTK_STYLE_PROVIDER (cascade));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_style_cascade_remove_paint (GtkStyleCascade *cascade,
|
||||
const char *name)
|
||||
{
|
||||
gboolean result;
|
||||
|
||||
if (cascade->paints == NULL)
|
||||
return FALSE;
|
||||
|
||||
result = g_hash_table_remove (cascade->paints, name);
|
||||
if (result)
|
||||
gtk_style_provider_changed (GTK_STYLE_PROVIDER (cascade));
|
||||
return result;
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ struct _GtkStyleCascade
|
||||
GObject object;
|
||||
|
||||
GtkStyleCascade *parent;
|
||||
GHashTable *paints;
|
||||
GArray *providers;
|
||||
int scale;
|
||||
};
|
||||
@@ -62,6 +63,11 @@ void _gtk_style_cascade_add_provider (GtkStyleCascade
|
||||
guint priority);
|
||||
void _gtk_style_cascade_remove_provider (GtkStyleCascade *cascade,
|
||||
GtkStyleProvider *provider);
|
||||
gboolean gtk_style_cascade_add_paint (GtkStyleCascade *cascade,
|
||||
const char *name,
|
||||
GdkPaintable *paintable);
|
||||
gboolean gtk_style_cascade_remove_paint (GtkStyleCascade *cascade,
|
||||
const char *name);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -638,6 +638,34 @@ gtk_style_context_remove_provider_for_display (GdkDisplay *display,
|
||||
_gtk_style_cascade_remove_provider (cascade, provider);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_style_context_add_paint_for_display (GdkDisplay *display,
|
||||
const char *name,
|
||||
GdkPaintable *paintable)
|
||||
{
|
||||
GtkStyleCascade *cascade;
|
||||
|
||||
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
|
||||
g_return_val_if_fail (name != NULL, FALSE);
|
||||
g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), FALSE);
|
||||
|
||||
cascade = _gtk_settings_get_style_cascade (gtk_settings_get_for_display (display), 1);
|
||||
return gtk_style_cascade_add_paint (cascade, name, paintable);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_style_context_remove_paint_for_display (GdkDisplay *display,
|
||||
const char *name)
|
||||
{
|
||||
GtkStyleCascade *cascade;
|
||||
|
||||
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
|
||||
g_return_val_if_fail (name != NULL, FALSE);
|
||||
|
||||
cascade = _gtk_settings_get_style_cascade (gtk_settings_get_for_display (display), 1);
|
||||
return gtk_style_cascade_remove_paint (cascade, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_style_context_get_section:
|
||||
* @context: a #GtkStyleContext
|
||||
|
@@ -945,6 +945,13 @@ void gtk_style_context_add_provider_for_display (GdkDisplay *display,
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_style_context_remove_provider_for_display (GdkDisplay *display,
|
||||
GtkStyleProvider *provider);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_style_context_add_paint_for_display (GdkDisplay *display,
|
||||
const char *name,
|
||||
GdkPaintable *paintable);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_style_context_remove_paint_for_display(GdkDisplay *display,
|
||||
const char *name);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_style_context_add_provider (GtkStyleContext *context,
|
||||
|
@@ -153,6 +153,22 @@ gtk_style_provider_get_scale (GtkStyleProvider *provider)
|
||||
return iface->get_scale (provider);
|
||||
}
|
||||
|
||||
GdkPaintable *
|
||||
gtk_style_provider_get_paint (GtkStyleProvider *provider,
|
||||
const char *name)
|
||||
{
|
||||
GtkStyleProviderInterface *iface;
|
||||
|
||||
gtk_internal_return_val_if_fail (GTK_IS_STYLE_PROVIDER (provider), NULL);
|
||||
|
||||
iface = GTK_STYLE_PROVIDER_GET_INTERFACE (provider);
|
||||
|
||||
if (!iface->get_paint)
|
||||
return NULL;
|
||||
|
||||
return iface->get_paint (provider, name);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_style_provider_emit_error (GtkStyleProvider *provider,
|
||||
GtkCssSection *section,
|
||||
|
@@ -35,7 +35,9 @@ struct _GtkStyleProviderInterface
|
||||
{
|
||||
GTypeInterface g_iface;
|
||||
|
||||
GtkCssValue * (* get_color) (GtkStyleProvider *provider,
|
||||
GtkCssValue * (* get_color) (GtkStyleProvider *provider,
|
||||
const char *name);
|
||||
GdkPaintable * (* get_paint) (GtkStyleProvider *provider,
|
||||
const char *name);
|
||||
GtkSettings * (* get_settings) (GtkStyleProvider *provider);
|
||||
GtkCssKeyframes * (* get_keyframes) (GtkStyleProvider *provider,
|
||||
@@ -52,8 +54,10 @@ struct _GtkStyleProviderInterface
|
||||
void (* changed) (GtkStyleProvider *provider);
|
||||
};
|
||||
|
||||
GtkSettings * gtk_style_provider_get_settings (GtkStyleProvider *provider);
|
||||
GtkCssValue * gtk_style_provider_get_color (GtkStyleProvider *provider,
|
||||
GtkSettings * gtk_style_provider_get_settings (GtkStyleProvider *provider);
|
||||
GtkCssValue * gtk_style_provider_get_color (GtkStyleProvider *provider,
|
||||
const char *name);
|
||||
GdkPaintable * gtk_style_provider_get_paint (GtkStyleProvider *provider,
|
||||
const char *name);
|
||||
GtkCssKeyframes * gtk_style_provider_get_keyframes (GtkStyleProvider *provider,
|
||||
const char *name);
|
||||
|
651
gtk/gtkvideo.c
Normal file
651
gtk/gtkvideo.c
Normal file
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkvideo.h"
|
||||
|
||||
#include "gtkeventcontrollermotion.h"
|
||||
#include "gtkimage.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtkmediacontrols.h"
|
||||
#include "gtkmediafile.h"
|
||||
#include "gtkrevealer.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkvideo
|
||||
* @title: GtkVideo
|
||||
* @short_description: A widget for displaying video
|
||||
*
|
||||
* GtkVideo is a widget to show a #GtkMediaStream.
|
||||
*/
|
||||
|
||||
struct _GtkVideo
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GFile *file;
|
||||
GtkMediaStream *media_stream;
|
||||
|
||||
GtkEventController *motion_controller;
|
||||
|
||||
GtkWidget *box;
|
||||
GtkWidget *video_image;
|
||||
GtkWidget *overlay_icon;
|
||||
GtkWidget *controls_revealer;
|
||||
GtkWidget *controls;
|
||||
guint controls_hide_source;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_FILE,
|
||||
PROP_MEDIA_STREAM,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET)
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
static void
|
||||
gtk_video_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (widget);
|
||||
|
||||
gtk_widget_measure (self->box,
|
||||
orientation,
|
||||
for_size,
|
||||
minimum, natural,
|
||||
minimum_baseline, natural_baseline);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_size_allocate (GtkWidget *widget,
|
||||
const GtkAllocation *allocation,
|
||||
int baseline,
|
||||
GtkAllocation *out_clip)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (widget);
|
||||
|
||||
gtk_widget_size_allocate (self->box, allocation, baseline, out_clip);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_realize (GtkWidget *widget)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (widget);
|
||||
|
||||
GTK_WIDGET_CLASS (gtk_video_parent_class)->realize (widget);
|
||||
|
||||
if (self->media_stream)
|
||||
gtk_media_stream_realize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
|
||||
|
||||
if (self->file)
|
||||
gtk_media_file_set_file (GTK_MEDIA_FILE (self->media_stream), self->file);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_unrealize (GtkWidget *widget)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (widget);
|
||||
|
||||
if (self->media_stream)
|
||||
gtk_media_stream_unrealize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
|
||||
|
||||
GTK_WIDGET_CLASS (gtk_video_parent_class)->unrealize (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_unmap (GtkWidget *widget)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (widget);
|
||||
|
||||
if (self->controls_hide_source)
|
||||
{
|
||||
g_source_remove (self->controls_hide_source);
|
||||
self->controls_hide_source = 0;
|
||||
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
|
||||
}
|
||||
|
||||
/* XXX: pause video here? */
|
||||
|
||||
GTK_WIDGET_CLASS (gtk_video_parent_class)->unmap (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_dispose (GObject *object)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (object);
|
||||
|
||||
gtk_video_set_media_stream (self, NULL);
|
||||
|
||||
g_clear_object (&self->motion_controller);
|
||||
g_clear_pointer (&self->box, gtk_widget_unparent);
|
||||
|
||||
G_OBJECT_CLASS (gtk_video_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_FILE:
|
||||
g_value_set_object (value, self->file);
|
||||
break;
|
||||
|
||||
case PROP_MEDIA_STREAM:
|
||||
g_value_set_object (value, self->media_stream);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkVideo *self = GTK_VIDEO (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_FILE:
|
||||
gtk_video_set_file (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
case PROP_MEDIA_STREAM:
|
||||
gtk_video_set_media_stream (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_class_init (GtkVideoClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
widget_class->measure = gtk_video_measure;
|
||||
widget_class->size_allocate = gtk_video_size_allocate;
|
||||
widget_class->realize = gtk_video_realize;
|
||||
widget_class->unrealize = gtk_video_unrealize;
|
||||
widget_class->unmap = gtk_video_unmap;
|
||||
|
||||
gobject_class->dispose = gtk_video_dispose;
|
||||
gobject_class->get_property = gtk_video_get_property;
|
||||
gobject_class->set_property = gtk_video_set_property;
|
||||
|
||||
/**
|
||||
* GtkVideo:file:
|
||||
*
|
||||
* The file played by this video if the video is playing a file.
|
||||
*/
|
||||
properties[PROP_FILE] =
|
||||
g_param_spec_object ("file",
|
||||
P_("File"),
|
||||
P_("The video file played back"),
|
||||
G_TYPE_FILE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* GtkVideo:media-stream:
|
||||
*
|
||||
* The media-stream played
|
||||
*/
|
||||
properties[PROP_MEDIA_STREAM] =
|
||||
g_param_spec_object ("media-stream",
|
||||
P_("Media Stream"),
|
||||
P_("The media stream played"),
|
||||
GTK_TYPE_MEDIA_STREAM,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkvideo.ui");
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkVideo, box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkVideo, video_image);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkVideo, overlay_icon);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls_revealer);
|
||||
|
||||
gtk_widget_class_set_css_name (widget_class, I_("video"));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_video_hide_controls (gpointer data)
|
||||
{
|
||||
GtkVideo *self = data;
|
||||
|
||||
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
|
||||
|
||||
self->controls_hide_source = 0;
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_motion (GtkEventControllerMotion *motion,
|
||||
double x,
|
||||
double y,
|
||||
GtkVideo *self)
|
||||
{
|
||||
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), TRUE);
|
||||
if (self->controls_hide_source)
|
||||
g_source_remove (self->controls_hide_source);
|
||||
self->controls_hide_source = g_timeout_add (5 * 1000,
|
||||
gtk_video_hide_controls,
|
||||
self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_init (GtkVideo *self)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
|
||||
|
||||
self->motion_controller = gtk_event_controller_motion_new (GTK_WIDGET (self));
|
||||
g_signal_connect (self->motion_controller, "motion", G_CALLBACK (gtk_video_motion), self);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_new:
|
||||
*
|
||||
* Creates a new empty #GtkVideo.
|
||||
*
|
||||
* Returns: a new #GtkVideo
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_video_new (void)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_VIDEO, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_new_for_media_stream:
|
||||
* @stream: (allow-none): a #GtkMediaStream
|
||||
*
|
||||
* Creates a #GtkVideo to play back the given @stream.
|
||||
*
|
||||
* Returns: a new #GtkVideo
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_video_new_for_media_stream (GtkMediaStream *stream)
|
||||
{
|
||||
g_return_val_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream), NULL);
|
||||
|
||||
return g_object_new (GTK_TYPE_VIDEO,
|
||||
"media-stream", stream,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_new_for_file:
|
||||
* @file: (allow-none): a #GFile
|
||||
*
|
||||
* Creates a #GtkVideo to play back the given @file.
|
||||
*
|
||||
* Returns: a new #GtkVideo
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_video_new_for_file (GFile *file)
|
||||
{
|
||||
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
|
||||
|
||||
return g_object_new (GTK_TYPE_VIDEO,
|
||||
"file", file,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_new_for_filename:
|
||||
* @filename: (allow-none): filename to play back
|
||||
*
|
||||
* Creates a #GtkVideo to play back the given @filename.
|
||||
*
|
||||
* This is a utility function that calls gtk_video_new_for_file(),
|
||||
*
|
||||
* Returns: a new #GtkVideo
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_video_new_for_filename (const char *filename)
|
||||
{
|
||||
GtkWidget *result;
|
||||
GFile *file;
|
||||
|
||||
if (filename)
|
||||
file = g_file_new_for_path (filename);
|
||||
else
|
||||
file = NULL;
|
||||
|
||||
result = gtk_video_new_for_file (file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_new_for_resource:
|
||||
* @filename: (allow-none): resource path to play back
|
||||
*
|
||||
* Creates a #GtkVideo to play back the resource at the
|
||||
* given @resource_path.
|
||||
*
|
||||
* This is a utility function that calls gtk_video_new_for_file(),
|
||||
*
|
||||
* Returns: a new #GtkVideo
|
||||
**/
|
||||
GtkWidget *
|
||||
gtk_video_new_for_resource (const char *resource_path)
|
||||
{
|
||||
GtkWidget *result;
|
||||
GFile *file;
|
||||
|
||||
if (resource_path)
|
||||
{
|
||||
char *uri, *escaped;
|
||||
|
||||
escaped = g_uri_escape_string (resource_path,
|
||||
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
|
||||
uri = g_strconcat ("resource://", escaped, NULL);
|
||||
g_free (escaped);
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
g_free (uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
result = gtk_video_new_for_file (file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_get_media_stream:
|
||||
* @self: a #GtkVideo
|
||||
*
|
||||
* Gets the media stream managed by @self or %NULL if none.
|
||||
*
|
||||
* Returns: (nullable): The media stream managed by @self
|
||||
**/
|
||||
GtkMediaStream *
|
||||
gtk_video_get_media_stream (GtkVideo *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_VIDEO (self), NULL);
|
||||
|
||||
return self->media_stream;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_update_overlay_icon (GtkVideo *self)
|
||||
{
|
||||
const char *icon_name;
|
||||
const GError *error = NULL;
|
||||
|
||||
if (self->media_stream == NULL)
|
||||
icon_name = "media-eject-symbolic";
|
||||
else if ((error = gtk_media_stream_get_error (self->media_stream)))
|
||||
icon_name = "dialog-error-symbolic";
|
||||
else if (gtk_media_stream_get_ended (self->media_stream))
|
||||
icon_name = "media-playlist-repeat-symbolic";
|
||||
else
|
||||
icon_name = "media-playback-start-symbolic";
|
||||
|
||||
gtk_image_set_from_icon_name (GTK_IMAGE (self->overlay_icon), icon_name);
|
||||
if (error)
|
||||
gtk_widget_set_tooltip_text (self->overlay_icon, error->message);
|
||||
else
|
||||
gtk_widget_set_tooltip_text (self->overlay_icon, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_update_ended (GtkVideo *self)
|
||||
{
|
||||
gtk_video_update_overlay_icon (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_update_error (GtkVideo *self)
|
||||
{
|
||||
gtk_video_update_overlay_icon (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_update_playing (GtkVideo *self)
|
||||
{
|
||||
gboolean playing;
|
||||
|
||||
if (self->media_stream != NULL)
|
||||
playing = gtk_media_stream_get_playing (self->media_stream);
|
||||
else
|
||||
playing = FALSE;
|
||||
|
||||
gtk_widget_set_visible (self->overlay_icon, !playing);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_update_all (GtkVideo *self)
|
||||
{
|
||||
gtk_video_update_ended (self);
|
||||
gtk_video_update_error (self);
|
||||
gtk_video_update_playing (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_notify_cb (GtkMediaStream *stream,
|
||||
GParamSpec *pspec,
|
||||
GtkVideo *self)
|
||||
{
|
||||
if (g_str_equal (pspec->name, "ended"))
|
||||
gtk_video_update_ended (self);
|
||||
if (g_str_equal (pspec->name, "error"))
|
||||
gtk_video_update_error (self);
|
||||
if (g_str_equal (pspec->name, "playing"))
|
||||
gtk_video_update_playing (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_set_media_stream:
|
||||
* @self: a #GtkVideo
|
||||
* @stream: (allow-none): The media stream to play or %NULL to unset
|
||||
*
|
||||
* Sets the media stream to be played back. @self will take full control
|
||||
* of managing the media stream. If you want to manage a media stream
|
||||
* yourself, consider using a #GtkImage for display.
|
||||
*
|
||||
* If you want to display a file, consider using gtk_video_set_file()
|
||||
* instead.
|
||||
**/
|
||||
void
|
||||
gtk_video_set_media_stream (GtkVideo *self,
|
||||
GtkMediaStream *stream)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_VIDEO (self));
|
||||
g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
|
||||
|
||||
if (self->media_stream == stream)
|
||||
return;
|
||||
|
||||
if (self->media_stream)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (self->media_stream,
|
||||
gtk_video_notify_cb,
|
||||
self);
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (self)))
|
||||
gtk_media_stream_unrealize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
|
||||
g_object_unref (self->media_stream);
|
||||
self->media_stream = NULL;
|
||||
}
|
||||
|
||||
if (stream)
|
||||
{
|
||||
self->media_stream = g_object_ref (stream);
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (self)))
|
||||
gtk_media_stream_realize (stream, gtk_widget_get_window (GTK_WIDGET (self)));
|
||||
g_signal_connect (self->media_stream,
|
||||
"notify",
|
||||
G_CALLBACK (gtk_video_notify_cb),
|
||||
self);
|
||||
}
|
||||
|
||||
gtk_media_controls_set_media_stream (GTK_MEDIA_CONTROLS (self->controls), stream);
|
||||
gtk_image_set_from_paintable (GTK_IMAGE (self->video_image), GDK_PAINTABLE (stream));
|
||||
|
||||
gtk_video_update_all (self);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MEDIA_STREAM]);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_set_file:
|
||||
* @self: a #GtkVideo
|
||||
* @file: (allow-none): the file to play
|
||||
*
|
||||
* Makes @self play the given @file.
|
||||
**/
|
||||
void
|
||||
gtk_video_set_file (GtkVideo *self,
|
||||
GFile *file)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_VIDEO (self));
|
||||
g_return_if_fail (file == NULL || G_IS_FILE (file));
|
||||
|
||||
if (!g_set_object (&self->file, file))
|
||||
return;
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
if (file)
|
||||
{
|
||||
GtkMediaStream *stream;
|
||||
|
||||
stream = gtk_media_file_new ();
|
||||
|
||||
gtk_video_set_media_stream (self, stream);
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (self)))
|
||||
gtk_media_file_set_file (GTK_MEDIA_FILE (stream), file);
|
||||
|
||||
g_object_unref (stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_video_set_media_stream (self, NULL);
|
||||
}
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_set_resource:
|
||||
* @self: a #GtkVideo
|
||||
* @filename: (allow-none): the filename to play
|
||||
*
|
||||
* Makes @self play the given @filename.
|
||||
*
|
||||
* This is a utility function that calls gtk_video_set_file(),
|
||||
**/
|
||||
void
|
||||
gtk_video_set_filename (GtkVideo *self,
|
||||
const char *filename)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
g_return_if_fail (GTK_IS_VIDEO (self));
|
||||
|
||||
if (filename)
|
||||
file = g_file_new_for_path (filename);
|
||||
else
|
||||
file = NULL;
|
||||
|
||||
gtk_video_set_file (self, file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_video_set_resource:
|
||||
* @self: a #GtkVideo
|
||||
* @resource_path: (allow-none): the resource to set
|
||||
*
|
||||
* Makes @self play the resource at the given @resource_path.
|
||||
*
|
||||
* This is a utility function that calls gtk_video_set_file(),
|
||||
**/
|
||||
void
|
||||
gtk_video_set_resource (GtkVideo *self,
|
||||
const char *resource_path)
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
g_return_if_fail (GTK_IS_VIDEO (self));
|
||||
|
||||
if (resource_path)
|
||||
{
|
||||
char *uri, *escaped;
|
||||
|
||||
escaped = g_uri_escape_string (resource_path,
|
||||
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
|
||||
uri = g_strconcat ("resource://", escaped, NULL);
|
||||
g_free (escaped);
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
g_free (uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
gtk_video_set_file (self, file);
|
||||
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
64
gtk/gtkvideo.h
Normal file
64
gtk/gtkvideo.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_VIDEO_H__
|
||||
#define __GTK_VIDEO_H__
|
||||
|
||||
#include <gtk/gtkmediastream.h>
|
||||
#include <gtk/gtkwidget.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_VIDEO (gtk_video_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_FINAL_TYPE (GtkVideo, gtk_video, GTK, VIDEO, GtkWidget)
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget * gtk_video_new (void);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget * gtk_video_new_for_media_stream (GtkMediaStream *stream);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget * gtk_video_new_for_file (GFile *file);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget * gtk_video_new_for_filename (const char *filename);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkWidget * gtk_video_new_for_resource (const char *resource_path);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMediaStream *gtk_video_get_media_stream (GtkVideo *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_video_set_media_stream (GtkVideo *self,
|
||||
GtkMediaStream *stream);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GFile * gtk_video_get_file (GtkVideo *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_video_set_file (GtkVideo *self,
|
||||
GFile *file);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_video_set_filename (GtkVideo *self,
|
||||
const char *filename);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_video_set_resource (GtkVideo *self,
|
||||
const char *resource_path);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_VIDEO_H__ */
|
@@ -73,6 +73,7 @@
|
||||
#include "inspector/init.h"
|
||||
#include "inspector/window.h"
|
||||
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
#include "gdk/gdk-private.h"
|
||||
|
||||
#include <cairo-gobject.h>
|
||||
@@ -4600,10 +4601,7 @@ icon_from_list (GList *list,
|
||||
cairo_destroy (cr);
|
||||
cairo_surface_destroy (source);
|
||||
|
||||
texture = gdk_texture_new_for_data (cairo_image_surface_get_data (target),
|
||||
cairo_image_surface_get_width (target),
|
||||
cairo_image_surface_get_height (target),
|
||||
cairo_image_surface_get_stride (target));
|
||||
texture = gdk_texture_new_for_surface (target);
|
||||
cairo_surface_destroy (target);
|
||||
|
||||
return texture;
|
||||
|
@@ -48,6 +48,7 @@ gtk_private_sources = files([
|
||||
'gtkcssimageicontheme.c',
|
||||
'gtkcssimageinvalid.c',
|
||||
'gtkcssimagelinear.c',
|
||||
'gtkcssimagepaint.c',
|
||||
'gtkcssimagepaintable.c',
|
||||
'gtkcssimageradial.c',
|
||||
'gtkcssimagerecolor.c',
|
||||
@@ -251,6 +252,9 @@ gtk_public_sources = files([
|
||||
'gtkliststore.c',
|
||||
'gtklockbutton.c',
|
||||
'gtkmain.c',
|
||||
'gtkmediacontrols.c',
|
||||
'gtkmediafile.c',
|
||||
'gtkmediastream.c',
|
||||
'gtkmenu.c',
|
||||
'gtkmenubar.c',
|
||||
'gtkmenubutton.c',
|
||||
@@ -263,6 +267,7 @@ gtk_public_sources = files([
|
||||
'gtkmodules.c',
|
||||
'gtkmountoperation.c',
|
||||
'gtknativedialog.c',
|
||||
'gtknomediafile.c',
|
||||
'gtknotebook.c',
|
||||
'gtkorientable.c',
|
||||
'gtkoverlay.c',
|
||||
@@ -354,6 +359,7 @@ gtk_public_sources = files([
|
||||
'gtktreeview.c',
|
||||
'gtktreeviewcolumn.c',
|
||||
'gtkutils.c',
|
||||
'gtkvideo.c',
|
||||
'gtkviewport.c',
|
||||
'gtkvolumebutton.c',
|
||||
'gtkwidget.c',
|
||||
@@ -480,6 +486,9 @@ gtk_public_headers = files([
|
||||
'gtkliststore.h',
|
||||
'gtklockbutton.h',
|
||||
'gtkmain.h',
|
||||
'gtkmediacontrols.h',
|
||||
'gtkmediafile.h',
|
||||
'gtkmediastream.h',
|
||||
'gtkmenu.h',
|
||||
'gtkmenubar.h',
|
||||
'gtkmenubutton.h',
|
||||
@@ -566,6 +575,7 @@ gtk_public_headers = files([
|
||||
'gtktreeview.h',
|
||||
'gtktreeviewcolumn.h',
|
||||
'gtktypes.h',
|
||||
'gtkvideo.h',
|
||||
'gtkviewport.h',
|
||||
'gtkvolumebutton.h',
|
||||
'gtkwidget.h',
|
||||
|
@@ -3998,6 +3998,19 @@ paned {
|
||||
}
|
||||
|
||||
|
||||
/**************
|
||||
* GtkVideo *
|
||||
**************/
|
||||
|
||||
video {
|
||||
& image.osd {
|
||||
min-width: 64px;
|
||||
min-height: 64px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
background: black;
|
||||
}
|
||||
|
||||
/**************
|
||||
* GtkInfoBar *
|
||||
**************/
|
||||
|
64
gtk/ui/gtkmediacontrols.ui
Normal file
64
gtk/ui/gtkmediacontrols.ui
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface domain="gtk40">
|
||||
<!-- interface-requires gtk+ 3.6 -->
|
||||
<object class="GtkAdjustment" id="time_adjustment">
|
||||
<property name="upper">10</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
<signal name="value-changed" handler="time_adjustment_changed" object="GtkMediaControls" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="volume_adjustment">
|
||||
<property name="upper">1</property>
|
||||
<property name="step-increment">0.1</property>
|
||||
<property name="page-increment">1</property>
|
||||
<property name="value">1</property>
|
||||
<signal name="value-changed" handler="volume_adjustment_changed" object="GtkMediaControls" swapped="no"/>
|
||||
</object>
|
||||
<template class="GtkMediaControls" parent="GtkWidget">
|
||||
<child>
|
||||
<object class="GtkBox" id="box">
|
||||
<property name="hexpand">0</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="play_button">
|
||||
<property name="can-focus">1</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
<signal name="clicked" handler="play_button_clicked" object="GtkMediaControls" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="time_box">
|
||||
<child>
|
||||
<object class="GtkLabel" id="time_label">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="seek_scale">
|
||||
<property name="adjustment">time_adjustment</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="draw_value">False</property>
|
||||
<property name="restrict-to-fill-level">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="duration_label">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVolumeButton" id="volume_button">
|
||||
<property name="adjustment">volume_adjustment</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
47
gtk/ui/gtkvideo.ui
Normal file
47
gtk/ui/gtkvideo.ui
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface domain="gtk40">
|
||||
<!-- interface-requires gtk+ 3.6 -->
|
||||
<template class="GtkVideo" parent="GtkWidget">
|
||||
<child>
|
||||
<object class="GtkOverlay" id="box">
|
||||
<child>
|
||||
<object class="GtkImage" id="video_image">
|
||||
<property name="can-shrink">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkImage" id="overlay_icon">
|
||||
<style>
|
||||
<class name="osd"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
<property name="icon-size">large</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="measure">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkRevealer" id="controls_revealer">
|
||||
<property name="reveal-child">0</property>
|
||||
<property name="valign">end</property>
|
||||
<child>
|
||||
<object class="GtkMediaControls" id="controls">
|
||||
<style>
|
||||
<class name="osd"/>
|
||||
<class name="bottom"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="measure">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
37
meson.build
37
meson.build
@@ -589,6 +589,42 @@ if cloudproviders_enabled
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
media_backends = []
|
||||
|
||||
# We require manually disabling support for media backends.
|
||||
# People should only do that when they know exactly what they're doing
|
||||
# (either not using media or providing their own implementation.)
|
||||
if get_option('nomedia')
|
||||
media_backends += ['none']
|
||||
endif
|
||||
|
||||
ffmpeg_enabled = get_option('ffmpeg')
|
||||
if ffmpeg_enabled
|
||||
libavfilter_dep = dependency('libavfilter', version: '>= 6.47.100', required: true)
|
||||
libavformat_dep = dependency('libavformat', version: '>= 57.41.100', required: true)
|
||||
libavcodec_dep = dependency('libavcodec', version: '>= 57.48.101', required: true)
|
||||
libavutil_dep = dependency('libavutil', version: '>= 55.28.100', required: true)
|
||||
libswscale_dep = dependency('libswscale', version: '>= 4.6.100', required: true)
|
||||
ffmpeg_deps = [libavfilter_dep, libavformat_dep, libavcodec_dep, libavutil_dep, libswscale_dep]
|
||||
cdata.set('HAVE_FFMPEG', 1)
|
||||
media_backends += ['ffmpeg']
|
||||
endif
|
||||
|
||||
gstreamer_enabled = get_option('gstreamer')
|
||||
if gstreamer_enabled
|
||||
gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.12.3', required: true)
|
||||
gstgl_dep = dependency('gstreamer-gl-1.0', version: '>= 1.12.3', required: true)
|
||||
gstreamer_deps = [gstplayer_dep, gstgl_dep]
|
||||
cdata.set('HAVE_GSTREAMER', 1)
|
||||
media_backends += ['gstreamer']
|
||||
endif
|
||||
|
||||
if media_backends.length() == 0
|
||||
error('No media backends enabled. If you are sure about that, enable the "nomedia" option.')
|
||||
endif
|
||||
|
||||
|
||||
subdir('gdk')
|
||||
subdir('gsk')
|
||||
subdir('gtk')
|
||||
@@ -705,6 +741,7 @@ summary = [
|
||||
' Enabled backends: @0@'.format(pkg_targets.strip()),
|
||||
' Vulkan support: @0@'.format(have_vulkan),
|
||||
' Print backends: @0@'.format(' '.join(print_backends)),
|
||||
' Media backends: @0@'.format(' '.join(media_backends)),
|
||||
' Tests: @0@'.format(get_option('build-tests')),
|
||||
' Documentation: @0@'.format(get_option('documentation')),
|
||||
' Demos: @0@'.format(get_option('demos')),
|
||||
|
@@ -10,6 +10,14 @@ option('win32-backend', type: 'boolean', value: true,
|
||||
option('quartz-backend', type: 'boolean', value: true,
|
||||
description : 'Enable the macOS gdk backend (only when building on macOS)')
|
||||
|
||||
# Media backends
|
||||
option('nomedia', type: 'boolean', value: false,
|
||||
description : 'Set to confirm that no media backend should be enabled')
|
||||
option('ffmpeg', type: 'boolean', value: false,
|
||||
description : 'Enable ffmpeg media backend')
|
||||
option('gstreamer', type: 'boolean', value: true,
|
||||
description : 'Enable GStreamer media backend')
|
||||
|
||||
# Optional dependencies
|
||||
option('vulkan', type: 'combo', choices : ['yes', 'no', 'auto'], value : 'auto',
|
||||
description : 'Enable support for the Vulkan graphics API')
|
||||
|
756
modules/media/gtkffmediafile.c
Normal file
756
modules/media/gtkffmediafile.c
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkffmediafileprivate.h"
|
||||
|
||||
#include "gtkintl.h"
|
||||
|
||||
#include "gdk/gdkmemorytextureprivate.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
typedef struct _GtkVideoFrameFFMpeg GtkVideoFrameFFMpeg;
|
||||
|
||||
struct _GtkVideoFrameFFMpeg
|
||||
{
|
||||
GdkTexture *texture;
|
||||
gint64 timestamp;
|
||||
};
|
||||
|
||||
struct _GtkFfMediaFile
|
||||
{
|
||||
GtkMediaFile parent_instance;
|
||||
|
||||
GFile *file;
|
||||
GInputStream *input_stream;
|
||||
|
||||
AVFormatContext *format_ctx;
|
||||
AVCodecContext *codec_ctx;
|
||||
int stream_id;
|
||||
struct SwsContext *sws_ctx;
|
||||
enum AVPixelFormat sws_pix_fmt;
|
||||
GdkMemoryFormat memory_format;
|
||||
|
||||
GtkVideoFrameFFMpeg current_frame;
|
||||
GtkVideoFrameFFMpeg next_frame;
|
||||
|
||||
gint64 start_time; /* monotonic time when we displayed the last frame */
|
||||
guint next_frame_cb; /* Source ID of next frame callback */
|
||||
};
|
||||
|
||||
struct _GtkFfMediaFileClass
|
||||
{
|
||||
GtkMediaFileClass parent_class;
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_video_frame_ffmpeg_init (GtkVideoFrameFFMpeg *frame,
|
||||
GdkTexture *texture,
|
||||
gint64 timestamp)
|
||||
{
|
||||
frame->texture = texture;
|
||||
frame->timestamp = timestamp;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_frame_ffmpeg_clear (GtkVideoFrameFFMpeg *frame)
|
||||
{
|
||||
g_clear_object (&frame->texture);
|
||||
frame->timestamp = 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_video_frame_ffmpeg_is_empty (GtkVideoFrameFFMpeg *frame)
|
||||
{
|
||||
return frame->texture == NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_video_frame_ffmpeg_move (GtkVideoFrameFFMpeg *dest,
|
||||
GtkVideoFrameFFMpeg *src)
|
||||
{
|
||||
*dest = *src;
|
||||
src->texture = NULL;
|
||||
src->timestamp = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
|
||||
|
||||
if (!gtk_video_frame_ffmpeg_is_empty (&video->current_frame))
|
||||
{
|
||||
gdk_paintable_snapshot (GDK_PAINTABLE (video->current_frame.texture), snapshot, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_ff_media_file_paintable_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
|
||||
|
||||
return GDK_PAINTABLE (g_object_ref (video->current_frame.texture));
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_ff_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
|
||||
|
||||
if (video->codec_ctx)
|
||||
return video->codec_ctx->width;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_ff_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
|
||||
|
||||
if (video->codec_ctx)
|
||||
return video->codec_ctx->height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
|
||||
|
||||
if (video->codec_ctx)
|
||||
return (double) video->codec_ctx->width / video->codec_ctx->height;
|
||||
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_ff_media_file_paintable_snapshot;
|
||||
iface->get_current_image = gtk_ff_media_file_paintable_get_current_image;
|
||||
iface->get_intrinsic_width = gtk_ff_media_file_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_ff_media_file_paintable_get_intrinsic_height;
|
||||
iface->get_intrinsic_aspect_ratio = gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (GtkFfMediaFile, gtk_ff_media_file, GTK_TYPE_MEDIA_FILE, 0,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_ff_media_file_paintable_init))
|
||||
|
||||
void
|
||||
g_io_module_load (GIOModule *module)
|
||||
{
|
||||
g_type_module_use (G_TYPE_MODULE (module));
|
||||
|
||||
av_register_all ();
|
||||
|
||||
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
|
||||
GTK_TYPE_FF_MEDIA_FILE,
|
||||
"ffmpeg",
|
||||
0);
|
||||
}
|
||||
|
||||
void
|
||||
g_io_module_unload (GIOModule *module)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
char **
|
||||
g_io_module_query (void)
|
||||
{
|
||||
char *eps[] = {
|
||||
GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
|
||||
NULL
|
||||
};
|
||||
|
||||
return g_strdupv (eps);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_set_ffmpeg_error (GtkFfMediaFile *video,
|
||||
int av_errnum)
|
||||
{
|
||||
char s[AV_ERROR_MAX_STRING_SIZE];
|
||||
|
||||
if (gtk_media_stream_get_error (GTK_MEDIA_STREAM (video)))
|
||||
return;
|
||||
|
||||
if (av_strerror (av_errnum, s, sizeof (s) != 0))
|
||||
snprintf (s, sizeof (s), _("Unspecified error decoding video"));
|
||||
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"%s",
|
||||
s);
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_ff_media_file_read_packet_cb (void *data,
|
||||
uint8_t *buf,
|
||||
int buf_size)
|
||||
{
|
||||
GtkFfMediaFile *video = data;
|
||||
GError *error = NULL;
|
||||
gssize n_read;
|
||||
|
||||
n_read = g_input_stream_read (video->input_stream,
|
||||
buf,
|
||||
buf_size,
|
||||
NULL,
|
||||
&error);
|
||||
if (n_read < 0)
|
||||
{
|
||||
gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
|
||||
}
|
||||
else if (n_read == 0)
|
||||
{
|
||||
n_read = AVERROR_EOF;
|
||||
}
|
||||
|
||||
return n_read;
|
||||
}
|
||||
|
||||
static GdkMemoryFormat
|
||||
memory_format_from_pix_fmt (enum AVPixelFormat pix_fmt)
|
||||
{
|
||||
switch ((int) pix_fmt)
|
||||
{
|
||||
case AV_PIX_FMT_RGBA:
|
||||
return GDK_MEMORY_R8G8B8A8;
|
||||
case AV_PIX_FMT_RGB24:
|
||||
return GDK_MEMORY_R8G8B8;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return GDK_MEMORY_R8G8B8A8;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_decode_frame (GtkFfMediaFile *video,
|
||||
GtkVideoFrameFFMpeg *result)
|
||||
{
|
||||
GdkTexture *texture;
|
||||
AVPacket packet;
|
||||
AVFrame *frame;
|
||||
int errnum;
|
||||
GBytes *bytes;
|
||||
guchar *data;
|
||||
|
||||
frame = av_frame_alloc ();
|
||||
|
||||
for (errnum = av_read_frame (video->format_ctx, &packet);
|
||||
errnum >= 0;
|
||||
errnum = av_read_frame (video->format_ctx, &packet))
|
||||
{
|
||||
if (packet.stream_index == video->stream_id)
|
||||
{
|
||||
errnum = avcodec_send_packet (video->codec_ctx, &packet);
|
||||
if (errnum < 0)
|
||||
G_BREAKPOINT();
|
||||
if (errnum >= 0)
|
||||
{
|
||||
errnum = avcodec_receive_frame (video->codec_ctx, frame);
|
||||
if (errnum < 0)
|
||||
G_BREAKPOINT();
|
||||
if (errnum >= 0)
|
||||
{
|
||||
av_packet_unref (&packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_unref (&packet);
|
||||
}
|
||||
|
||||
if (errnum < 0)
|
||||
{
|
||||
if (errnum != AVERROR_EOF)
|
||||
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
|
||||
av_frame_free (&frame);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
data = g_try_malloc0 (video->codec_ctx->width * video->codec_ctx->height * 4);
|
||||
if (data == NULL)
|
||||
{
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("Not enough memory"));
|
||||
av_frame_free (&frame);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (video->sws_ctx == NULL ||
|
||||
video->sws_pix_fmt != frame->format)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc;
|
||||
enum AVPixelFormat gdk_pix_fmt;
|
||||
|
||||
g_clear_pointer (&video->sws_ctx, sws_freeContext);
|
||||
video->sws_pix_fmt = frame->format;
|
||||
desc = av_pix_fmt_desc_get (video->sws_pix_fmt);
|
||||
/* Use gdk-pixbuf formats because ffmpeg can't premultiply */
|
||||
if (desc != NULL && (desc->flags & AV_PIX_FMT_FLAG_ALPHA))
|
||||
gdk_pix_fmt = AV_PIX_FMT_RGBA;
|
||||
else
|
||||
gdk_pix_fmt = AV_PIX_FMT_RGB24;
|
||||
|
||||
video->sws_ctx = sws_getContext (video->codec_ctx->width,
|
||||
video->codec_ctx->height,
|
||||
frame->format,
|
||||
video->codec_ctx->width,
|
||||
video->codec_ctx->height,
|
||||
gdk_pix_fmt,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
video->memory_format = memory_format_from_pix_fmt (gdk_pix_fmt);
|
||||
}
|
||||
|
||||
sws_scale(video->sws_ctx,
|
||||
(const uint8_t * const *) frame->data, frame->linesize,
|
||||
0, video->codec_ctx->height,
|
||||
(uint8_t *[1]) { data }, (int[1]) { video->codec_ctx->width * 4 });
|
||||
|
||||
bytes = g_bytes_new_take (data, video->codec_ctx->width * video->codec_ctx->height * 4);
|
||||
texture = gdk_memory_texture_new (video->codec_ctx->width,
|
||||
video->codec_ctx->height,
|
||||
video->memory_format,
|
||||
bytes,
|
||||
video->codec_ctx->width * 4);
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
gtk_video_frame_ffmpeg_init (result,
|
||||
texture,
|
||||
av_rescale_q (av_frame_get_best_effort_timestamp (frame),
|
||||
video->format_ctx->streams[video->stream_id]->time_base,
|
||||
(AVRational) { 1, G_USEC_PER_SEC }));
|
||||
|
||||
av_frame_free (&frame);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int64_t
|
||||
gtk_ff_media_file_seek_cb (void *data,
|
||||
int64_t offset,
|
||||
int whence)
|
||||
{
|
||||
GtkFfMediaFile *video = data;
|
||||
GSeekType seek_type;
|
||||
gboolean result;
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
seek_type = G_SEEK_SET;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
seek_type = G_SEEK_CUR;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
seek_type = G_SEEK_END;
|
||||
break;
|
||||
|
||||
case AVSEEK_SIZE:
|
||||
/* FIXME: Handle size querying */
|
||||
return -1;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = g_seekable_seek (G_SEEKABLE (video->input_stream),
|
||||
offset,
|
||||
seek_type,
|
||||
NULL,
|
||||
NULL);
|
||||
if (!result)
|
||||
return -1;
|
||||
|
||||
return g_seekable_tell (G_SEEKABLE (video->input_stream));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_create_input_stream (GtkFfMediaFile *video)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GFile *file;
|
||||
|
||||
file = gtk_media_file_get_file (GTK_MEDIA_FILE (video));
|
||||
if (file)
|
||||
{
|
||||
video->input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
|
||||
if (video->input_stream == NULL)
|
||||
{
|
||||
gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
|
||||
g_error_free (error);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
video->input_stream = g_object_ref (gtk_media_file_get_input_stream (GTK_MEDIA_FILE (video)));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static AVIOContext *
|
||||
gtk_ff_media_file_create_io_context (GtkFfMediaFile *video)
|
||||
{
|
||||
AVIOContext *result;
|
||||
int buffer_size = 4096; /* it's what everybody else uses... */
|
||||
unsigned char *buffer;
|
||||
|
||||
if (!gtk_ff_media_file_create_input_stream (video))
|
||||
return NULL;
|
||||
|
||||
buffer = av_malloc (buffer_size);
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
|
||||
result = avio_alloc_context (buffer,
|
||||
buffer_size,
|
||||
AVIO_FLAG_READ,
|
||||
video,
|
||||
gtk_ff_media_file_read_packet_cb,
|
||||
NULL,
|
||||
G_IS_SEEKABLE (video->input_stream)
|
||||
? gtk_ff_media_file_seek_cb
|
||||
: NULL);
|
||||
|
||||
result->buf_ptr = result->buf_end;
|
||||
result->write_flag = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean gtk_ff_media_file_play (GtkMediaStream *stream);
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_open (GtkMediaFile *file)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
|
||||
AVStream *stream;
|
||||
AVCodec *codec;
|
||||
int errnum;
|
||||
|
||||
video->format_ctx = avformat_alloc_context ();
|
||||
video->format_ctx->pb = gtk_ff_media_file_create_io_context (video);
|
||||
if (video->format_ctx->pb == NULL)
|
||||
{
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("Not enough memory"));
|
||||
return;
|
||||
}
|
||||
errnum = avformat_open_input (&video->format_ctx, NULL, NULL, NULL);
|
||||
if (errnum != 0)
|
||||
{
|
||||
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
|
||||
return;
|
||||
}
|
||||
|
||||
errnum = avformat_find_stream_info (video->format_ctx, NULL);
|
||||
if (errnum < 0)
|
||||
{
|
||||
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
|
||||
return;
|
||||
}
|
||||
|
||||
video->stream_id = av_find_best_stream (video->format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
if (video->stream_id < 0)
|
||||
{
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
_("Not a video file"));
|
||||
return;
|
||||
}
|
||||
|
||||
stream = video->format_ctx->streams[video->stream_id];
|
||||
/* alpha transparency requires the libvpx codecs, not the ffmpeg builtin ones */
|
||||
if (stream->codecpar->codec_id == AV_CODEC_ID_VP8)
|
||||
codec = avcodec_find_decoder_by_name ("libvpx");
|
||||
else if (stream->codecpar->codec_id == AV_CODEC_ID_VP9)
|
||||
codec = avcodec_find_decoder_by_name ("libvpx-vp9");
|
||||
else
|
||||
codec = NULL;
|
||||
if (codec == NULL)
|
||||
codec = avcodec_find_decoder (stream->codecpar->codec_id);
|
||||
if (codec == NULL)
|
||||
{
|
||||
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
_("Unsupported video codec"));
|
||||
return;
|
||||
}
|
||||
|
||||
video->codec_ctx = avcodec_alloc_context3 (codec);
|
||||
errnum = avcodec_parameters_to_context (video->codec_ctx, stream->codecpar);
|
||||
if (errnum < 0)
|
||||
{
|
||||
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
|
||||
return;
|
||||
}
|
||||
errnum = avcodec_open2 (video->codec_ctx, codec, &stream->metadata);
|
||||
if (errnum < 0)
|
||||
{
|
||||
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_media_stream_prepared (GTK_MEDIA_STREAM (video),
|
||||
FALSE,
|
||||
video->codec_ctx != NULL,
|
||||
TRUE,
|
||||
video->format_ctx->duration != AV_NOPTS_VALUE
|
||||
? av_rescale (video->format_ctx->duration, G_USEC_PER_SEC, AV_TIME_BASE)
|
||||
: 0);
|
||||
|
||||
gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
|
||||
|
||||
if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
|
||||
|
||||
if (gtk_media_stream_get_playing (GTK_MEDIA_STREAM (video)))
|
||||
gtk_ff_media_file_play (GTK_MEDIA_STREAM (video));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_close (GtkMediaFile *file)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
|
||||
|
||||
g_clear_object (&video->input_stream);
|
||||
|
||||
g_clear_pointer (&video->sws_ctx, sws_freeContext);
|
||||
g_clear_pointer (&video->codec_ctx, avcodec_close);
|
||||
avformat_close_input (&video->format_ctx);
|
||||
video->stream_id = -1;
|
||||
gtk_video_frame_ffmpeg_clear (&video->next_frame);
|
||||
gtk_video_frame_ffmpeg_clear (&video->current_frame);
|
||||
|
||||
gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_next_frame_cb (gpointer data);
|
||||
static void
|
||||
gtk_ff_media_file_queue_frame (GtkFfMediaFile *video)
|
||||
{
|
||||
gint64 time, frame_time;
|
||||
guint delay;
|
||||
|
||||
time = g_get_monotonic_time ();
|
||||
frame_time = video->start_time + video->next_frame.timestamp;
|
||||
delay = time > frame_time ? 0 : (frame_time - time) / 1000;
|
||||
|
||||
video->next_frame_cb = g_timeout_add (delay, gtk_ff_media_file_next_frame_cb, video);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_restart (GtkFfMediaFile *video)
|
||||
{
|
||||
if (av_seek_frame (video->format_ctx,
|
||||
video->stream_id,
|
||||
av_rescale_q (0,
|
||||
(AVRational) { 1, G_USEC_PER_SEC },
|
||||
video->format_ctx->streams[video->stream_id]->time_base),
|
||||
AVSEEK_FLAG_BACKWARD) < 0)
|
||||
return FALSE;
|
||||
|
||||
if (!gtk_ff_media_file_decode_frame (video, &video->next_frame))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_next_frame_cb (gpointer data)
|
||||
{
|
||||
GtkFfMediaFile *video = data;
|
||||
|
||||
video->next_frame_cb = 0;
|
||||
|
||||
if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame))
|
||||
{
|
||||
if (!gtk_media_stream_get_loop (GTK_MEDIA_STREAM (video)) ||
|
||||
!gtk_ff_media_file_restart (video))
|
||||
{
|
||||
gtk_media_stream_ended (GTK_MEDIA_STREAM (video));
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
video->start_time += video->current_frame.timestamp - video->next_frame.timestamp;
|
||||
}
|
||||
|
||||
gtk_video_frame_ffmpeg_clear (&video->current_frame);
|
||||
gtk_video_frame_ffmpeg_move (&video->current_frame,
|
||||
&video->next_frame);
|
||||
|
||||
gtk_media_stream_update (GTK_MEDIA_STREAM (video),
|
||||
video->current_frame.timestamp);
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
|
||||
|
||||
/* ignore failure here, we'll handle the empty frame case above
|
||||
* the next time we're called. */
|
||||
gtk_ff_media_file_decode_frame (video, &video->next_frame);
|
||||
gtk_ff_media_file_queue_frame (video);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_ff_media_file_play (GtkMediaStream *stream)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
|
||||
|
||||
if (!gtk_media_stream_is_prepared (stream))
|
||||
return TRUE;
|
||||
|
||||
if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame) &&
|
||||
!gtk_ff_media_file_decode_frame (video, &video->next_frame))
|
||||
{
|
||||
if (gtk_ff_media_file_restart (video))
|
||||
{
|
||||
video->start_time = g_get_monotonic_time () - video->next_frame.timestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
video->start_time = g_get_monotonic_time () - video->current_frame.timestamp;
|
||||
}
|
||||
|
||||
gtk_ff_media_file_queue_frame (video);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_pause (GtkMediaStream *stream)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
|
||||
|
||||
if (video->next_frame_cb)
|
||||
{
|
||||
g_source_remove (video->next_frame_cb);
|
||||
video->next_frame_cb = 0;
|
||||
}
|
||||
|
||||
video->start_time = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_seek (GtkMediaStream *stream,
|
||||
gint64 timestamp)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
|
||||
int errnum;
|
||||
|
||||
errnum = av_seek_frame (video->format_ctx,
|
||||
video->stream_id,
|
||||
av_rescale_q (timestamp,
|
||||
(AVRational) { 1, G_USEC_PER_SEC },
|
||||
video->format_ctx->streams[video->stream_id]->time_base),
|
||||
AVSEEK_FLAG_BACKWARD);
|
||||
if (errnum < 0)
|
||||
{
|
||||
gtk_media_stream_seek_failed (stream);
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_media_stream_seek_success (stream);
|
||||
|
||||
gtk_video_frame_ffmpeg_clear (&video->next_frame);
|
||||
gtk_video_frame_ffmpeg_clear (&video->current_frame);
|
||||
if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
|
||||
gtk_media_stream_update (stream, video->current_frame.timestamp);
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
|
||||
|
||||
if (gtk_media_stream_get_playing (stream))
|
||||
{
|
||||
gtk_ff_media_file_pause (stream);
|
||||
if (!gtk_ff_media_file_play (stream))
|
||||
gtk_media_stream_ended (stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_dispose (GObject *object)
|
||||
{
|
||||
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (object);
|
||||
|
||||
gtk_ff_media_file_pause (GTK_MEDIA_STREAM (video));
|
||||
gtk_ff_media_file_close (GTK_MEDIA_FILE (video));
|
||||
|
||||
G_OBJECT_CLASS (gtk_ff_media_file_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_class_init (GtkFfMediaFileClass *klass)
|
||||
{
|
||||
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
|
||||
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
file_class->open = gtk_ff_media_file_open;
|
||||
file_class->close = gtk_ff_media_file_close;
|
||||
stream_class->play = gtk_ff_media_file_play;
|
||||
stream_class->pause = gtk_ff_media_file_pause;
|
||||
stream_class->seek = gtk_ff_media_file_seek;
|
||||
|
||||
gobject_class->dispose = gtk_ff_media_file_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_ff_media_file_init (GtkFfMediaFile *video)
|
||||
{
|
||||
video->stream_id = -1;
|
||||
}
|
||||
|
||||
|
33
modules/media/gtkffmediafileprivate.h
Normal file
33
modules/media/gtkffmediafileprivate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_FF_MEDIA_FILE_H__
|
||||
#define __GTK_FF_MEDIA_FILE_H__
|
||||
|
||||
#include <gtk/gtkmediafile.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_FF_MEDIA_FILE (gtk_ff_media_file_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkFfMediaFile, gtk_ff_media_file, GTK, FF_MEDIA_FILE, GtkMediaFile)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_FF_MEDIA_FILE_H__ */
|
342
modules/media/gtkgstmediafile.c
Normal file
342
modules/media/gtkgstmediafile.c
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkgstmediafileprivate.h"
|
||||
#include "gtkgstpaintableprivate.h"
|
||||
|
||||
#include <gst/player/gstplayer.h>
|
||||
#include <gst/player/gstplayer-g-main-context-signal-dispatcher.h>
|
||||
|
||||
struct _GtkGstMediaFile
|
||||
{
|
||||
GtkMediaFile parent_instance;
|
||||
|
||||
GstPlayer *player;
|
||||
GdkPaintable *paintable;
|
||||
};
|
||||
|
||||
struct _GtkGstMediaFileClass
|
||||
{
|
||||
GtkMediaFileClass parent_class;
|
||||
};
|
||||
|
||||
#define TO_GST_TIME(ts) ((ts) * (GST_SECOND / G_USEC_PER_SEC))
|
||||
#define FROM_GST_TIME(ts) ((ts) / (GST_SECOND / G_USEC_PER_SEC))
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
|
||||
|
||||
gdk_paintable_snapshot (self->paintable, snapshot, width, height);
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_gst_media_file_paintable_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
|
||||
|
||||
return gdk_paintable_get_current_image (self->paintable);
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_gst_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
|
||||
|
||||
return gdk_paintable_get_intrinsic_width (self->paintable);
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_gst_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
|
||||
|
||||
return gdk_paintable_get_intrinsic_height (self->paintable);
|
||||
}
|
||||
|
||||
static double gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
|
||||
|
||||
return gdk_paintable_get_intrinsic_aspect_ratio (self->paintable);
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_gst_media_file_paintable_snapshot;
|
||||
iface->get_current_image = gtk_gst_media_file_paintable_get_current_image;
|
||||
iface->get_intrinsic_width = gtk_gst_media_file_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_gst_media_file_paintable_get_intrinsic_height;
|
||||
iface->get_intrinsic_aspect_ratio = gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (GtkGstMediaFile, gtk_gst_media_file, GTK_TYPE_MEDIA_FILE, 0,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_gst_media_file_paintable_init))
|
||||
void
|
||||
g_io_module_load (GIOModule *module)
|
||||
{
|
||||
g_type_module_use (G_TYPE_MODULE (module));
|
||||
|
||||
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
|
||||
GTK_TYPE_GST_MEDIA_FILE,
|
||||
"gstreamer",
|
||||
10);
|
||||
}
|
||||
|
||||
void
|
||||
g_io_module_unload (GIOModule *module)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
char **
|
||||
g_io_module_query (void)
|
||||
{
|
||||
char *eps[] = {
|
||||
GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
|
||||
NULL
|
||||
};
|
||||
|
||||
return g_strdupv (eps);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_position_updated_cb (GstPlayer *player,
|
||||
GstClockTime time,
|
||||
GtkGstMediaFile *self)
|
||||
{
|
||||
gtk_media_stream_update (GTK_MEDIA_STREAM (self), FROM_GST_TIME (time));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_duration_changed_cb (GstPlayer *player,
|
||||
GstClockTime duration,
|
||||
GtkGstMediaFile *self)
|
||||
{
|
||||
if (gtk_media_stream_is_prepared (GTK_MEDIA_STREAM (self)))
|
||||
return;
|
||||
|
||||
gtk_media_stream_prepared (GTK_MEDIA_STREAM (self),
|
||||
TRUE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
FROM_GST_TIME (duration));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_seek_done_cb (GstPlayer *player,
|
||||
GstClockTime time,
|
||||
GtkGstMediaFile *self)
|
||||
{
|
||||
/* if we're not seeking, we're doing the loop seek-back after EOS */
|
||||
if (gtk_media_stream_is_seeking (GTK_MEDIA_STREAM (self)))
|
||||
gtk_media_stream_seek_success (GTK_MEDIA_STREAM (self));
|
||||
gtk_media_stream_update (GTK_MEDIA_STREAM (self), FROM_GST_TIME (time));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_end_of_stream_cb (GstPlayer *player,
|
||||
GtkGstMediaFile *self)
|
||||
{
|
||||
if (gtk_media_stream_get_ended (GTK_MEDIA_STREAM (self)))
|
||||
return;
|
||||
|
||||
if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self)))
|
||||
{
|
||||
gst_player_seek (self->player, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_media_stream_ended (GTK_MEDIA_STREAM (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_destroy_player (GtkGstMediaFile *self)
|
||||
{
|
||||
if (self->player == NULL)
|
||||
return;
|
||||
|
||||
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_duration_changed_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_position_updated_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_end_of_stream_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_seek_done_cb, self);
|
||||
g_object_unref (self->player);
|
||||
self->player = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_create_player (GtkGstMediaFile *file)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (file);
|
||||
|
||||
if (self->player != NULL)
|
||||
return;
|
||||
|
||||
self->player = gst_player_new (GST_PLAYER_VIDEO_RENDERER (g_object_ref (self->paintable)),
|
||||
gst_player_g_main_context_signal_dispatcher_new (NULL));
|
||||
g_signal_connect (self->player, "duration-changed", G_CALLBACK (gtk_gst_media_file_duration_changed_cb), self);
|
||||
g_signal_connect (self->player, "position-updated", G_CALLBACK (gtk_gst_media_file_position_updated_cb), self);
|
||||
g_signal_connect (self->player, "end-of-stream", G_CALLBACK (gtk_gst_media_file_end_of_stream_cb), self);
|
||||
g_signal_connect (self->player, "seek-done", G_CALLBACK (gtk_gst_media_file_seek_done_cb), self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_open (GtkMediaFile *media_file)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (media_file);
|
||||
GFile *file;
|
||||
|
||||
gtk_gst_media_file_create_player (self);
|
||||
|
||||
file = gtk_media_file_get_file (media_file);
|
||||
|
||||
if (file)
|
||||
{
|
||||
/* XXX: This is technically incorrect because GFile uris aren't real uris */
|
||||
char *uri = g_file_get_uri (file);
|
||||
|
||||
gst_player_set_uri (self->player, uri);
|
||||
|
||||
g_free (uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's an input stream */
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
gst_player_pause (self->player);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_close (GtkMediaFile *file)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (file);
|
||||
|
||||
gtk_gst_media_file_destroy_player (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_media_file_play (GtkMediaStream *stream)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gst_player_play (self->player);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_pause (GtkMediaStream *stream)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gst_player_pause (self->player);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_seek (GtkMediaStream *stream,
|
||||
gint64 timestamp)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gst_player_seek (self->player, TO_GST_TIME (timestamp));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_update_audio (GtkMediaStream *stream,
|
||||
gboolean muted,
|
||||
double volume)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gst_player_set_mute (self->player, muted);
|
||||
gst_player_set_volume (self->player, volume);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_stream_realize (GtkMediaStream *stream,
|
||||
GdkWindow *window)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gtk_gst_paintable_realize (GTK_GST_PAINTABLE (self->paintable), window);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_stream_unrealize (GtkMediaStream *stream,
|
||||
GdkWindow *window)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
|
||||
|
||||
gtk_gst_paintable_unrealize (GTK_GST_PAINTABLE (self->paintable), window);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_dispose (GObject *object)
|
||||
{
|
||||
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (object);
|
||||
|
||||
gtk_gst_media_file_destroy_player (self);
|
||||
if (self->paintable)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self);
|
||||
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self);
|
||||
g_clear_object (&self->paintable);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (gtk_gst_media_file_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_class_init (GtkGstMediaFileClass *klass)
|
||||
{
|
||||
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
|
||||
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
file_class->open = gtk_gst_media_file_open;
|
||||
file_class->close = gtk_gst_media_file_close;
|
||||
|
||||
stream_class->play = gtk_gst_media_file_play;
|
||||
stream_class->pause = gtk_gst_media_file_pause;
|
||||
stream_class->seek = gtk_gst_media_file_seek;
|
||||
stream_class->update_audio = gtk_gst_media_file_update_audio;
|
||||
stream_class->realize = gtk_gst_media_stream_realize;
|
||||
stream_class->unrealize = gtk_gst_media_stream_unrealize;
|
||||
|
||||
gobject_class->dispose = gtk_gst_media_file_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_media_file_init (GtkGstMediaFile *self)
|
||||
{
|
||||
self->paintable = gtk_gst_paintable_new ();
|
||||
g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
|
||||
g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
|
||||
}
|
||||
|
33
modules/media/gtkgstmediafileprivate.h
Normal file
33
modules/media/gtkgstmediafileprivate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_MEDIA_FILE_H__
|
||||
#define __GTK_GST_MEDIA_FILE_H__
|
||||
|
||||
#include <gtk/gtkmediafile.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_GST_MEDIA_FILE (gtk_gst_media_file_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkGstMediaFile, gtk_gst_media_file, GTK, GST_MEDIA_FILE, GtkMediaFile)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_MEDIA_FILE_H__ */
|
274
modules/media/gtkgstpaintable.c
Normal file
274
modules/media/gtkgstpaintable.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkgstpaintableprivate.h"
|
||||
|
||||
#include "gtkgstsinkprivate.h"
|
||||
|
||||
#include <gst/player/gstplayer-video-renderer.h>
|
||||
|
||||
struct _GtkGstPaintable
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GdkGLContext *context;
|
||||
|
||||
GdkPaintable *image;
|
||||
};
|
||||
|
||||
struct _GtkGstPaintableClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
|
||||
|
||||
if (self->image)
|
||||
gdk_paintable_snapshot (self->image, snapshot, width, height);
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_gst_paintable_paintable_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
|
||||
|
||||
if (self->image)
|
||||
return GDK_PAINTABLE (g_object_ref (self->image));
|
||||
|
||||
g_warning ("FIXME: return empty something here");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_gst_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
|
||||
|
||||
if (self->image)
|
||||
return gdk_paintable_get_intrinsic_width (self->image);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_gst_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
|
||||
|
||||
if (self->image)
|
||||
return gdk_paintable_get_intrinsic_height (self->image);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double
|
||||
gtk_gst_paintable_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
|
||||
|
||||
if (self->image)
|
||||
return gdk_paintable_get_intrinsic_aspect_ratio (self->image);
|
||||
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_gst_paintable_paintable_snapshot;
|
||||
iface->get_current_image = gtk_gst_paintable_paintable_get_current_image;
|
||||
iface->get_intrinsic_width = gtk_gst_paintable_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_gst_paintable_paintable_get_intrinsic_height;
|
||||
iface->get_intrinsic_aspect_ratio = gtk_gst_paintable_paintable_get_intrinsic_aspect_ratio;
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
gtk_gst_paintable_video_renderer_create_video_sink (GstPlayerVideoRenderer *renderer,
|
||||
GstPlayer *player)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (renderer);
|
||||
GstElement *element;
|
||||
|
||||
element = g_object_new (GTK_TYPE_GST_SINK,
|
||||
"paintable", self,
|
||||
"gl-context", self->context,
|
||||
NULL);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_video_renderer_init (GstPlayerVideoRendererInterface *iface)
|
||||
{
|
||||
iface->create_video_sink = gtk_gst_paintable_video_renderer_create_video_sink;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkGstPaintable, gtk_gst_paintable, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_gst_paintable_paintable_init)
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER,
|
||||
gtk_gst_paintable_video_renderer_init));
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_dispose (GObject *object)
|
||||
{
|
||||
GtkGstPaintable *self = GTK_GST_PAINTABLE (object);
|
||||
|
||||
g_clear_object (&self->image);
|
||||
|
||||
G_OBJECT_CLASS (gtk_gst_paintable_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_class_init (GtkGstPaintableClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_gst_paintable_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_init (GtkGstPaintable *self)
|
||||
{
|
||||
}
|
||||
|
||||
GdkPaintable *
|
||||
gtk_gst_paintable_new (void)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_GST_PAINTABLE, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_paintable_realize (GtkGstPaintable *self,
|
||||
GdkWindow *window)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
if (self->context)
|
||||
return;
|
||||
|
||||
self->context = gdk_window_create_gl_context (window, &error);
|
||||
if (self->context == NULL)
|
||||
{
|
||||
GST_INFO ("failed to create GDK GL context: %s", error->message);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gdk_gl_context_realize (self->context, &error))
|
||||
{
|
||||
GST_INFO ("failed to realize GDK GL context: %s", error->message);
|
||||
g_clear_object (&self->context);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_paintable_unrealize (GtkGstPaintable *self,
|
||||
GdkWindow *window)
|
||||
{
|
||||
/* XXX: We could be smarter here and:
|
||||
* - track how often we were realized with that window
|
||||
* - track alternate windows
|
||||
*/
|
||||
if (self->context == NULL)
|
||||
return;
|
||||
|
||||
if (gdk_gl_context_get_window (self->context) == window)
|
||||
g_clear_object (&self->context);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_paintable_set_paintable (GtkGstPaintable *self,
|
||||
GdkPaintable *paintable)
|
||||
{
|
||||
gboolean size_changed;
|
||||
|
||||
if (self->image == paintable)
|
||||
return;
|
||||
|
||||
if (self->image == NULL ||
|
||||
gdk_paintable_get_intrinsic_width (self->image) != gdk_paintable_get_intrinsic_width (paintable) ||
|
||||
gdk_paintable_get_intrinsic_height (self->image) != gdk_paintable_get_intrinsic_height (paintable) ||
|
||||
gdk_paintable_get_intrinsic_aspect_ratio (self->image) != gdk_paintable_get_intrinsic_aspect_ratio (paintable))
|
||||
size_changed = TRUE;
|
||||
else
|
||||
size_changed = FALSE;
|
||||
|
||||
g_set_object (&self->image, paintable);
|
||||
|
||||
if (size_changed)
|
||||
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
|
||||
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
||||
}
|
||||
|
||||
typedef struct _SetTextureInvocation SetTextureInvocation;
|
||||
|
||||
struct _SetTextureInvocation {
|
||||
GtkGstPaintable *paintable;
|
||||
GdkTexture *texture;
|
||||
};
|
||||
|
||||
static void
|
||||
set_texture_invocation_free (SetTextureInvocation *invoke)
|
||||
{
|
||||
g_object_unref (invoke->paintable);
|
||||
g_object_unref (invoke->texture);
|
||||
|
||||
g_slice_free (SetTextureInvocation, invoke);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_paintable_set_texture_invoke (gpointer data)
|
||||
{
|
||||
SetTextureInvocation *invoke = data;
|
||||
|
||||
gtk_gst_paintable_set_paintable (invoke->paintable,
|
||||
GDK_PAINTABLE (invoke->texture));
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_paintable_queue_set_texture (GtkGstPaintable *self,
|
||||
GdkTexture *texture)
|
||||
{
|
||||
SetTextureInvocation *invoke;
|
||||
|
||||
invoke = g_slice_new0 (SetTextureInvocation);
|
||||
invoke->paintable = g_object_ref (self);
|
||||
invoke->texture = g_object_ref (texture);
|
||||
|
||||
g_main_context_invoke_full (NULL,
|
||||
G_PRIORITY_DEFAULT,
|
||||
gtk_gst_paintable_set_texture_invoke,
|
||||
invoke,
|
||||
(GDestroyNotify) set_texture_invocation_free);
|
||||
}
|
||||
|
43
modules/media/gtkgstpaintableprivate.h
Normal file
43
modules/media/gtkgstpaintableprivate.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright © 2018 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_PAINTABLE_H__
|
||||
#define __GTK_GST_PAINTABLE_H__
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_GST_PAINTABLE (gtk_gst_paintable_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkGstPaintable, gtk_gst_paintable, GTK, GST_PAINTABLE, GObject)
|
||||
|
||||
GdkPaintable * gtk_gst_paintable_new (void);
|
||||
|
||||
void gtk_gst_paintable_realize (GtkGstPaintable *self,
|
||||
GdkWindow *window);
|
||||
void gtk_gst_paintable_unrealize (GtkGstPaintable *self,
|
||||
GdkWindow *window);
|
||||
|
||||
void gtk_gst_paintable_queue_set_texture (GtkGstPaintable *self,
|
||||
GdkTexture *texture);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_PAINTABLE_H__ */
|
525
modules/media/gtkgstsink.c
Normal file
525
modules/media/gtkgstsink.c
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkgstsinkprivate.h"
|
||||
|
||||
#include "gtkgstpaintableprivate.h"
|
||||
#include "gtkintl.h"
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
#include <gst/gl/wayland/gstgldisplay_wayland.h>
|
||||
#endif
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PAINTABLE,
|
||||
PROP_GL_CONTEXT,
|
||||
|
||||
N_PROPS,
|
||||
};
|
||||
|
||||
GST_DEBUG_CATEGORY (gtk_debug_gst_sink);
|
||||
#define GST_CAT_DEFAULT gtk_debug_gst_sink
|
||||
|
||||
#define FORMATS "{ BGRA, ARGB, RGBA, ABGR, RGB, BGR }"
|
||||
|
||||
#define NOGL_CAPS GST_VIDEO_CAPS_MAKE (FORMATS)
|
||||
#define GL_CAPS GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")
|
||||
|
||||
static GstStaticPadTemplate gtk_gst_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GL_CAPS "; " NOGL_CAPS)
|
||||
);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkGstSink, gtk_gst_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
GST_DEBUG_CATEGORY_INIT (gtk_debug_gst_sink,
|
||||
"gtkgstsink", 0, "GtkGstMediaFile Video Sink"));
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
|
||||
static void
|
||||
gtk_gst_sink_get_times (GstBaseSink *bsink,
|
||||
GstBuffer *buf,
|
||||
GstClockTime *start,
|
||||
GstClockTime *end)
|
||||
{
|
||||
GtkGstSink *gtk_sink;
|
||||
|
||||
gtk_sink = GTK_GST_SINK (bsink);
|
||||
|
||||
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf))
|
||||
{
|
||||
*start = GST_BUFFER_TIMESTAMP (buf);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
||||
{
|
||||
*end = *start + GST_BUFFER_DURATION (buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0)
|
||||
*end = *start + gst_util_uint64_scale_int (GST_SECOND,
|
||||
GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
|
||||
GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gtk_gst_sink_get_caps (GstBaseSink *bsink,
|
||||
GstCaps *filter)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (bsink);
|
||||
GstCaps *tmp;
|
||||
GstCaps *result;
|
||||
|
||||
if (self->gst_context)
|
||||
{
|
||||
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = gst_caps_from_string (NOGL_CAPS);
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "advertising own caps %" GST_PTR_FORMAT, tmp);
|
||||
|
||||
if (filter)
|
||||
{
|
||||
GST_DEBUG_OBJECT (self, "intersecting with filter caps %" GST_PTR_FORMAT, filter);
|
||||
|
||||
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "returning caps: %" GST_PTR_FORMAT, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_sink_set_caps (GstBaseSink *bsink,
|
||||
GstCaps *caps)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (bsink);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "set caps with %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (!gst_video_info_from_caps (&self->v_info, caps))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_sink_query (GstBaseSink *bsink,
|
||||
GstQuery *query)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (bsink);
|
||||
|
||||
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT &&
|
||||
self->gst_display != NULL &&
|
||||
gst_gl_handle_context_query (GST_ELEMENT (self), query, self->gst_display, self->gst_context, self->gst_app_context))
|
||||
return TRUE;
|
||||
|
||||
return GST_BASE_SINK_CLASS (gtk_gst_sink_parent_class)->query (bsink, query);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_sink_propose_allocation (GstBaseSink *bsink,
|
||||
GstQuery *query)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (bsink);
|
||||
GstBufferPool *pool = NULL;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
guint size;
|
||||
gboolean need_pool;
|
||||
|
||||
if (!self->gst_context)
|
||||
return FALSE;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, &need_pool);
|
||||
|
||||
if (caps == NULL)
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gst_caps_features_contains (gst_caps_get_features (caps, 0), GST_CAPS_FEATURE_MEMORY_GL_MEMORY))
|
||||
return FALSE;
|
||||
|
||||
if (need_pool)
|
||||
{
|
||||
GstVideoInfo info;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
{
|
||||
GST_DEBUG_OBJECT (self, "invalid caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "create new pool");
|
||||
pool = gst_gl_buffer_pool_new (self->gst_context);
|
||||
|
||||
/* the normal size of a frame */
|
||||
size = info.size;
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, config))
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "failed setting config");
|
||||
gst_object_unref (pool);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* we need at least 2 buffer because we hold on to the last one */
|
||||
gst_query_add_allocation_pool (query, pool, size, 2, 0);
|
||||
gst_object_unref (pool);
|
||||
}
|
||||
|
||||
/* we also support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static GdkMemoryFormat
|
||||
gtk_gst_memory_format_from_video (GstVideoFormat format)
|
||||
{
|
||||
switch ((guint) format)
|
||||
{
|
||||
case GST_VIDEO_FORMAT_BGRA:
|
||||
return GDK_MEMORY_B8G8R8A8;
|
||||
case GST_VIDEO_FORMAT_ARGB:
|
||||
return GDK_MEMORY_A8R8G8B8;
|
||||
case GST_VIDEO_FORMAT_RGBA:
|
||||
return GDK_MEMORY_R8G8B8A8;
|
||||
case GST_VIDEO_FORMAT_ABGR:
|
||||
return GDK_MEMORY_A8B8G8R8;
|
||||
case GST_VIDEO_FORMAT_RGB:
|
||||
return GDK_MEMORY_R8G8B8;
|
||||
case GST_VIDEO_FORMAT_BGR:
|
||||
return GDK_MEMORY_B8G8R8;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return GDK_MEMORY_A8R8G8B8;
|
||||
}
|
||||
}
|
||||
|
||||
static GdkTexture *
|
||||
gtk_gst_sink_texture_from_buffer (GtkGstSink *self,
|
||||
GstBuffer *buffer)
|
||||
{
|
||||
GstVideoFrame frame;
|
||||
GdkTexture *texture;
|
||||
|
||||
if (buffer->pool && GST_IS_GL_BUFFER_POOL (buffer->pool) &&
|
||||
gst_video_frame_map (&frame, &self->v_info, buffer, GST_MAP_READ | GST_MAP_GL))
|
||||
{
|
||||
texture = gdk_gl_texture_new (self->gdk_context,
|
||||
*(guint *) frame.data[0],
|
||||
frame.info.width,
|
||||
frame.info.height,
|
||||
(GDestroyNotify) gst_buffer_unref,
|
||||
gst_buffer_ref (buffer));
|
||||
gst_video_frame_unmap (&frame);
|
||||
}
|
||||
else if (gst_video_frame_map (&frame, &self->v_info, buffer, GST_MAP_READ))
|
||||
{
|
||||
GBytes *bytes;
|
||||
|
||||
bytes = g_bytes_new_with_free_func (frame.data[0],
|
||||
frame.info.width * frame.info.stride[0],
|
||||
(GDestroyNotify) gst_buffer_unref,
|
||||
gst_buffer_ref (buffer));
|
||||
texture = gdk_memory_texture_new (frame.info.width,
|
||||
frame.info.height,
|
||||
gtk_gst_memory_format_from_video (GST_VIDEO_FRAME_FORMAT (&frame)),
|
||||
bytes,
|
||||
frame.info.stride[0]);
|
||||
gst_video_frame_unmap (&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
GST_ERROR_OBJECT (self, "Could not convert buffer to texture.");
|
||||
texture = NULL;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gtk_gst_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
||||
{
|
||||
GtkGstSink *self;
|
||||
GdkTexture *texture;
|
||||
|
||||
GST_TRACE ("rendering buffer:%p", buf);
|
||||
|
||||
self = GTK_GST_SINK (vsink);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
texture = gtk_gst_sink_texture_from_buffer (self, buf);
|
||||
if (texture)
|
||||
{
|
||||
gtk_gst_paintable_queue_set_texture (self->paintable, texture);
|
||||
g_object_unref (texture);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_initialize_gl (GtkGstSink *self)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
GError *error = NULL;
|
||||
|
||||
display = gdk_gl_context_get_display (self->gdk_context);
|
||||
|
||||
gdk_gl_context_make_current (self->gdk_context);
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
|
||||
if (GDK_IS_X11_DISPLAY (display))
|
||||
{
|
||||
GstGLPlatform platform = GST_GL_PLATFORM_GLX;
|
||||
GstGLAPI gl_api;
|
||||
guintptr gl_handle;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "got GLX on X11!");
|
||||
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
if (gl_handle)
|
||||
{
|
||||
self->gst_display = GST_GL_DISPLAY (gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay (display)));
|
||||
self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api);
|
||||
}
|
||||
else
|
||||
{
|
||||
GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using GLX");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GDK_IS_WAYLAND_DISPLAY (display))
|
||||
{
|
||||
GstGLPlatform platform = GST_GL_PLATFORM_GLX;
|
||||
GstGLAPI gl_api;
|
||||
guintptr gl_handle;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "got EGL on Wayland!");
|
||||
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
|
||||
if (gl_handle)
|
||||
{
|
||||
struct wl_display *wayland_display;
|
||||
|
||||
wayland_display = gdk_wayland_display_get_wl_display (display);
|
||||
self->gst_display = GST_GL_DISPLAY (gst_gl_display_wayland_new_with_display (wayland_display));
|
||||
self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api);
|
||||
}
|
||||
else
|
||||
{
|
||||
GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using Wayland EGL");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
GST_INFO_OBJECT (self, "Unsupported GDK display %s for GL", G_OBJECT_TYPE_NAME (display));
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (self->gst_app_context != NULL);
|
||||
|
||||
gst_gl_context_activate (self->gst_app_context, TRUE);
|
||||
if (!gst_gl_context_fill_info (self->gst_app_context, &error))
|
||||
{
|
||||
GST_ERROR_OBJECT (self, "failed to retrieve GDK context info: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
g_clear_object (&self->gst_app_context);
|
||||
g_clear_object (&self->gst_display);
|
||||
return;
|
||||
} else {
|
||||
gst_gl_context_activate (self->gst_app_context, FALSE);
|
||||
}
|
||||
|
||||
if (!gst_gl_display_create_context (self->gst_display, self->gst_app_context, &self->gst_context, &error))
|
||||
{
|
||||
GST_ERROR_OBJECT (self, "Couldn't create GL context: %s", error->message);
|
||||
g_error_free (error);
|
||||
g_clear_object (&self->gst_app_context);
|
||||
g_clear_object (&self->gst_display);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PAINTABLE:
|
||||
self->paintable = g_value_dup_object (value);
|
||||
if (self->paintable == NULL)
|
||||
self->paintable = GTK_GST_PAINTABLE (gtk_gst_paintable_new ());
|
||||
break;
|
||||
|
||||
case PROP_GL_CONTEXT:
|
||||
self->gdk_context = g_value_dup_object (value);
|
||||
if (self->gdk_context != NULL)
|
||||
gtk_gst_sink_initialize_gl (self);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PAINTABLE:
|
||||
g_value_set_object (value, self->paintable);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_dispose (GObject *object)
|
||||
{
|
||||
GtkGstSink *self = GTK_GST_SINK (object);
|
||||
|
||||
g_clear_object (&self->gst_app_context);
|
||||
g_clear_object (&self->gst_display);
|
||||
g_clear_object (&self->gdk_context);
|
||||
g_clear_object (&self->paintable);
|
||||
|
||||
G_OBJECT_CLASS (gtk_gst_sink_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_class_init (GtkGstSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
||||
GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
|
||||
GstVideoSinkClass *gstvideosink_class = GST_VIDEO_SINK_CLASS (klass);
|
||||
|
||||
gobject_class->set_property = gtk_gst_sink_set_property;
|
||||
gobject_class->get_property = gtk_gst_sink_get_property;
|
||||
gobject_class->dispose = gtk_gst_sink_dispose;
|
||||
|
||||
gstbasesink_class->set_caps = gtk_gst_sink_set_caps;
|
||||
gstbasesink_class->get_times = gtk_gst_sink_get_times;
|
||||
gstbasesink_class->query = gtk_gst_sink_query;
|
||||
gstbasesink_class->propose_allocation = gtk_gst_sink_propose_allocation;
|
||||
gstbasesink_class->get_caps = gtk_gst_sink_get_caps;
|
||||
|
||||
gstvideosink_class->show_frame = gtk_gst_sink_show_frame;
|
||||
|
||||
/**
|
||||
* GtkGstSink:paintable:
|
||||
*
|
||||
* The paintable that provides the picture for this sink.
|
||||
*/
|
||||
properties[PROP_PAINTABLE] =
|
||||
g_param_spec_object ("paintable",
|
||||
P_("paintable"),
|
||||
P_("Paintable providing the picture"),
|
||||
GTK_TYPE_GST_PAINTABLE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* GtkGstSink:gl-context:
|
||||
*
|
||||
* The #GdkGLContext to use for GL rendering.
|
||||
*/
|
||||
properties[PROP_GL_CONTEXT] =
|
||||
g_param_spec_object ("gl-context",
|
||||
P_("gl-context"),
|
||||
P_("GL context to use for rendering"),
|
||||
GDK_TYPE_GL_CONTEXT,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class,
|
||||
"GtkMediaStream Video Sink",
|
||||
"Sink/Video", "The video sink used by GtkMediaStream",
|
||||
"Matthew Waters <matthew@centricular.com>, "
|
||||
"Benjamin Otte <otte@gnome.org>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
>k_gst_sink_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_sink_init (GtkGstSink * gtk_sink)
|
||||
{
|
||||
}
|
||||
|
68
modules/media/gtkgstsinkprivate.h
Normal file
68
modules/media/gtkgstsinkprivate.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_SINK_PRIVATE_H__
|
||||
#define __GTK_GST_SINK_PRIVATE_H__
|
||||
|
||||
#include "gtkgstpaintableprivate.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
#define GST_USE_UNSTABLE_API
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#define GTK_TYPE_GST_SINK (gtk_gst_sink_get_type())
|
||||
#define GTK_GST_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_SINK,GtkGstSink))
|
||||
#define GTK_GST_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_SINK,GtkGstSinkClass))
|
||||
#define GTK_GST_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_GST_SINK, GtkGstSinkClass))
|
||||
#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_SINK))
|
||||
#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_SINK))
|
||||
#define GTK_GST_SINK_CAST(obj) ((GtkGstSink*)(obj))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GtkGstSink GtkGstSink;
|
||||
typedef struct _GtkGstSinkClass GtkGstSinkClass;
|
||||
|
||||
struct _GtkGstSink
|
||||
{
|
||||
/* <private> */
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo v_info;
|
||||
GtkGstPaintable * paintable;
|
||||
|
||||
GdkGLContext * gdk_context;
|
||||
GstGLDisplay * gst_display;
|
||||
GstGLContext * gst_app_context;
|
||||
GstGLContext * gst_context;
|
||||
};
|
||||
|
||||
struct _GtkGstSinkClass
|
||||
{
|
||||
GstVideoSinkClass object_class;
|
||||
};
|
||||
|
||||
GType gtk_gst_sink_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_SINK_PRIVATE_H__ */
|
26
modules/media/meson.build
Normal file
26
modules/media/meson.build
Normal file
@@ -0,0 +1,26 @@
|
||||
media_subdir = 'gtk-4.0/@0@/media'.format(gtk_binary_version)
|
||||
media_install_dir = join_paths(get_option('libdir'), media_subdir)
|
||||
|
||||
if ffmpeg_enabled
|
||||
shared_module('media-ffmpeg',
|
||||
'gtkffmediafile.c',
|
||||
c_args: [
|
||||
'-DGTK_COMPILATION'
|
||||
],
|
||||
dependencies: [ libgtk_dep, ffmpeg_deps ],
|
||||
install_dir: media_install_dir,
|
||||
install : true)
|
||||
endif
|
||||
|
||||
if gstreamer_enabled
|
||||
shared_module('media-gstreamer',
|
||||
'gtkgstmediafile.c',
|
||||
'gtkgstpaintable.c',
|
||||
'gtkgstsink.c',
|
||||
c_args: [
|
||||
'-DGTK_COMPILATION'
|
||||
],
|
||||
dependencies: [ libgtk_dep, gstreamer_deps ],
|
||||
install_dir: media_install_dir,
|
||||
install : true)
|
||||
endif
|
@@ -1,3 +1,5 @@
|
||||
if os_unix
|
||||
subdir('printbackends')
|
||||
endif
|
||||
|
||||
subdir('media')
|
||||
|
@@ -4,12 +4,6 @@
|
||||
/* maximum bytes per pixel */
|
||||
#define MAX_BPP 4
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
|
||||
#elif G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
BLUE,
|
||||
GREEN,
|
||||
@@ -124,7 +118,7 @@ test_download_1x1 (gconstpointer data)
|
||||
const TestData *test_data = data;
|
||||
GdkTexture *expected, *test;
|
||||
|
||||
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
|
||||
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
|
||||
test = create_texture (test_data->format, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
|
||||
|
||||
compare_textures (expected, test, tests[test_data->format].opaque);
|
||||
@@ -139,7 +133,7 @@ test_download_1x1_with_stride (gconstpointer data)
|
||||
const TestData *test_data = data;
|
||||
GdkTexture *expected, *test;
|
||||
|
||||
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 1, 1, 4);
|
||||
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, 4);
|
||||
test = create_texture (test_data->format, test_data->color, 1, 1, 2 * MAX_BPP);
|
||||
|
||||
compare_textures (expected, test, tests[test_data->format].opaque);
|
||||
@@ -154,7 +148,7 @@ test_download_4x4 (gconstpointer data)
|
||||
const TestData *test_data = data;
|
||||
GdkTexture *expected, *test;
|
||||
|
||||
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 4, 4, 16);
|
||||
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
|
||||
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * tests[test_data->format].bytes_per_pixel);
|
||||
|
||||
compare_textures (expected, test, tests[test_data->format].opaque);
|
||||
@@ -169,7 +163,7 @@ test_download_4x4_with_stride (gconstpointer data)
|
||||
const TestData *test_data = data;
|
||||
GdkTexture *expected, *test;
|
||||
|
||||
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 4, 4, 16);
|
||||
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
|
||||
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * MAX_BPP);
|
||||
|
||||
compare_textures (expected, test, tests[test_data->format].opaque);
|
||||
|
Reference in New Issue
Block a user