• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer base utils library plugin install support for applications
2  * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net>
3  * Copyright (C) 2006 Ryan Lortie <desrt desrt ca>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:gstpbutilsinstallplugins
23  * @title: Install-plugins
24  * @short_description: Missing plugin installation support for applications
25  *
26  * ## Overview
27  *
28  * Using this API, applications can request the installation of missing
29  * GStreamer plugins. These may be missing decoders/demuxers or
30  * encoders/muxers for a certain format, sources or sinks for a certain URI
31  * protocol (e.g. 'http'), or certain elements known by their element
32  * factory name ('audioresample').
33  *
34  * Whether plugin installation is supported or not depends on the operating
35  * system and/or distribution in question. The vendor of the operating
36  * system needs to make sure the necessary hooks and mechanisms are in
37  * place for plugin installation to work. See below for more detailed
38  * information.
39  *
40  * From the application perspective, plugin installation is usually
41  * triggered either
42  *
43  * -   when the application itself has found that it wants or needs to
44  *     install a certain element
45  * -   when the application has been notified by an element (such as
46  *     playbin or decodebin) that one or more plugins are missing *and* the
47  *     application has decided that it wants to install one or more of
48  *     those missing plugins
49  *
50  * The install functions in this section all take one or more 'detail
51  * strings'. These detail strings contain information about the type of
52  * plugin that needs to be installed (decoder, encoder, source, sink, or
53  * named element), and some additional information such GStreamer version
54  * used and a human-readable description of the component to install for
55  * user dialogs.
56  *
57  * Applications should not concern themselves with the composition of the
58  * string itself. They should regard the string as if it was a shared
59  * secret between GStreamer and the plugin installer application.
60  *
61  * Detail strings can be obtained using the function
62  * gst_missing_plugin_message_get_installer_detail() on a
63  * missing-plugin message. Such a message will either have been found by
64  * the application on a pipeline's #GstBus, or the application will have
65  * created it itself using gst_missing_element_message_new(),
66  * gst_missing_decoder_message_new(),
67  * gst_missing_encoder_message_new(),
68  * gst_missing_uri_sink_message_new(), or
69  * gst_missing_uri_source_message_new().
70  *
71  * For each GStreamer element/plugin/component that should be installed,
72  * the application needs one of those 'installer detail' string mentioned
73  * in the previous section. This string can be obtained, as already
74  * mentioned above, from a missing-plugin message using the function
75  * gst_missing_plugin_message_get_installer_detail(). The
76  * missing-plugin message is either posted by another element and then
77  * found on the bus by the application, or the application has created it
78  * itself as described above.
79  *
80  * The application will then call gst_install_plugins_async(), passing a
81  * NULL-terminated array of installer detail strings, and a function that
82  * should be called when the installation of the plugins has finished
83  * (successfully or not). Optionally, a #GstInstallPluginsContext created
84  * with gst_install_plugins_context_new() may be passed as well. This
85  * way additional optional arguments like the application window's XID can
86  * be passed to the external installer application.
87  *
88  * gst_install_plugins_async() will return almost immediately, with the
89  * return code indicating whether plugin installation was started or not.
90  * If the necessary hooks for plugin installation are in place and an
91  * external installer application has in fact been called, the passed in
92  * function will be called with a result code as soon as the external
93  * installer has finished. If the result code indicates that new plugins
94  * have been installed, the application will want to call
95  * gst_update_registry() so the run-time plugin registry is updated and
96  * the new plugins are made available to the application.
97  *
98  * > A Gtk/GLib main loop must be running in order for the result function
99  * > to be called when the external installer has finished. If this is not
100  * > the case, make sure to regularly call in your code:
101  * >
102  * > g_main_context_iteration (NULL,FALSE);
103  *
104  * ## 1. Installer hook
105  *
106  * When GStreamer applications initiate plugin installation via
107  * gst_install_plugins_async() or gst_install_plugins_sync(), a
108  * pre-defined helper application will be called.
109  *
110  * The exact path of the helper application to be called is set at compile
111  * time, usually by the build system based on the install prefix.
112  * For a normal package build into the `/usr` prefix, this will usually
113  * default to `/usr/libexec/gst-install-plugins-helper` or
114  * `/usr/lib/gst-install-plugins-helper`.
115  *
116  * Vendors/distros who want to support GStreamer plugin installation should
117  * either provide such a helper script/application or use the meson option
118  * `-Dinstall_plugins_helper'=/path/to/installer` to make GStreamer call an
119  * installer of their own directly.
120  *
121  * It is strongly recommended that vendors provide a small helper
122  * application as interlocutor to the real installer though, even more so
123  * if command line argument munging is required to transform the command
124  * line arguments passed by GStreamer to the helper application into
125  * arguments that are understood by the real installer.
126  *
127  * The helper application path defined at compile time can be overridden at
128  * runtime by setting the GST_INSTALL_PLUGINS_HELPER environment
129  * variable. This can be useful for testing/debugging purposes.
130  *
131  * ## 2. Arguments passed to the install helper
132  *
133  * GStreamer will pass the following arguments to the install helper (this
134  * is in addition to the path of the executable itself, which is by
135  * convention argv[0]):
136  *
137  * -   none to many optional arguments in the form of `--foo-bar=val`.
138  *     Example: `--transient-for=XID` where XID is the X Window ID of the
139  *     main window of the calling application (so the installer can make
140  *     itself transient to that window). Unknown optional arguments should
141  *     be ignored by the installer.
142  *
143  * -   one 'installer detail string' argument for each plugin to be
144  *     installed; these strings will have a `gstreamer` prefix; the exact
145  *     format of the detail string is explained below
146  *
147  * ## 3. Detail string describing the missing plugin
148  *
149  * The string is in UTF-8 encoding and is made up of several fields,
150  * separated by '|' characters (but neither the first nor the last
151  * character is a '|'). The fields are:
152  *
153  * -   plugin system identifier, ie. "gstreamer"
154  *     This identifier determines the format of the rest of the detail
155  *     string. Automatic plugin installers should not process detail
156  *     strings with unknown identifiers. This allows other plugin-based
157  *     libraries to use the same mechanism for their automatic plugin
158  *     installation needs, or for the format to be changed should it turn
159  *     out to be insufficient.
160  * -   plugin system version, e.g. "1.0"
161  *     This is required so that when there is GStreamer-2.0 at some point
162  *     in future, the different major versions can still co-exist and use
163  *     the same plugin install mechanism in the same way.
164  * -   application identifier, e.g. "totem"
165  *     This may also be in the form of "pid/12345" if the program name
166  *     can't be obtained for some reason.
167  * -   human-readable localised description of the required component, e.g.
168  *     "Vorbis audio decoder"
169  * -   identifier string for the required component (see below for details
170  *     about how to map this to the package/plugin that needs installing),
171  *     e.g.
172  *     -   urisource-$(PROTOCOL_REQUIRED), e.g. urisource-http or
173  *         urisource-mms
174  *     -   element-$(ELEMENT_REQUIRED), e.g. element-videoconvert
175  *     -   decoder-$(CAPS_REQUIRED), e.g. (do read below for more
176  *         details!):
177  *         -   decoder-audio/x-vorbis
178  *         -   decoder-application/ogg
179  *         -   decoder-audio/mpeg, mpegversion=(int)4
180  *         -   decoder-video/mpeg, systemstream=(boolean)true,
181  *             mpegversion=(int)2
182  *     -   encoder-$(CAPS_REQUIRED), e.g. encoder-audio/x-vorbis
183  * -   optional further fields not yet specified
184  *
185  * An entire ID string might then look like this, for example: `
186  * gstreamer|1.0|totem|Vorbis audio decoder|decoder-audio/x-vorbis`
187  *
188  * Plugin installers parsing this ID string should expect further fields
189  * also separated by '|' symbols and either ignore them, warn the user, or
190  * error out when encountering them.
191  *
192  * Those unfamiliar with the GStreamer 'caps' system should note a few
193  * things about the caps string used in the above decoder/encoder case:
194  *
195  * -   the first part ("video/mpeg") of the caps string is a GStreamer
196  *     media type and *not* a MIME type. Wherever possible, the GStreamer
197  *     media type will be the same as the corresponding MIME type, but
198  *     often it is not.
199  * -   a caps string may or may not have additional comma-separated fields
200  *     of various types (as seen in the examples above)
201  * -   the caps string of a 'required' component (as above) will always
202  *     have fields with fixed values, whereas an introspected string (see
203  *     below) may have fields with non-fixed values. Compare for example:
204  *     -   `audio/mpeg, mpegversion=(int)4` vs.
205  *         `audio/mpeg, mpegversion=(int){2, 4}`
206  *     -   `video/mpeg, mpegversion=(int)2` vs.
207  *         `video/mpeg, systemstream=(boolean){ true, false}, mpegversion=(int)[1, 2]`
208  *
209  * ## 4. Exit codes the installer should return
210  *
211  * The installer should return one of the following exit codes when it
212  * exits:
213  *
214  * -   0 if all of the requested plugins could be installed
215  *     (#GST_INSTALL_PLUGINS_SUCCESS)
216  * -   1 if no appropriate installation candidate for any of the requested
217  *     plugins could be found. Only return this if nothing has been
218  *     installed (#GST_INSTALL_PLUGINS_NOT_FOUND)
219  * -   2 if an error occurred during the installation. The application will
220  *     assume that the user will already have seen an error message by the
221  *     installer in this case and will usually not show another one
222  *     (#GST_INSTALL_PLUGINS_ERROR)
223  * -   3 if some of the requested plugins could be installed, but not all
224  *     (#GST_INSTALL_PLUGINS_PARTIAL_SUCCESS)
225  * -   4 if the user aborted the installation
226  *     (#GST_INSTALL_PLUGINS_USER_ABORT)
227  *
228  * ## 5. How to map the required detail string to packages
229  *
230  * It is up to the vendor to find mechanism to map required components from
231  * the detail string to the actual packages/plugins to install. This could
232  * be a hardcoded list of mappings, for example, or be part of the
233  * packaging system metadata.
234  *
235  * GStreamer plugin files can be introspected for this information. The
236  * `gst-inspect` utility has a special command line option that will output
237  * information similar to what is required. For example `
238  * $ gst-inspect-1.0 --print-plugin-auto-install-info /path/to/libgstvorbis.so
239  * should output something along the lines of
240  * `decoder-audio/x-vorbis`, `element-vorbisdec` `element-vorbisenc`
241  * `element-vorbisparse`, `element-vorbistag`, `encoder-audio/x-vorbis`
242  *
243  * Note that in the encoder and decoder case the introspected caps can be
244  * more complex with additional fields, e.g.
245  * `audio/mpeg,mpegversion=(int){2,4}`, so they will not always exactly
246  * match the caps wanted by the application. It is up to the installer to
247  * deal with this (either by doing proper caps intersection using the
248  * GStreamer #GstCaps API, or by only taking into account the media type).
249  *
250  * Another potential source of problems are plugins such as ladspa or
251  * libvisual where the list of elements depends on the installed
252  * ladspa/libvisual plugins at the time. This is also up to the
253  * distribution to handle (but usually not relevant for playback
254  * applications).
255  */
256 
257 #ifdef HAVE_CONFIG_H
258 #include "config.h"
259 #endif
260 
261 #include "install-plugins.h"
262 
263 #include <gst/gstinfo.h>
264 
265 #ifdef HAVE_SYS_TYPES_H
266 #include <sys/types.h>
267 #endif
268 
269 #ifdef HAVE_SYS_WAIT_H
270 #include <sys/wait.h>
271 #endif
272 
273 #include <string.h>
274 
275 #ifndef GST_DISABLE_GST_DEBUG
276 #define GST_CAT_DEFAULT gst_pb_utils_install_plugins_ensure_debug_category()
277 
278 static GstDebugCategory *
gst_pb_utils_install_plugins_ensure_debug_category(void)279 gst_pb_utils_install_plugins_ensure_debug_category (void)
280 {
281   static gsize cat_gonce = 0;
282 
283   if (g_once_init_enter (&cat_gonce)) {
284     GstDebugCategory *cat = NULL;
285 
286     GST_DEBUG_CATEGORY_INIT (cat, "install-plugins", 0,
287         "GstPbUtils plugins installation helper");
288 
289     g_once_init_leave (&cat_gonce, (gsize) cat);
290   }
291 
292   return (GstDebugCategory *) cat_gonce;
293 }
294 #endif /* GST_DISABLE_GST_DEBUG */
295 
296 /* best effort to make things compile and possibly even work on win32 */
297 #ifndef WEXITSTATUS
298 # define WEXITSTATUS(status) ((((guint)(status)) & 0xff00) >> 8)
299 #endif
300 #ifndef WIFEXITED
301 # define WIFEXITED(status) ((((guint)(status)) & 0x7f) == 0)
302 #endif
303 
304 static gboolean install_in_progress;    /* FALSE */
305 
306 /* private struct */
307 struct _GstInstallPluginsContext
308 {
309   gchar *confirm_search;
310   gchar *desktop_id;
311   gchar *startup_notification_id;
312   guint xid;
313 };
314 
315 /**
316  * gst_install_plugins_context_set_confirm_search:
317  * @ctx: a #GstInstallPluginsContext
318  * @confirm_search: whether to ask for confirmation before searching for plugins
319  *
320  * This function is used to tell the external installer process whether it
321  * should ask for confirmation or not before searching for missing plugins.
322  *
323  * If set, this option will be passed to the installer via a
324  * --interaction=[show-confirm-search|hide-confirm-search] command line option.
325  *
326  * Since: 1.6
327  */
328 void
gst_install_plugins_context_set_confirm_search(GstInstallPluginsContext * ctx,gboolean confirm_search)329 gst_install_plugins_context_set_confirm_search (GstInstallPluginsContext * ctx,
330     gboolean confirm_search)
331 {
332   g_return_if_fail (ctx != NULL);
333 
334   if (confirm_search)
335     ctx->confirm_search = g_strdup ("show-confirm-search");
336   else
337     ctx->confirm_search = g_strdup ("hide-confirm-search");
338 }
339 
340 /**
341  * gst_install_plugins_context_set_desktop_id:
342  * @ctx: a #GstInstallPluginsContext
343  * @desktop_id: the desktop file ID of the calling application
344  *
345  * This function is used to pass the calling application's desktop file ID to
346  * the external installer process.
347  *
348  * A desktop file ID is the basename of the desktop file, including the
349  * .desktop extension.
350  *
351  * If set, the desktop file ID will be passed to the installer via a
352  * --desktop-id= command line option.
353  *
354  * Since: 1.6
355  */
356 void
gst_install_plugins_context_set_desktop_id(GstInstallPluginsContext * ctx,const gchar * desktop_id)357 gst_install_plugins_context_set_desktop_id (GstInstallPluginsContext * ctx,
358     const gchar * desktop_id)
359 {
360   g_return_if_fail (ctx != NULL);
361 
362   ctx->desktop_id = g_strdup (desktop_id);
363 }
364 
365 /**
366  * gst_install_plugins_context_set_startup_notification_id:
367  * @ctx: a #GstInstallPluginsContext
368  * @startup_id: the startup notification ID
369  *
370  * Sets the startup notification ID for the launched process.
371  *
372  * This is typically used to to pass the current X11 event timestamp to the
373  * external installer process.
374  *
375  * Startup notification IDs are defined in the
376  * [FreeDesktop.Org Startup Notifications standard](http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt).
377  *
378  * If set, the ID will be passed to the installer via a
379  * --startup-notification-id= command line option.
380  *
381  * GTK+/GNOME applications should be able to create a startup notification ID
382  * like this:
383  * |[
384  *   timestamp = gtk_get_current_event_time ();
385  *   startup_id = g_strdup_printf ("_TIME%u", timestamp);
386  * ...
387  * ]|
388  *
389  * Since: 1.6
390  */
gst_install_plugins_context_set_startup_notification_id(GstInstallPluginsContext * ctx,const gchar * startup_id)391 void gst_install_plugins_context_set_startup_notification_id
392     (GstInstallPluginsContext * ctx, const gchar * startup_id)
393 {
394   g_return_if_fail (ctx != NULL);
395 
396   ctx->startup_notification_id = g_strdup (startup_id);
397 }
398 
399 /**
400  * gst_install_plugins_context_set_xid:
401  * @ctx: a #GstInstallPluginsContext
402  * @xid: the XWindow ID (XID) of the top-level application
403  *
404  * This function is for X11-based applications (such as most Gtk/Qt
405  * applications on linux/unix) only. You can use it to tell the external
406  * installer the XID of your main application window. That way the installer
407  * can make its own window transient to your application window during the
408  * installation.
409  *
410  * If set, the XID will be passed to the installer via a --transient-for=XID
411  * command line option.
412  *
413  * Gtk+/Gnome application should be able to obtain the XID of the top-level
414  * window like this:
415  * |[
416  * ##include <gtk/gtk.h>
417  * ##ifdef GDK_WINDOWING_X11
418  * ##include <gdk/gdkx.h>
419  * ##endif
420  * ...
421  * ##ifdef GDK_WINDOWING_X11
422  *   xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (application_window)->window);
423  * ##endif
424  * ...
425  * ]|
426  *
427  */
428 void
gst_install_plugins_context_set_xid(GstInstallPluginsContext * ctx,guint xid)429 gst_install_plugins_context_set_xid (GstInstallPluginsContext * ctx, guint xid)
430 {
431   g_return_if_fail (ctx != NULL);
432 
433   ctx->xid = xid;
434 }
435 
436 /**
437  * gst_install_plugins_context_new:
438  *
439  * Creates a new #GstInstallPluginsContext.
440  *
441  * Returns: a new #GstInstallPluginsContext. Free with
442  * gst_install_plugins_context_free() when no longer needed
443  */
444 GstInstallPluginsContext *
gst_install_plugins_context_new(void)445 gst_install_plugins_context_new (void)
446 {
447   return g_new0 (GstInstallPluginsContext, 1);
448 }
449 
450 /**
451  * gst_install_plugins_context_free:
452  * @ctx: a #GstInstallPluginsContext
453  *
454  * Frees a #GstInstallPluginsContext.
455  */
456 void
gst_install_plugins_context_free(GstInstallPluginsContext * ctx)457 gst_install_plugins_context_free (GstInstallPluginsContext * ctx)
458 {
459   g_return_if_fail (ctx != NULL);
460 
461   g_free (ctx->confirm_search);
462   g_free (ctx->desktop_id);
463   g_free (ctx->startup_notification_id);
464   g_free (ctx);
465 }
466 
467 /**
468  * gst_install_plugins_context_copy:
469  * @ctx: a #GstInstallPluginsContext
470  *
471  * Copies a #GstInstallPluginsContext.
472  *
473  * Returns: (transfer full): A copy of @ctx
474  *
475  * Since: 1.12.1
476  */
477 GstInstallPluginsContext *
gst_install_plugins_context_copy(GstInstallPluginsContext * ctx)478 gst_install_plugins_context_copy (GstInstallPluginsContext * ctx)
479 {
480   GstInstallPluginsContext *ret;
481 
482   ret = gst_install_plugins_context_new ();
483   ret->confirm_search = g_strdup (ctx->confirm_search);
484   ret->desktop_id = g_strdup (ctx->desktop_id);
485   ret->startup_notification_id = g_strdup (ctx->startup_notification_id);
486   ret->xid = ctx->xid;
487 
488   return ret;
489 }
490 
491 G_DEFINE_BOXED_TYPE (GstInstallPluginsContext, gst_install_plugins_context,
492     (GBoxedCopyFunc) gst_install_plugins_context_copy,
493     (GBoxedFreeFunc) gst_install_plugins_context_free);
494 
495 static const gchar *
gst_install_plugins_get_helper(void)496 gst_install_plugins_get_helper (void)
497 {
498   const gchar *helper;
499 
500   helper = g_getenv ("GST_INSTALL_PLUGINS_HELPER");
501   if (helper == NULL)
502     helper = GST_INSTALL_PLUGINS_HELPER;
503 
504   GST_LOG ("Using plugin install helper '%s'", helper);
505   return helper;
506 }
507 
508 static gboolean
ptr_array_contains_string(GPtrArray * arr,const gchar * s)509 ptr_array_contains_string (GPtrArray * arr, const gchar * s)
510 {
511   gint i;
512 
513   for (i = 0; i < arr->len; ++i) {
514     if (strcmp ((const char *) g_ptr_array_index (arr, i), s) == 0)
515       return TRUE;
516   }
517   return FALSE;
518 }
519 
520 static gboolean
gst_install_plugins_spawn_child(const gchar * const * details,GstInstallPluginsContext * ctx,GPid * child_pid,gint * exit_status)521 gst_install_plugins_spawn_child (const gchar * const *details,
522     GstInstallPluginsContext * ctx, GPid * child_pid, gint * exit_status)
523 {
524   GPtrArray *arr;
525   gboolean ret;
526   GError *err = NULL;
527   gchar **argv;
528 
529   arr = g_ptr_array_new_with_free_func (g_free);
530 
531   /* argv[0] = helper path */
532   g_ptr_array_add (arr, g_strdup (gst_install_plugins_get_helper ()));
533 
534   /* add any additional command line args from the context */
535   if (ctx != NULL && ctx->confirm_search) {
536     g_ptr_array_add (arr, g_strdup_printf ("--interaction=%s",
537             ctx->confirm_search));
538   }
539   if (ctx != NULL && ctx->desktop_id != NULL) {
540     g_ptr_array_add (arr, g_strdup_printf ("--desktop-id=%s", ctx->desktop_id));
541   }
542   if (ctx != NULL && ctx->startup_notification_id != NULL) {
543     g_ptr_array_add (arr, g_strdup_printf ("--startup-notification-id=%s",
544             ctx->startup_notification_id));
545   }
546   if (ctx != NULL && ctx->xid != 0) {
547     g_ptr_array_add (arr, g_strdup_printf ("--transient-for=%u", ctx->xid));
548   }
549 
550   /* finally, add the detail strings, but without duplicates */
551   while (details != NULL && details[0] != NULL) {
552     if (!ptr_array_contains_string (arr, details[0]))
553       g_ptr_array_add (arr, g_strdup (details[0]));
554     ++details;
555   }
556 
557   /* and NULL-terminate */
558   g_ptr_array_add (arr, NULL);
559 
560   argv = (gchar **) arr->pdata;
561 
562   if (child_pid == NULL && exit_status != NULL) {
563     install_in_progress = TRUE;
564     ret = g_spawn_sync (NULL, argv, NULL, (GSpawnFlags) 0, NULL, NULL,
565         NULL, NULL, exit_status, &err);
566     install_in_progress = FALSE;
567   } else if (child_pid != NULL && exit_status == NULL) {
568     install_in_progress = TRUE;
569     ret = g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL,
570         NULL, child_pid, &err);
571   } else {
572     g_return_val_if_reached (FALSE);
573   }
574 
575   if (!ret) {
576     GST_ERROR ("Error spawning plugin install helper: %s", err->message);
577     g_error_free (err);
578   }
579 
580   g_ptr_array_unref (arr);
581   return ret;
582 }
583 
584 static GstInstallPluginsReturn
gst_install_plugins_return_from_status(gint status)585 gst_install_plugins_return_from_status (gint status)
586 {
587   GstInstallPluginsReturn ret;
588 
589   /* did we exit cleanly? */
590   if (!WIFEXITED (status)) {
591     ret = GST_INSTALL_PLUGINS_CRASHED;
592   } else {
593     ret = (GstInstallPluginsReturn) WEXITSTATUS (status);
594 
595     /* did the helper return an invalid status code? */
596     if (((guint) ret) >= GST_INSTALL_PLUGINS_STARTED_OK &&
597         ret != GST_INSTALL_PLUGINS_INTERNAL_FAILURE) {
598       ret = GST_INSTALL_PLUGINS_INVALID;
599     }
600   }
601 
602   GST_LOG ("plugin installer exited with status 0x%04x = %s", status,
603       gst_install_plugins_return_get_name (ret));
604 
605   return ret;
606 }
607 
608 typedef struct
609 {
610   GstInstallPluginsResultFunc func;
611   gpointer user_data;
612 } GstInstallPluginsAsyncHelper;
613 
614 static void
gst_install_plugins_installer_exited(GPid pid,gint status,gpointer data)615 gst_install_plugins_installer_exited (GPid pid, gint status, gpointer data)
616 {
617   GstInstallPluginsAsyncHelper *helper;
618   GstInstallPluginsReturn ret;
619 
620   install_in_progress = FALSE;
621 
622   helper = (GstInstallPluginsAsyncHelper *) data;
623   ret = gst_install_plugins_return_from_status (status);
624 
625   GST_LOG ("calling plugin install result function %p", helper->func);
626   helper->func (ret, helper->user_data);
627 
628   g_free (helper);
629 }
630 
631 /**
632  * gst_install_plugins_async:
633  * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
634  *     of installer string details (see below)
635  * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
636  * @func: (scope async): the function to call when the installer program returns
637  * @user_data: (closure): the user data to pass to @func when called, or NULL
638  *
639  * Requests plugin installation without blocking. Once the plugins have been
640  * installed or installation has failed, @func will be called with the result
641  * of the installation and your provided @user_data pointer.
642  *
643  * This function requires a running GLib/Gtk main loop. If you are not
644  * running a GLib/Gtk main loop, make sure to regularly call
645  * g_main_context_iteration(NULL,FALSE).
646  *
647  * The installer strings that make up @detail are typically obtained by
648  * calling gst_missing_plugin_message_get_installer_detail() on missing-plugin
649  * messages that have been caught on a pipeline's bus or created by the
650  * application via the provided API, such as gst_missing_element_message_new().
651  *
652  * It is possible to request the installation of multiple missing plugins in
653  * one go (as might be required if there is a demuxer for a certain format
654  * installed but no suitable video decoder and no suitable audio decoder).
655  *
656  * Returns: result code whether an external installer could be started
657  */
658 
659 GstInstallPluginsReturn
gst_install_plugins_async(const gchar * const * details,GstInstallPluginsContext * ctx,GstInstallPluginsResultFunc func,gpointer user_data)660 gst_install_plugins_async (const gchar * const *details,
661     GstInstallPluginsContext * ctx, GstInstallPluginsResultFunc func,
662     gpointer user_data)
663 {
664   GstInstallPluginsAsyncHelper *helper;
665   GPid pid;
666 
667   g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
668   g_return_val_if_fail (func != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
669 
670   if (install_in_progress)
671     return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;
672 
673   /* if we can't access our helper, don't bother */
674   if (!g_file_test (gst_install_plugins_get_helper (),
675           G_FILE_TEST_IS_EXECUTABLE))
676     return GST_INSTALL_PLUGINS_HELPER_MISSING;
677 
678   if (!gst_install_plugins_spawn_child (details, ctx, &pid, NULL))
679     return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;
680 
681   helper = g_new (GstInstallPluginsAsyncHelper, 1);
682   helper->func = func;
683   helper->user_data = user_data;
684 
685   g_child_watch_add (pid, gst_install_plugins_installer_exited, helper);
686 
687   return GST_INSTALL_PLUGINS_STARTED_OK;
688 }
689 
690 /**
691  * gst_install_plugins_sync:
692  * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
693  *     of installer string details
694  * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
695  *
696  * Requests plugin installation and block until the plugins have been
697  * installed or installation has failed.
698  *
699  * This function should almost never be used, it only exists for cases where
700  * a non-GLib main loop is running and the user wants to run it in a separate
701  * thread and marshal the result back asynchronously into the main thread
702  * using the other non-GLib main loop. You should almost always use
703  * gst_install_plugins_async() instead of this function.
704  *
705  * Returns: the result of the installation.
706  */
707 GstInstallPluginsReturn
gst_install_plugins_sync(const gchar * const * details,GstInstallPluginsContext * ctx)708 gst_install_plugins_sync (const gchar * const *details,
709     GstInstallPluginsContext * ctx)
710 {
711   gint status;
712 
713   g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
714 
715   if (install_in_progress)
716     return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;
717 
718   /* if we can't access our helper, don't bother */
719   if (!g_file_test (gst_install_plugins_get_helper (),
720           G_FILE_TEST_IS_EXECUTABLE))
721     return GST_INSTALL_PLUGINS_HELPER_MISSING;
722 
723   if (!gst_install_plugins_spawn_child (details, ctx, NULL, &status))
724     return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;
725 
726   return gst_install_plugins_return_from_status (status);
727 }
728 
729 /**
730  * gst_install_plugins_return_get_name:
731  * @ret: the return status code
732  *
733  * Convenience function to return the descriptive string associated
734  * with a status code.  This function returns English strings and
735  * should not be used for user messages. It is here only to assist
736  * in debugging.
737  *
738  * Returns: a descriptive string for the status code in @ret
739  */
740 const gchar *
gst_install_plugins_return_get_name(GstInstallPluginsReturn ret)741 gst_install_plugins_return_get_name (GstInstallPluginsReturn ret)
742 {
743   switch (ret) {
744     case GST_INSTALL_PLUGINS_SUCCESS:
745       return "success";
746     case GST_INSTALL_PLUGINS_NOT_FOUND:
747       return "not-found";
748     case GST_INSTALL_PLUGINS_ERROR:
749       return "install-error";
750     case GST_INSTALL_PLUGINS_CRASHED:
751       return "installer-exit-unclean";
752     case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS:
753       return "partial-success";
754     case GST_INSTALL_PLUGINS_USER_ABORT:
755       return "user-abort";
756     case GST_INSTALL_PLUGINS_STARTED_OK:
757       return "started-ok";
758     case GST_INSTALL_PLUGINS_INTERNAL_FAILURE:
759       return "internal-failure";
760     case GST_INSTALL_PLUGINS_HELPER_MISSING:
761       return "helper-missing";
762     case GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS:
763       return "install-in-progress";
764     case GST_INSTALL_PLUGINS_INVALID:
765       return "invalid";
766     default:
767       break;
768   }
769   return "(UNKNOWN)";
770 }
771 
772 /**
773  * gst_install_plugins_installation_in_progress:
774  *
775  * Checks whether plugin installation (initiated by this application only)
776  * is currently in progress.
777  *
778  * Returns: TRUE if plugin installation is in progress, otherwise FALSE
779  */
780 gboolean
gst_install_plugins_installation_in_progress(void)781 gst_install_plugins_installation_in_progress (void)
782 {
783   return install_in_progress;
784 }
785 
786 /**
787  * gst_install_plugins_supported:
788  *
789  * Checks whether plugin installation is likely to be supported by the
790  * current environment. This currently only checks whether the helper script
791  * that is to be provided by the distribution or operating system vendor
792  * exists.
793  *
794  * Returns: TRUE if plugin installation is likely to be supported.
795  */
796 gboolean
gst_install_plugins_supported(void)797 gst_install_plugins_supported (void)
798 {
799   return g_file_test (gst_install_plugins_get_helper (),
800       G_FILE_TEST_IS_EXECUTABLE);
801 }
802