macos: Add wrapper API to run a NSApplication in the main thread

On macOS, a Cocoa event loop is needed in the main thread to ensure
things like opening a GL window work correctly. In the past, this was
patched into glib via Cerbero, but that prevented us from updating it.
This workaround simply runs an NSApplication and then calls the
main function on a secondary thread, allowing GStreamer to correctly
display windows and/or system permission prompts, for example.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3532>
This commit is contained in:
Piotr Brzeziński 2022-12-13 18:42:11 +01:00 committed by GStreamer Marge Bot
parent 9ca6b1196e
commit 3bb8700577
24 changed files with 458 additions and 21 deletions

View file

@ -1,7 +1,11 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
@ -38,3 +42,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,6 +1,10 @@
#include <gst/gst.h>
#include <string.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
typedef struct _CustomData
{
gboolean is_live;
@ -59,7 +63,7 @@ cb_message (GstBus * bus, GstMessage * msg, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
@ -106,3 +110,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -2,6 +2,10 @@
#include <stdio.h>
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
typedef struct _CustomData
{
GstElement *pipeline;
@ -103,7 +107,7 @@ handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstStateChangeReturn ret;
@ -160,3 +164,13 @@ main (int argc, char *argv[])
gst_object_unref (data.pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,5 +1,9 @@
#include <clutter-gst/clutter-gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Setup the video texture once its size is known */
void
size_change (ClutterActor * texture, gint width, gint height,
@ -42,7 +46,7 @@ size_change (ClutterActor * texture, gint width, gint height,
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *sink;
ClutterTimeline *timeline;
@ -106,3 +110,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,7 +1,11 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink;
GstBus *bus;
@ -80,3 +84,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,5 +1,9 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData
{
@ -15,7 +19,7 @@ static void pad_added_handler (GstElement * src, GstPad * pad,
CustomData * data);
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
@ -166,3 +170,13 @@ exit:
/* Unreference the sink pad */
gst_object_unref (sink_pad);
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,5 +1,9 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData
{
@ -15,7 +19,7 @@ typedef struct _CustomData
static void handle_message (CustomData * data, GstMessage * msg);
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
@ -170,3 +174,13 @@ handle_message (CustomData * data, GstMessage * msg)
}
gst_message_unref (msg);
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -4,6 +4,10 @@
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#include <gdk/gdk.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
@ -372,7 +376,7 @@ application_cb (GstBus * bus, GstMessage * msg, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstStateChangeReturn ret;
@ -443,3 +447,13 @@ main (int argc, char *argv[])
gst_object_unref (data.playbin);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,5 +1,9 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Functions below print the Capabilities in a human-friendly format */
static gboolean
print_field (GQuark field, const GValue * value, gpointer pfx)
@ -110,7 +114,7 @@ print_pad_capabilities (GstElement * element, gchar * pad_name)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink;
GstElementFactory *source_factory, *sink_factory;
@ -222,3 +226,13 @@ main (int argc, char *argv[])
gst_object_unref (sink_factory);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,7 +1,11 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert,
*audio_resample, *audio_sink;
@ -96,3 +100,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -2,6 +2,10 @@
#include <gst/audio/audio.h>
#include <string.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */
@ -134,7 +138,7 @@ error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad;
@ -268,3 +272,13 @@ main (int argc, char *argv[])
gst_object_unref (data.pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -2,6 +2,10 @@
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData
{
@ -181,7 +185,7 @@ on_finished_cb (GstDiscoverer * discoverer, CustomData * data)
}
int
main (int argc, char **argv)
tutorial_main (int argc, char **argv)
{
CustomData data;
GError *err = NULL;
@ -238,3 +242,13 @@ main (int argc, char **argv)
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,6 +1,10 @@
#include <stdio.h>
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData
{
@ -32,7 +36,7 @@ static gboolean handle_keyboard (GIOChannel * source, GIOCondition cond,
CustomData * data);
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
@ -240,3 +244,13 @@ handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
g_free (str);
return TRUE;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,6 +1,10 @@
#include <stdio.h>
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData
{
@ -32,7 +36,7 @@ static gboolean handle_keyboard (GIOChannel * source, GIOCondition cond,
CustomData * data);
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
@ -244,3 +248,13 @@ handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
g_free (str);
return TRUE;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -2,6 +2,10 @@
#include <gst/audio/audio.h>
#include <string.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */
@ -133,7 +137,7 @@ source_setup (GstElement * pipeline, GstElement * source, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
@ -170,3 +174,13 @@ main (int argc, char *argv[])
gst_object_unref (data.pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,6 +1,10 @@
#include <gst/gst.h>
#include <string.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#define GRAPH_LENGTH 78
/* playbin flags */
@ -122,7 +126,7 @@ refresh_ui (CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
@ -186,3 +190,13 @@ main (int argc, char *argv[])
g_print ("\n");
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -3,6 +3,10 @@
#include <gst/gst.h>
#include <gst/video/colorbalance.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
typedef struct _CustomData
{
GstElement *pipeline;
@ -109,7 +113,7 @@ handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstStateChangeReturn ret;
@ -163,3 +167,13 @@ main (int argc, char *argv[])
gst_object_unref (data.pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,5 +1,9 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* playbin2 flags */
typedef enum
{
@ -22,7 +26,7 @@ filter_vis_features (GstPluginFeature * feature, gpointer data)
}
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *vis_plugin;
GstBus *bus;
@ -99,3 +103,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -1,7 +1,11 @@
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
int
main (int argc, char *argv[])
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *bin, *equalizer, *convert, *sink;
GstPad *pad, *ghost_pad;
@ -60,3 +64,13 @@ main (int argc, char *argv[])
gst_object_unref (pipeline);
return 0;
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

View file

@ -95,6 +95,13 @@
/* API compatibility stuff */
#include <gst/gstcompat.h>
#ifdef __APPLE__
# include <TargetConditionals.h>
# if TARGET_OS_MAC && !TARGET_OS_IPHONE
# include <gst/gstmacos.h>
# endif
#endif
G_BEGIN_DECLS
GST_API

View file

@ -0,0 +1,43 @@
#ifndef __GST_MACOS_H__
#define __GST_MACOS_H__
#include <gst/gstconfig.h>
#include <gst/glib-compat.h>
G_BEGIN_DECLS
/**
* GstMainFunc:
* @argc: the amount of arguments passed in @argv
* @argv: (array length=argc): an array of arguments passed to the main function
* @user_data: (nullable): user data passed to the main function
*
* Represents a simple pointer to the main() function of a program.
* It is used to pass that function along with its arguments to
* gst_macos_main(), which ensures correct behaviour of various
* GStreamer elements (e.g glimagesink) on macOS.
*/
typedef int (*GstMainFunc) (int argc, char** argv, gpointer user_data);
/**
* GstMainFuncSimple:
* @user_data: (nullable): user data passed to the main function
*
* Simplified version of #GstMainFunc, meant to be used with
* gst_macos_main_simple(), which does not require argc/argv to be passed.
*/
typedef int (*GstMainFuncSimple) (gpointer user_data);
GST_API
int gst_macos_main (GstMainFunc main_func,
int argc,
char *argv[],
gpointer user_data);
GST_API
int gst_macos_main_simple (GstMainFuncSimple main_func,
gpointer user_data);
G_END_DECLS
#endif /* __GST_MACOS_H__ */

View file

@ -0,0 +1,101 @@
#include "gstmacos.h"
#include <Cocoa/Cocoa.h>
typedef struct _ThreadArgs ThreadArgs;
struct _ThreadArgs {
void* main_func;
int argc;
char **argv;
gpointer user_data;
gboolean is_simple;
};
int
gst_thread_func (ThreadArgs *args)
{
int ret;
if (args->is_simple) {
ret = ((GstMainFuncSimple) args->main_func) (args->user_data);
} else {
ret = ((GstMainFunc) args->main_func) (args->argc, args->argv, args->user_data);
}
[NSApp terminate: nil];
return ret;
}
int
run_main_with_nsapp (ThreadArgs args)
{
GThread *gst_thread;
[NSApplication sharedApplication];
gst_thread = g_thread_new ("macos-gst-thread", (GThreadFunc) gst_thread_func, &args);
[NSApp run];
return GPOINTER_TO_INT (g_thread_join (gst_thread));
}
/**
* gst_macos_main:
* @main_func: (scope async): pointer to the main function to be called
* @argc: the amount of arguments passed in @argv
* @argv: (array length=argc): an array of arguments to be passed to the main function
* @user_data: (nullable): user data to be passed to the main function
*
* Starts an NSApplication on the main thread before calling
* the provided main() function on a secondary thread.
*
* This ensures that GStreamer can correctly perform actions
* such as creating a GL window, which require a Cocoa main loop
* to be running on the main thread.
*
* Do not call this function more than once - especially while
* another one is still running - as that will cause unpredictable
* behaviour and most likely completely fail.
*
* Returns: the return value of the provided main_func
*
* Since: 1.22
*/
int
gst_macos_main (GstMainFunc main_func, int argc, char **argv, gpointer user_data)
{
ThreadArgs args;
args.argc = argc;
args.argv = argv;
args.main_func = main_func;
args.user_data = user_data;
args.is_simple = FALSE;
return run_main_with_nsapp (args);
}
/**
* gst_macos_main_simple:
* @main_func: (scope async): pointer to the main function to be called
* @user_data: (nullable): user data to be passed to the main function
*
* Simplified variant of gst_macos_main(), meant to be used with bindings
* for languages which do not have to pass argc and argv like C does.
* See gst_macos_main() for a more detailed description.
*
* Returns: the return value of the provided main_func
*
* Since: 1.22
*/
int
gst_macos_main_simple (GstMainFuncSimple main_func, gpointer user_data)
{
ThreadArgs args;
args.argc = 0;
args.argv = NULL;
args.main_func = main_func;
args.user_data = user_data;
args.is_simple = TRUE;
return run_main_with_nsapp (args);
}

View file

@ -148,6 +148,11 @@ gst_headers = files(
'gstparse.h',
'math-compat.h',
)
if host_system == 'darwin'
gst_headers += 'gstmacos.h'
gst_sources += 'gstmacos.m'
endif
install_headers(gst_headers, subdir : 'gstreamer-1.0/gst')
extra_deps = []
@ -156,6 +161,10 @@ if host_system == 'android'
extra_deps += cc.find_library('log')
endif
if host_system == 'darwin'
extra_deps += dependency('appleframeworks', modules : ['Cocoa'])
endif
gst_registry = get_option('registry')
if gst_registry
gst_registry_sources = files('gstregistrybinary.c')

View file

@ -16,8 +16,6 @@ else
endif
gst_version_is_dev = gst_version_minor % 2 == 1 and gst_version_micro < 90
host_system = host_machine.system()
apiversion = '1.0'
soversion = 0
# maintaining compatibility with the previous libtool versioning
@ -33,6 +31,19 @@ libexecdir = get_option('libexecdir')
helpers_install_dir = join_paths(libexecdir, 'gstreamer-1.0')
cc = meson.get_compiler('c')
host_system = host_machine.system()
if host_system == 'darwin'
ios_test_code = '''#include <TargetConditionals.h>
#if ! TARGET_OS_IPHONE
#error "Not iOS/tvOS/watchOS/iPhoneSimulator"
#endif'''
if cc.compiles(ios_test_code, name : 'building for iOS')
host_system = 'ios'
endif
add_languages('objc', native: false, required: true)
endif
cdata = configuration_data()