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 `./configure` script 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 `./configure`
118 * option `--with-install-plugins-helper=/path/to/installer` to make
119 * GStreamer call an 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 overriden 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 occured 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 /* best effort to make things compile and possibly even work on win32 */
276 #ifndef WEXITSTATUS
277 # define WEXITSTATUS(status) ((((guint)(status)) & 0xff00) >> 8)
278 #endif
279 #ifndef WIFEXITED
280 # define WIFEXITED(status) ((((guint)(status)) & 0x7f) == 0)
281 #endif
282
283 static gboolean install_in_progress; /* FALSE */
284
285 /* private struct */
286 struct _GstInstallPluginsContext
287 {
288 gchar *confirm_search;
289 gchar *desktop_id;
290 gchar *startup_notification_id;
291 guint xid;
292 };
293
294 /**
295 * gst_install_plugins_context_set_confirm_search:
296 * @ctx: a #GstInstallPluginsContext
297 * @confirm_search: whether to ask for confirmation before searching for plugins
298 *
299 * This function is used to tell the external installer process whether it
300 * should ask for confirmation or not before searching for missing plugins.
301 *
302 * If set, this option will be passed to the installer via a
303 * --interaction=[show-confirm-search|hide-confirm-search] command line option.
304 *
305 * Since: 1.6
306 */
307 void
gst_install_plugins_context_set_confirm_search(GstInstallPluginsContext * ctx,gboolean confirm_search)308 gst_install_plugins_context_set_confirm_search (GstInstallPluginsContext * ctx,
309 gboolean confirm_search)
310 {
311 g_return_if_fail (ctx != NULL);
312
313 if (confirm_search)
314 ctx->confirm_search = g_strdup ("show-confirm-search");
315 else
316 ctx->confirm_search = g_strdup ("hide-confirm-search");
317 }
318
319 /**
320 * gst_install_plugins_context_set_desktop_id:
321 * @ctx: a #GstInstallPluginsContext
322 * @desktop_id: the desktop file ID of the calling application
323 *
324 * This function is used to pass the calling application's desktop file ID to
325 * the external installer process.
326 *
327 * A desktop file ID is the basename of the desktop file, including the
328 * .desktop extension.
329 *
330 * If set, the desktop file ID will be passed to the installer via a
331 * --desktop-id= command line option.
332 *
333 * Since: 1.6
334 */
335 void
gst_install_plugins_context_set_desktop_id(GstInstallPluginsContext * ctx,const gchar * desktop_id)336 gst_install_plugins_context_set_desktop_id (GstInstallPluginsContext * ctx,
337 const gchar * desktop_id)
338 {
339 g_return_if_fail (ctx != NULL);
340
341 ctx->desktop_id = g_strdup (desktop_id);
342 }
343
344 /**
345 * gst_install_plugins_context_set_startup_notification_id:
346 * @ctx: a #GstInstallPluginsContext
347 * @startup_id: the startup notification ID
348 *
349 * Sets the startup notification ID for the launched process.
350 *
351 * This is typically used to to pass the current X11 event timestamp to the
352 * external installer process.
353 *
354 * Startup notification IDs are defined in the
355 * [FreeDesktop.Org Startup Notifications standard](http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt).
356 *
357 * If set, the ID will be passed to the installer via a
358 * --startup-notification-id= command line option.
359 *
360 * GTK+/GNOME applications should be able to create a startup notification ID
361 * like this:
362 * |[
363 * timestamp = gtk_get_current_event_time ();
364 * startup_id = g_strdup_printf ("_TIME%u", timestamp);
365 * ...
366 * ]|
367 *
368 * Since: 1.6
369 */
gst_install_plugins_context_set_startup_notification_id(GstInstallPluginsContext * ctx,const gchar * startup_id)370 void gst_install_plugins_context_set_startup_notification_id
371 (GstInstallPluginsContext * ctx, const gchar * startup_id)
372 {
373 g_return_if_fail (ctx != NULL);
374
375 ctx->startup_notification_id = g_strdup (startup_id);
376 }
377
378 /**
379 * gst_install_plugins_context_set_xid:
380 * @ctx: a #GstInstallPluginsContext
381 * @xid: the XWindow ID (XID) of the top-level application
382 *
383 * This function is for X11-based applications (such as most Gtk/Qt
384 * applications on linux/unix) only. You can use it to tell the external
385 * installer the XID of your main application window. That way the installer
386 * can make its own window transient to your application window during the
387 * installation.
388 *
389 * If set, the XID will be passed to the installer via a --transient-for=XID
390 * command line option.
391 *
392 * Gtk+/Gnome application should be able to obtain the XID of the top-level
393 * window like this:
394 * |[
395 * ##include <gtk/gtk.h>
396 * ##ifdef GDK_WINDOWING_X11
397 * ##include <gdk/gdkx.h>
398 * ##endif
399 * ...
400 * ##ifdef GDK_WINDOWING_X11
401 * xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (application_window)->window);
402 * ##endif
403 * ...
404 * ]|
405 *
406 */
407 void
gst_install_plugins_context_set_xid(GstInstallPluginsContext * ctx,guint xid)408 gst_install_plugins_context_set_xid (GstInstallPluginsContext * ctx, guint xid)
409 {
410 g_return_if_fail (ctx != NULL);
411
412 ctx->xid = xid;
413 }
414
415 /**
416 * gst_install_plugins_context_new:
417 *
418 * Creates a new #GstInstallPluginsContext.
419 *
420 * Returns: a new #GstInstallPluginsContext. Free with
421 * gst_install_plugins_context_free() when no longer needed
422 */
423 GstInstallPluginsContext *
gst_install_plugins_context_new(void)424 gst_install_plugins_context_new (void)
425 {
426 return g_new0 (GstInstallPluginsContext, 1);
427 }
428
429 /**
430 * gst_install_plugins_context_free:
431 * @ctx: a #GstInstallPluginsContext
432 *
433 * Frees a #GstInstallPluginsContext.
434 */
435 void
gst_install_plugins_context_free(GstInstallPluginsContext * ctx)436 gst_install_plugins_context_free (GstInstallPluginsContext * ctx)
437 {
438 g_return_if_fail (ctx != NULL);
439
440 g_free (ctx->confirm_search);
441 g_free (ctx->desktop_id);
442 g_free (ctx->startup_notification_id);
443 g_free (ctx);
444 }
445
446 GstInstallPluginsContext *
gst_install_plugins_context_copy(GstInstallPluginsContext * ctx)447 gst_install_plugins_context_copy (GstInstallPluginsContext * ctx)
448 {
449 GstInstallPluginsContext *ret;
450
451 ret = gst_install_plugins_context_new ();
452 ret->confirm_search = g_strdup (ctx->confirm_search);
453 ret->desktop_id = g_strdup (ctx->desktop_id);
454 ret->startup_notification_id = g_strdup (ctx->startup_notification_id);
455 ret->xid = ctx->xid;
456
457 return ret;
458 }
459
460 G_DEFINE_BOXED_TYPE (GstInstallPluginsContext, gst_install_plugins_context,
461 (GBoxedCopyFunc) gst_install_plugins_context_copy,
462 (GBoxedFreeFunc) gst_install_plugins_context_free);
463
464 static const gchar *
gst_install_plugins_get_helper(void)465 gst_install_plugins_get_helper (void)
466 {
467 const gchar *helper;
468
469 helper = g_getenv ("GST_INSTALL_PLUGINS_HELPER");
470 if (helper == NULL)
471 helper = GST_INSTALL_PLUGINS_HELPER;
472
473 GST_LOG ("Using plugin install helper '%s'", helper);
474 return helper;
475 }
476
477 static gboolean
ptr_array_contains_string(GPtrArray * arr,const gchar * s)478 ptr_array_contains_string (GPtrArray * arr, const gchar * s)
479 {
480 gint i;
481
482 for (i = 0; i < arr->len; ++i) {
483 if (strcmp ((const char *) g_ptr_array_index (arr, i), s) == 0)
484 return TRUE;
485 }
486 return FALSE;
487 }
488
489 static gboolean
gst_install_plugins_spawn_child(const gchar * const * details,GstInstallPluginsContext * ctx,GPid * child_pid,gint * exit_status)490 gst_install_plugins_spawn_child (const gchar * const *details,
491 GstInstallPluginsContext * ctx, GPid * child_pid, gint * exit_status)
492 {
493 GPtrArray *arr;
494 gboolean ret;
495 GError *err = NULL;
496 gchar **argv;
497
498 arr = g_ptr_array_new_with_free_func (g_free);
499
500 /* argv[0] = helper path */
501 g_ptr_array_add (arr, g_strdup (gst_install_plugins_get_helper ()));
502
503 /* add any additional command line args from the context */
504 if (ctx != NULL && ctx->confirm_search) {
505 g_ptr_array_add (arr, g_strdup_printf ("--interaction=%s",
506 ctx->confirm_search));
507 }
508 if (ctx != NULL && ctx->desktop_id != NULL) {
509 g_ptr_array_add (arr, g_strdup_printf ("--desktop-id=%s", ctx->desktop_id));
510 }
511 if (ctx != NULL && ctx->startup_notification_id != NULL) {
512 g_ptr_array_add (arr, g_strdup_printf ("--startup-notification-id=%s",
513 ctx->startup_notification_id));
514 }
515 if (ctx != NULL && ctx->xid != 0) {
516 g_ptr_array_add (arr, g_strdup_printf ("--transient-for=%u", ctx->xid));
517 }
518
519 /* finally, add the detail strings, but without duplicates */
520 while (details != NULL && details[0] != NULL) {
521 if (!ptr_array_contains_string (arr, details[0]))
522 g_ptr_array_add (arr, g_strdup (details[0]));
523 ++details;
524 }
525
526 /* and NULL-terminate */
527 g_ptr_array_add (arr, NULL);
528
529 argv = (gchar **) arr->pdata;
530
531 if (child_pid == NULL && exit_status != NULL) {
532 install_in_progress = TRUE;
533 ret = g_spawn_sync (NULL, argv, NULL, (GSpawnFlags) 0, NULL, NULL,
534 NULL, NULL, exit_status, &err);
535 install_in_progress = FALSE;
536 } else if (child_pid != NULL && exit_status == NULL) {
537 install_in_progress = TRUE;
538 ret = g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL,
539 NULL, child_pid, &err);
540 } else {
541 g_return_val_if_reached (FALSE);
542 }
543
544 if (!ret) {
545 GST_ERROR ("Error spawning plugin install helper: %s", err->message);
546 g_error_free (err);
547 }
548
549 g_ptr_array_unref (arr);
550 return ret;
551 }
552
553 static GstInstallPluginsReturn
gst_install_plugins_return_from_status(gint status)554 gst_install_plugins_return_from_status (gint status)
555 {
556 GstInstallPluginsReturn ret;
557
558 /* did we exit cleanly? */
559 if (!WIFEXITED (status)) {
560 ret = GST_INSTALL_PLUGINS_CRASHED;
561 } else {
562 ret = (GstInstallPluginsReturn) WEXITSTATUS (status);
563
564 /* did the helper return an invalid status code? */
565 if (((guint) ret) >= GST_INSTALL_PLUGINS_STARTED_OK &&
566 ret != GST_INSTALL_PLUGINS_INTERNAL_FAILURE) {
567 ret = GST_INSTALL_PLUGINS_INVALID;
568 }
569 }
570
571 GST_LOG ("plugin installer exited with status 0x%04x = %s", status,
572 gst_install_plugins_return_get_name (ret));
573
574 return ret;
575 }
576
577 typedef struct
578 {
579 GstInstallPluginsResultFunc func;
580 gpointer user_data;
581 } GstInstallPluginsAsyncHelper;
582
583 static void
gst_install_plugins_installer_exited(GPid pid,gint status,gpointer data)584 gst_install_plugins_installer_exited (GPid pid, gint status, gpointer data)
585 {
586 GstInstallPluginsAsyncHelper *helper;
587 GstInstallPluginsReturn ret;
588
589 install_in_progress = FALSE;
590
591 helper = (GstInstallPluginsAsyncHelper *) data;
592 ret = gst_install_plugins_return_from_status (status);
593
594 GST_LOG ("calling plugin install result function %p", helper->func);
595 helper->func (ret, helper->user_data);
596
597 g_free (helper);
598 }
599
600 /**
601 * gst_install_plugins_async:
602 * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
603 * of installer string details (see below)
604 * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
605 * @func: (scope async): the function to call when the installer program returns
606 * @user_data: (closure): the user data to pass to @func when called, or NULL
607 *
608 * Requests plugin installation without blocking. Once the plugins have been
609 * installed or installation has failed, @func will be called with the result
610 * of the installation and your provided @user_data pointer.
611 *
612 * This function requires a running GLib/Gtk main loop. If you are not
613 * running a GLib/Gtk main loop, make sure to regularly call
614 * g_main_context_iteration(NULL,FALSE).
615 *
616 * The installer strings that make up @detail are typically obtained by
617 * calling gst_missing_plugin_message_get_installer_detail() on missing-plugin
618 * messages that have been caught on a pipeline's bus or created by the
619 * application via the provided API, such as gst_missing_element_message_new().
620 *
621 * It is possible to request the installation of multiple missing plugins in
622 * one go (as might be required if there is a demuxer for a certain format
623 * installed but no suitable video decoder and no suitable audio decoder).
624 *
625 * Returns: result code whether an external installer could be started
626 */
627
628 GstInstallPluginsReturn
gst_install_plugins_async(const gchar * const * details,GstInstallPluginsContext * ctx,GstInstallPluginsResultFunc func,gpointer user_data)629 gst_install_plugins_async (const gchar * const *details,
630 GstInstallPluginsContext * ctx, GstInstallPluginsResultFunc func,
631 gpointer user_data)
632 {
633 GstInstallPluginsAsyncHelper *helper;
634 GPid pid;
635
636 g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
637 g_return_val_if_fail (func != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
638
639 if (install_in_progress)
640 return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;
641
642 /* if we can't access our helper, don't bother */
643 if (!g_file_test (gst_install_plugins_get_helper (),
644 G_FILE_TEST_IS_EXECUTABLE))
645 return GST_INSTALL_PLUGINS_HELPER_MISSING;
646
647 if (!gst_install_plugins_spawn_child (details, ctx, &pid, NULL))
648 return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;
649
650 helper = g_new (GstInstallPluginsAsyncHelper, 1);
651 helper->func = func;
652 helper->user_data = user_data;
653
654 g_child_watch_add (pid, gst_install_plugins_installer_exited, helper);
655
656 return GST_INSTALL_PLUGINS_STARTED_OK;
657 }
658
659 /**
660 * gst_install_plugins_sync:
661 * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
662 * of installer string details
663 * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
664 *
665 * Requests plugin installation and block until the plugins have been
666 * installed or installation has failed.
667 *
668 * This function should almost never be used, it only exists for cases where
669 * a non-GLib main loop is running and the user wants to run it in a separate
670 * thread and marshal the result back asynchronously into the main thread
671 * using the other non-GLib main loop. You should almost always use
672 * gst_install_plugins_async() instead of this function.
673 *
674 * Returns: the result of the installation.
675 */
676 GstInstallPluginsReturn
gst_install_plugins_sync(const gchar * const * details,GstInstallPluginsContext * ctx)677 gst_install_plugins_sync (const gchar * const *details,
678 GstInstallPluginsContext * ctx)
679 {
680 gint status;
681
682 g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
683
684 if (install_in_progress)
685 return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;
686
687 /* if we can't access our helper, don't bother */
688 if (!g_file_test (gst_install_plugins_get_helper (),
689 G_FILE_TEST_IS_EXECUTABLE))
690 return GST_INSTALL_PLUGINS_HELPER_MISSING;
691
692 if (!gst_install_plugins_spawn_child (details, ctx, NULL, &status))
693 return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;
694
695 return gst_install_plugins_return_from_status (status);
696 }
697
698 /**
699 * gst_install_plugins_return_get_name:
700 * @ret: the return status code
701 *
702 * Convenience function to return the descriptive string associated
703 * with a status code. This function returns English strings and
704 * should not be used for user messages. It is here only to assist
705 * in debugging.
706 *
707 * Returns: a descriptive string for the status code in @ret
708 */
709 const gchar *
gst_install_plugins_return_get_name(GstInstallPluginsReturn ret)710 gst_install_plugins_return_get_name (GstInstallPluginsReturn ret)
711 {
712 switch (ret) {
713 case GST_INSTALL_PLUGINS_SUCCESS:
714 return "success";
715 case GST_INSTALL_PLUGINS_NOT_FOUND:
716 return "not-found";
717 case GST_INSTALL_PLUGINS_ERROR:
718 return "install-error";
719 case GST_INSTALL_PLUGINS_CRASHED:
720 return "installer-exit-unclean";
721 case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS:
722 return "partial-success";
723 case GST_INSTALL_PLUGINS_USER_ABORT:
724 return "user-abort";
725 case GST_INSTALL_PLUGINS_STARTED_OK:
726 return "started-ok";
727 case GST_INSTALL_PLUGINS_INTERNAL_FAILURE:
728 return "internal-failure";
729 case GST_INSTALL_PLUGINS_HELPER_MISSING:
730 return "helper-missing";
731 case GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS:
732 return "install-in-progress";
733 case GST_INSTALL_PLUGINS_INVALID:
734 return "invalid";
735 default:
736 break;
737 }
738 return "(UNKNOWN)";
739 }
740
741 /**
742 * gst_install_plugins_installation_in_progress:
743 *
744 * Checks whether plugin installation (initiated by this application only)
745 * is currently in progress.
746 *
747 * Returns: TRUE if plugin installation is in progress, otherwise FALSE
748 */
749 gboolean
gst_install_plugins_installation_in_progress(void)750 gst_install_plugins_installation_in_progress (void)
751 {
752 return install_in_progress;
753 }
754
755 /**
756 * gst_install_plugins_supported:
757 *
758 * Checks whether plugin installation is likely to be supported by the
759 * current environment. This currently only checks whether the helper script
760 * that is to be provided by the distribution or operating system vendor
761 * exists.
762 *
763 * Returns: TRUE if plugin installation is likely to be supported.
764 */
765 gboolean
gst_install_plugins_supported(void)766 gst_install_plugins_supported (void)
767 {
768 return g_file_test (gst_install_plugins_get_helper (),
769 G_FILE_TEST_IS_EXECUTABLE);
770 }
771