diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3bfd22464..dd24bbfb5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -475,6 +475,7 @@ sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/hidapi/*.c"
"${SDL3_SOURCE_DIR}/src/libm/*.c"
"${SDL3_SOURCE_DIR}/src/locale/*.c"
+ "${SDL3_SOURCE_DIR}/src/main/*.c"
"${SDL3_SOURCE_DIR}/src/misc/*.c"
"${SDL3_SOURCE_DIR}/src/power/*.c"
"${SDL3_SOURCE_DIR}/src/render/*.c"
@@ -1383,6 +1384,9 @@ elseif(EMSCRIPTEN)
# project. Uncomment at will for verbose cross-compiling -I/../ path info.
sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths")
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/emscripten/*.c")
+ set(HAVE_SDL_MAIN_CALLBACKS TRUE)
+
if(SDL_MISC)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c")
set(HAVE_SDL_MISC TRUE)
@@ -2033,6 +2037,8 @@ elseif(WINDOWS)
elseif(APPLE)
# TODO: rework this all for proper macOS, iOS and Darwin support
+ # !!! FIXME: all the `if(IOS OR TVOS OR VISIONOS)` checks should get merged into one variable, so we're ready for the next platform (or just WatchOS).
+
# We always need these libs on macOS at the moment.
# !!! FIXME: we need Carbon for some very old API calls in
# !!! FIXME: src/video/cocoa/SDL_cocoakeyboard.c, but we should figure out
@@ -2044,6 +2050,12 @@ elseif(APPLE)
set(SDL_FRAMEWORK_FOUNDATION 1)
set(SDL_FRAMEWORK_COREVIDEO 1)
+ # iOS can use a CADisplayLink for main callbacks. macOS just uses the generic one atm.
+ if(IOS OR TVOS OR VISIONOS)
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ios/*.m")
+ set(HAVE_SDL_MAIN_CALLBACKS TRUE)
+ endif()
+
# Requires the darwin file implementation
if(SDL_FILE)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/file/cocoa/*.m")
@@ -2803,6 +2815,11 @@ if(NOT HAVE_SDL_TIMERS)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/dummy/*.c")
endif()
+# Most platforms use this.
+if(NOT HAVE_SDL_MAIN_CALLBACKS)
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/generic/*.c")
+endif()
+
# config variables may contain generator expression, so we need to generate SDL_build_config.h in 2 steps:
# 1. replace all `#cmakedefine`'s and `@abc@`
configure_file("${SDL3_SOURCE_DIR}/include/build_config/SDL_build_config.h.cmake"
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 70b625b37..b19bb5fba 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -418,6 +418,7 @@
+
@@ -468,6 +469,8 @@
$(IntDir)$(TargetName)_cpp.pch
$(IntDir)$(TargetName)_cpp.pch
+
+
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index a530ef1bf..ba454f428 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -175,6 +175,12 @@
{3ad16a8a-0ed8-439c-a771-383af2e2867f}
+
+ {00002ddb6c5ea921181bf32d50e40000}
+
+
+ {00000a808f8ba6b489985f82a4e80000}
+
@@ -399,6 +405,9 @@
API Headers
+
+ main
+
@@ -846,6 +855,12 @@
+
+ main\generic
+
+
+ main
+
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index 21c5e7ebb..0dee306b6 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -133,6 +133,7 @@
+
@@ -337,6 +338,8 @@
+
+
true
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index 430babceb..7d765c13c 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -7,6 +7,12 @@
{68e1b30b-19ed-4612-93e4-6260c5a979e5}
+
+ {00004a2523fc69c7128c60648c860000}
+
+
+ {0000318d975e0a2867ab1d5727bf0000}
+
@@ -270,6 +276,9 @@
Source Files
+
+ main
+
Source Files
@@ -588,6 +597,12 @@
Source Files
+
+ main\generic
+
+
+ main
+
Source Files
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 84e774521..57eb07e85 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -368,6 +368,7 @@
+
@@ -397,6 +398,8 @@
Create
Create
+
+
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 23fa7b2e3..cd7b2b2e3 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -169,6 +169,12 @@
{f48c2b17-1bee-4fec-a7c8-24cf619abe08}
+
+ {00001967ea2801028a046a722a070000}
+
+
+ {0000ddc7911820dbe64274d3654f0000}
+
@@ -390,6 +396,9 @@
API Headers
+
+ main
+
@@ -828,6 +837,12 @@
+
+ main\generic
+
+
+ main
+
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 279a74856..bd32ee428 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -34,6 +34,7 @@
/* Begin PBXBuildFile section */
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = 000078E1881E857EBB6C0000 /* SDL_hashtable.c */; };
+ 0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
007317A40858DECD00B2BC32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179D0858DECD00B2BC32 /* Cocoa.framework */; platformFilters = (macos, ); };
007317A60858DECD00B2BC32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179F0858DECD00B2BC32 /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, ); };
00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); };
@@ -476,6 +477,8 @@
F3F7D9E12933074E00816151 /* SDL_begin_code.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E72933074E00816151 /* SDL_begin_code.h */; settings = {ATTRIBUTES = (Public, ); }; };
F3F7D9E52933074E00816151 /* SDL_system.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E82933074E00816151 /* SDL_system.h */; settings = {ATTRIBUTES = (Public, ); }; };
FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
+ 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */; };
+ 0000C3B22D46279F99170000 /* SDL_main_callbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 00003260407E1002EAC10000 /* SDL_main_callbacks.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -504,6 +507,7 @@
/* Begin PBXFileReference section */
000078E1881E857EBB6C0000 /* SDL_hashtable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hashtable.c; sourceTree = ""; };
0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hashtable.h; sourceTree = ""; };
+ 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysmain_callbacks.m; sourceTree = ""; };
0073179D0858DECD00B2BC32 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
0073179F0858DECD00B2BC32 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
007317C10858E15000B2BC32 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
@@ -977,6 +981,8 @@
F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = ""; };
F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; };
FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
+ 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_main_callbacks.c; path = SDL_main_callbacks.c; sourceTree = ""; };
+ 00003260407E1002EAC10000 /* SDL_main_callbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_main_callbacks.h; path = SDL_main_callbacks.h; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1002,6 +1008,24 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 000082EF09C89B62BD840000 /* main */ = {
+ isa = PBXGroup;
+ children = (
+ 00008B5A0CB83D2069E80000 /* ios */,
+ 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */,
+ 00003260407E1002EAC10000 /* SDL_main_callbacks.h */,
+ );
+ path = main;
+ sourceTree = "";
+ };
+ 00008B5A0CB83D2069E80000 /* ios */ = {
+ isa = PBXGroup;
+ children = (
+ 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */,
+ );
+ path = ios;
+ sourceTree = "";
+ };
0153844A006D81B07F000001 /* Public Headers */ = {
isa = PBXGroup;
children = (
@@ -1122,6 +1146,7 @@
A7D8A91123E2514000DCD162 /* libm */,
A7D8A85D23E2513F00DCD162 /* loadso */,
566E26CB246274AE00718109 /* locale */,
+ 000082EF09C89B62BD840000 /* main */,
5616CA47252BB278005D5928 /* misc */,
A7D8A7DF23E2513F00DCD162 /* power */,
A7D8A8DA23E2514000DCD162 /* render */,
@@ -2614,6 +2639,8 @@
A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */,
566E26D8246274CC00718109 /* SDL_locale.c in Sources */,
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
+ 0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */,
+ 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 01193b0cd..2af3c88ff 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2531,6 +2531,22 @@ extern "C" {
*/
#define SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES "SDL_AUDIO_DEVICE_SAMPLE_FRAMES"
+
+/**
+ * Request SDL_AppIterate() be called at a specific rate.
+ *
+ * This number is in Hz, so "60" means try to iterate 60 times per second.
+ *
+ * On some platforms, or if you are using SDL_main instead of SDL_AppIterate,
+ * this hint is ignored. When the hint can be used, it is allowed to be
+ * changed at any time.
+ *
+ * This defaults to 60, and specifying NULL for the hint's value will restore
+ * the default.
+ */
+#define SDL_HINT_MAIN_CALLBACK_RATE "SDL_MAIN_CALLBACK_RATE"
+
+
/**
* \brief An enumeration of hint priorities
*/
diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h
index 6181c0c01..1833fa73f 100644
--- a/include/SDL3/SDL_main.h
+++ b/include/SDL3/SDL_main.h
@@ -140,7 +140,7 @@
* \endcode
*/
-#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE)
+#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) || defined(SDL_MAIN_USE_CALLBACKS)
#define main SDL_main
#endif
@@ -149,11 +149,199 @@
extern "C" {
#endif
+union SDL_Event;
+typedef int (SDLCALL *SDL_AppInit_func)(int argc, char *argv[]);
+typedef int (SDLCALL *SDL_AppIterate_func)(void);
+typedef int (SDLCALL *SDL_AppEvent_func)(const union SDL_Event *event);
+typedef void (SDLCALL *SDL_AppQuit_func)(void);
+
+/**
+ * You can (optionally!) define SDL_MAIN_USE_CALLBACKS before including
+ * SDL_main.h, and then your application will _not_ have a standard
+ * "main" entry point. Instead, it will operate as a collection of
+ * functions that are called as necessary by the system. On some
+ * platforms, this is just a layer where SDL drives your program
+ * instead of your program driving SDL, on other platforms this might
+ * hook into the OS to manage the lifecycle. Programs on most platforms
+ * can use whichever approach they prefer, but the decision boils down
+ * to:
+ *
+ * - Using a standard "main" function: this works like it always has for
+ * the past 50+ years in C programming, and your app is in control.
+ * - Using the callback functions: this might clean up some code,
+ * avoid some #ifdef blocks in your program for some platforms, be more
+ * resource-friendly to the system, and possibly be the primary way to
+ * access some future platforms (but none require this at the moment).
+ *
+ * This is up to the app; both approaches are considered valid and supported
+ * ways to write SDL apps.
+ *
+ * If using the callbacks, don't define a "main" function. Instead, implement
+ * the functions listed below in your program.
+ */
+#ifdef SDL_MAIN_USE_CALLBACKS
+
+/**
+ * App-implemented initial entry point for SDL_MAIN_USE_CALLBACKS apps.
+ *
+ * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
+ * using a standard "main" function, you should not supply this.
+ *
+ * This function is called by SDL once, at startup. The function should
+ * initialize whatever is necessary, possibly create windows and open
+ * audio devices, etc. The `argc` and `argv` parameters work like they would
+ * with a standard "main" function.
+ *
+ * This function should not go into an infinite mainloop; it should do any
+ * one-time setup it requires and then return.
+ *
+ * If this function returns 0, the app will proceed to normal operation,
+ * and will begin receiving repeated calls to SDL_AppIterate and SDL_AppEvent
+ * for the life of the program. If this function returns < 0, SDL will
+ * call SDL_AppQuit and terminate the process with an exit code that reports
+ * an error to the platform. If it returns > 0, the SDL calls SDL_AppQuit
+ * and terminates with an exit code that reports success to the platform.
+ *
+ * \param argc The standard ANSI C main's argc; number of elements in `argv`
+ * \param argv The standard ANSI C main's argv; array of command line arguments.
+ * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
+ *
+ * \threadsafety This function is not thread safe.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AppIterate
+ * \sa SDL_AppEvent
+ * \sa SDL_AppQuit
+ */
+extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]);
+
+/**
+ * App-implemented iteration entry point for SDL_MAIN_USE_CALLBACKS apps.
+ *
+ * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
+ * using a standard "main" function, you should not supply this.
+ *
+ * This function is called repeatedly by SDL after SDL_AppInit returns 0.
+ * The function should operate as a single iteration the program's primary
+ * loop; it should update whatever state it needs and draw a new frame of
+ * video, usually.
+ *
+ * On some platforms, this function will be called at the refresh rate of
+ * the display (which might change during the life of your app!). There are
+ * no promises made about what frequency this function might run at. You
+ * should use SDL's timer functions if you need to see how much time has
+ * passed since the last iteration.
+ *
+ * There is no need to process the SDL event queue during this function;
+ * SDL will send events as they arrive in SDL_AppEvent, and in most cases
+ * the event queue will be empty when this function runs anyhow.
+ *
+ * This function should not go into an infinite mainloop; it should do one
+ * iteration of whatever the program does and return.
+ *
+ * If this function returns 0, the app will continue normal operation,
+ * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life
+ * of the program. If this function returns < 0, SDL will call SDL_AppQuit
+ * and terminate the process with an exit code that reports an error to the
+ * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with
+ * an exit code that reports success to the platform.
+ *
+ * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
+ *
+ * \threadsafety This function is not thread safe.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AppInit
+ * \sa SDL_AppEvent
+ * \sa SDL_AppQuit
+ */
+extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void);
+
+/**
+ * App-implemented event entry point for SDL_MAIN_USE_CALLBACKS apps.
+ *
+ * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
+ * using a standard "main" function, you should not supply this.
+ *
+ * This function is called as needed by SDL after SDL_AppInit returns 0;
+ * It is called once for each new event.
+ *
+ * There is (currently) no guarantee about what thread this will be called
+ * from; whatever thread pushes an event onto SDL's queue will trigger this
+ * function. SDL is responsible for pumping the event queue between
+ * each call to SDL_AppIterate, so in normal operation one should only
+ * get events in a serial fashion, but be careful if you have a thread that
+ * explicitly calls SDL_PushEvent.
+ *
+ * Events sent to this function are not owned by the app; if you need to
+ * save the data, you should copy it.
+ *
+ * You do not need to free event data (such as the `file` string in
+ * SDL_EVENT_DROP_FILE), as SDL will free it once this function returns.
+ * Note that this is different than one might expect when using a standard
+ * "main" function!
+ *
+ * This function should not go into an infinite mainloop; it should handle
+ * the provided event appropriately and return.
+ *
+ * If this function returns 0, the app will continue normal operation,
+ * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life
+ * of the program. If this function returns < 0, SDL will call SDL_AppQuit
+ * and terminate the process with an exit code that reports an error to the
+ * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with
+ * an exit code that reports success to the platform.
+ *
+ * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
+ *
+ * \threadsafety This function is not thread safe.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AppInit
+ * \sa SDL_AppIterate
+ * \sa SDL_AppQuit
+ */
+extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(const SDL_Event *event);
+
+/**
+ * App-implemented deinit entry point for SDL_MAIN_USE_CALLBACKS apps.
+ *
+ * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
+ * using a standard "main" function, you should not supply this.
+ *
+ * This function is called once by SDL before terminating the program.
+ *
+ * This function will be called no matter what, even if SDL_AppInit
+ * requests termination.
+ *
+ * This function should not go into an infinite mainloop; it should
+ * deinitialize any resources necessary, perform whatever shutdown
+ * activities, and return.
+ *
+ * You do not need to call SDL_Quit() in this function, as SDL will call
+ * it after this function returns and before the process terminates, but
+ * it is safe to do so.
+ *
+ * \threadsafety This function is not thread safe.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AppInit
+ * \sa SDL_AppIterate
+ * \sa SDL_AppEvent
+ */
+extern SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void);
+
+#endif /* SDL_MAIN_USE_CALLBACKS */
+
+
/**
* The prototype for the application's main() function
*/
-typedef int (*SDL_main_func)(int argc, char *argv[]);
-extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
+typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]);
+extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]);
/**
@@ -198,6 +386,33 @@ extern DECLSPEC void SDLCALL SDL_SetMainReady(void);
*/
extern DECLSPEC int SDLCALL SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved);
+/**
+ * An entry point for SDL's use in SDL_MAIN_USE_CALLBACKS.
+ *
+ * Generally, you should not call this function directly. This only exists
+ * to hand off work into SDL as soon as possible, where it has a lot more
+ * control and functionality available, and make the inline code in
+ * SDL_main.h as small as possible.
+ *
+ * Not all platforms use this, it's actual use is hidden in a magic
+ * header-only library, and you should not call this directly unless you
+ * _really_ know what you're doing.
+ *
+ * \param argc standard Unix main argc
+ * \param argv standard Unix main argv
+ * \param appinit The application's SDL_AppInit function
+ * \param appiter The application's SDL_AppIterate function
+ * \param appevent The application's SDL_AppEvent function
+ * \param appquit The application's SDL_AppQuit function
+ * \returns standard Unix main return value
+ *
+ * \threadsafety It is not safe to call this anywhere except as the only function call in SDL_main.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit);
+
+
#if defined(__WIN32__) || defined(__GDK__)
/**
@@ -282,7 +497,8 @@ extern DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
#if !defined(SDL_MAIN_HANDLED) && !defined(SDL_MAIN_NOIMPL)
/* include header-only SDL_main implementations */
-#if defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \
+#if defined(SDL_MAIN_USE_CALLBACKS) \
+ || defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \
|| defined(__3DS__) || defined(__NGAGE__) || defined(__PS2__) || defined(__PSP__)
/* platforms which main (-equivalent) can be implemented in plain C */
diff --git a/include/SDL3/SDL_main_impl.h b/include/SDL3/SDL_main_impl.h
index 6b73bbb3f..dd35dbd52 100644
--- a/include/SDL3/SDL_main_impl.h
+++ b/include/SDL3/SDL_main_impl.h
@@ -41,6 +41,30 @@
# undef main
#endif /* main */
+#ifdef SDL_MAIN_USE_CALLBACKS
+
+#if 0
+ /* currently there are no platforms that _need_ a magic entry point here
+ for callbacks, but if one shows up, implement it here. */
+
+#else /* use a standard SDL_main, which the app SHOULD NOT ALSO SUPPLY. */
+
+/* this define makes the normal SDL_main entry point stuff work...we just provide SDL_main() instead of the app. */
+#define SDL_MAIN_CALLBACK_STANDARD 1
+
+int SDL_main(int argc, char **argv)
+{
+ return SDL_EnterAppMainCallbacks(argc, argv, SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit);
+}
+
+#endif /* platform-specific tests */
+
+#endif /* SDL_MAIN_USE_CALLBACKS */
+
+
+/* set up the usual SDL_main stuff if we're not using callbacks or if we are but need the normal entry point. */
+#if !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD)
+
#if defined(__WIN32__) || defined(__GDK__)
/* these defines/typedefs are needed for the WinMain() definition */
@@ -193,6 +217,8 @@ int main(int argc, char *argv[])
#endif /* __WIN32__ etc */
+#endif /* !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD) */
+
/* rename users main() function to SDL_main() so it can be called from the wrappers above */
#define main SDL_main
diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h
index f2d66e51e..f194a42b5 100644
--- a/include/SDL3/SDL_test_common.h
+++ b/include/SDL3/SDL_test_common.h
@@ -201,15 +201,27 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state);
SDL_bool SDLTest_CommonDefaultArgs(SDLTest_CommonState *state, const int argc, char **argv);
/**
- * \brief Common event handler for test windows.
+ * Common event handler for test windows if you use a standard SDL_main.
+ *
+ * This will free data from the event, like the string in a drop event!
*
* \param state The common state used to create test window.
* \param event The event to handle.
* \param done Flag indicating we are done.
- *
*/
void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done);
+/**
+ * Common event handler for test windows if you use SDL_AppEvent.
+ *
+ * This does _not_ free anything in `event`.
+ *
+ * \param state The common state used to create test window.
+ * \param event The event to handle.
+ * \returns Value suitable for returning from SDL_AppEvent().
+ */
+int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event);
+
/**
* \brief Close test window.
*
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index c53c02adc..6ee8a813a 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -920,6 +920,7 @@ SDL3_0.0.0 {
SDL_GetSurfaceProperties;
SDL_GetWindowProperties;
SDL_ClearProperty;
+ SDL_EnterAppMainCallbacks;
# extra symbols go here (don't modify this line)
local: *;
};
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index dc8596662..09c6dbbd4 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -945,3 +945,4 @@
#define SDL_GetSurfaceProperties SDL_GetSurfaceProperties_REAL
#define SDL_GetWindowProperties SDL_GetWindowProperties_REAL
#define SDL_ClearProperty SDL_ClearProperty_REAL
+#define SDL_EnterAppMainCallbacks SDL_EnterAppMainCallbacks_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index ac41615e9..868a35ec9 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -977,3 +977,4 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSensorProperties,(SDL_Sensor *a),(a),ret
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSurfaceProperties,(SDL_Surface *a),(a),return)
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
+SDL_DYNAPI_PROC(int,SDL_EnterAppMainCallbacks,(int a, char *b[], SDL_AppInit_func c, SDL_AppIterate_func d, SDL_AppEvent_func e, SDL_AppQuit_func f),(a,b,c,d,e,f),return)
diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c
new file mode 100644
index 000000000..5d880066d
--- /dev/null
+++ b/src/main/SDL_main_callbacks.c
@@ -0,0 +1,114 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+#include "SDL_main_callbacks.h"
+
+static SDL_AppEvent_func SDL_main_event_callback;
+static SDL_AppIterate_func SDL_main_iteration_callback;
+static SDL_AppQuit_func SDL_main_quit_callback;
+static SDL_AtomicInt apprc; // use an atomic, since events might land from any thread and we don't want to wrap this all in a mutex. A CAS makes sure we only move from zero once.
+
+static int SDLCALL EventWatcher(void *userdata, SDL_Event *event)
+{
+ if (SDL_AtomicGet(&apprc) == 0) { // if already quitting, don't send the event to the app.
+ SDL_AtomicCAS(&apprc, 0, SDL_main_event_callback(event));
+ }
+ return 0;
+}
+
+int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
+{
+ SDL_main_iteration_callback = appiter;
+ SDL_main_event_callback = appevent;
+ SDL_main_quit_callback = appquit;
+ SDL_AtomicSet(&apprc, 0);
+
+ const int rc = appinit(argc, argv);
+ if (SDL_AtomicCAS(&apprc, 0, rc) && (rc == 0)) { // bounce if SDL_AppInit already said abort, otherwise...
+ // make sure we definitely have events initialized, even if the app didn't do it.
+ if (SDL_InitSubSystem(SDL_INIT_EVENTS) == -1) {
+ SDL_AtomicSet(&apprc, -1);
+ return -1;
+ }
+
+ // drain any initial events that might have arrived before we added a watcher.
+ SDL_Event event;
+ SDL_Event *pending_events = NULL;
+ int total_pending_events = 0;
+ while (SDL_PollEvent(&event)) {
+ void *ptr = SDL_realloc(pending_events, sizeof (SDL_Event) * (total_pending_events + 1));
+ if (!ptr) {
+ SDL_OutOfMemory();
+ SDL_free(pending_events);
+ SDL_AtomicSet(&apprc, -1);
+ return -1;
+ }
+ pending_events = (SDL_Event *) ptr;
+ SDL_copyp(&pending_events[total_pending_events], &event);
+ total_pending_events++;
+ }
+
+ SDL_AddEventWatch(EventWatcher, NULL); // !!! FIXME: this should really return an error.
+
+ for (int i = 0; i < total_pending_events; i++) {
+ SDL_PushEvent(&pending_events[i]);
+ }
+
+ SDL_free(pending_events);
+ }
+
+ return SDL_AtomicGet(&apprc);
+}
+
+int SDL_IterateMainCallbacks(void)
+{
+ SDL_Event event;
+ SDL_PumpEvents();
+ while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) == 1) {
+ // just empty the queue, EventWatcher sends the events to the app.
+ switch (event.type) {
+ case SDL_EVENT_DROP_FILE:
+ case SDL_EVENT_DROP_TEXT:
+ SDL_free(event.drop.file);
+ break;
+ }
+ }
+
+ int rc = SDL_main_iteration_callback();
+ if (!SDL_AtomicCAS(&apprc, 0, rc)) {
+ rc = SDL_AtomicGet(&apprc); // something else already set a quit result, keep that.
+ }
+
+ return rc;
+}
+
+void SDL_QuitMainCallbacks(void)
+{
+ SDL_DelEventWatch(EventWatcher, NULL);
+ SDL_main_quit_callback();
+
+ // for symmetry, you should explicitly Quit what you Init, but we might come through here uninitialized and SDL_Quit() will clear everything anyhow.
+ //SDL_QuitSubSystem(SDL_INIT_EVENTS);
+
+ SDL_Quit();
+}
+
diff --git a/src/main/SDL_main_callbacks.h b/src/main/SDL_main_callbacks.h
new file mode 100644
index 000000000..9df171a99
--- /dev/null
+++ b/src/main/SDL_main_callbacks.h
@@ -0,0 +1,31 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_main_callbacks_h_
+#define SDL_main_callbacks_h_
+
+int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func _appiter, SDL_AppEvent_func _appevent, SDL_AppQuit_func _appquit);
+int SDL_IterateMainCallbacks(void);
+void SDL_QuitMainCallbacks(void);
+
+#endif // SDL_main_callbacks_h_
+
+
diff --git a/src/main/emscripten/SDL_sysmain_callbacks.c b/src/main/emscripten/SDL_sysmain_callbacks.c
new file mode 100644
index 000000000..fc6f53e6c
--- /dev/null
+++ b/src/main/emscripten/SDL_sysmain_callbacks.c
@@ -0,0 +1,47 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+#include "../SDL_main_callbacks.h"
+
+#include
+
+static void EmscriptenInternalMainloop(void)
+{
+ const int rc = SDL_IterateMainCallbacks();
+ if (rc != 0) {
+ SDL_QuitMainCallbacks();
+ emscripten_cancel_main_loop(); // kill" the mainloop, so it stops calling back into it.
+ exit((rc < 0) ? 1 : 0); // hopefully this takes down everything else, too.
+ }
+}
+
+int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
+{
+ const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
+ if (rc == 0) {
+ emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // run at refresh rate, don't throw an exception since we do an orderly return.
+ } else {
+ SDL_QuitMainCallbacks();
+ }
+ return (rc < 0) ? 1 : 0;
+}
+
diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c
new file mode 100644
index 000000000..3e47264e3
--- /dev/null
+++ b/src/main/generic/SDL_sysmain_callbacks.c
@@ -0,0 +1,80 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+#include "../SDL_main_callbacks.h"
+#include "../../video/SDL_sysvideo.h"
+
+static int callback_rate_increment = 0;
+
+static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue)
+{
+ const int callback_rate = newValue ? SDL_atoi(newValue) : 60;
+ if (callback_rate > 0) {
+ callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate);
+ } else {
+ callback_rate_increment = 0;
+ }
+}
+
+int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
+{
+ int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
+
+ SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL);
+
+ Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0;
+
+ while ((rc = SDL_IterateMainCallbacks()) == 0) {
+ // !!! FIXME: this can be made more complicated if we decide to
+ // !!! FIXME: optionally hand off callback responsibility to the
+ // !!! FIXME: video subsystem (for example, if Wayland has a
+ // !!! FIXME: protocol to drive an animation loop, maybe we hand
+ // !!! FIXME: off to them here if/when the video subsystem becomes
+ // !!! FIXME: initialized).
+
+ // !!! FIXME: maybe respect this hint even if there _is_ a window.
+ // if there's no window, try to run at about 60fps (or whatever rate
+ // the hint requested). This makes this not eat all the CPU in
+ // simple things like loopwave. If there's a window, we run as fast
+ // as possible, which means we'll clamp to vsync in common cases,
+ // and won't be restrained to vsync if the app is doing a benchmark
+ // or doesn't want to be, based on how they've set up that window.
+ if ((callback_rate_increment == 0) || SDL_HasWindows()) {
+ next_iteration = 0; // just clear the timer and run at the pace the video subsystem allows.
+ } else {
+ const Uint64 now = SDL_GetTicksNS();
+ if (next_iteration > now) { // Running faster than the limit, sleep a little.
+ SDL_DelayNS(next_iteration - now);
+ } else {
+ next_iteration = now; // running behind (or just lost the window)...reset the timer.
+ }
+ next_iteration += callback_rate_increment;
+ }
+ }
+
+ SDL_DelHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL);
+
+ SDL_QuitMainCallbacks();
+
+ return (rc < 0) ? 1 : 0;
+}
+
diff --git a/src/main/ios/SDL_sysmain_callbacks.m b/src/main/ios/SDL_sysmain_callbacks.m
new file mode 100644
index 000000000..63722630f
--- /dev/null
+++ b/src/main/ios/SDL_sysmain_callbacks.m
@@ -0,0 +1,82 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+#include "../SDL_main_callbacks.h"
+
+#ifdef __IOS__
+
+#import
+
+@interface SDLIosMainCallbacksDisplayLink : NSObject
+@property(nonatomic, retain) CADisplayLink *displayLink;
+- (void)appIteration:(CADisplayLink *)sender;
+- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit;
+@end
+
+static SDLIosMainCallbacksDisplayLink *globalDisplayLink;
+
+@implementation SDLIosMainCallbacksDisplayLink
+
+- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit;
+{
+ if ((self = [super init])) {
+ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(appIteration:)];
+ [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ }
+ return self;
+}
+
+- (void)appIteration:(CADisplayLink *)sender
+{
+ const int rc = SDL_IterateMainCallbacks();
+ if (rc != 0) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ globalDisplayLink = nil;
+ SDL_QuitMainCallbacks();
+ exit((rc < 0) ? 1 : 0);
+ }
+}
+@end
+
+// SDL_RunApp will land in UIApplicationMain, which calls SDL_main from postFinishLaunch, which calls this.
+// When we return from here, we're living in the RunLoop, and a CADisplayLink is firing regularly for us.
+int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
+{
+ const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
+ if (rc == 0) {
+ globalDisplayLink = [[SDLIosMainCallbacksDisplayLink alloc] init:appiter quitfunc:appquit];
+ if (globalDisplayLink != nil) {
+ return 0; // this will fall all the way out of SDL_main, where UIApplicationMain will keep running the RunLoop.
+ }
+ }
+
+ // appinit requested quit, just bounce out now.
+ SDL_QuitMainCallbacks();
+ exit((rc < 0) ? 1 : 0);
+
+ return 1; // just in case.
+}
+
+#endif
+
+
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index e4b62709b..f18e94028 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -1560,7 +1560,7 @@ static const char *GamepadButtonName(const SDL_GamepadButton button)
}
}
-static void SDLTest_PrintEvent(SDL_Event *event)
+static void SDLTest_PrintEvent(const SDL_Event *event)
{
switch (event->type) {
case SDL_EVENT_SYSTEM_THEME_CHANGED:
@@ -2029,7 +2029,7 @@ static void FullscreenTo(SDLTest_CommonState *state, int index, int windowId)
SDL_free(displays);
}
-void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done)
+int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event)
{
int i;
@@ -2408,20 +2408,27 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done
}
break;
case SDLK_ESCAPE:
- *done = 1;
- break;
+ return 1;
default:
break;
}
break;
}
case SDL_EVENT_QUIT:
- *done = 1;
- break;
+ return 1;
+ }
+ return 0; /* keep going */
+}
+
+void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done)
+{
+ *done = SDLTest_CommonEventMainCallbacks(state, event) ? 1 : 0;
+
+ switch (event->type) {
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
- SDL_free(event->drop.file);
+ SDL_free(event->drop.file); // SDL frees these if you use SDL_AppEvent, not us, so explicitly handle it here.
break;
}
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4ce431e73..ee4251a47 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -47,11 +47,16 @@ if(WINDOWS_STORE)
target_link_libraries(sdl_test_main_uwp PRIVATE SDL3::Headers)
target_compile_options(sdl_test_main_uwp PRIVATE "/ZW")
+ add_library(sdl_test_main_callbacks_uwp OBJECT main.cpp)
+ target_link_libraries(sdl_test_main_callbacks_uwp PRIVATE SDL3::Headers)
+ target_compile_options(sdl_test_main_callbacks_uwp PRIVATE "/ZW")
+ target_compile_definitions(sdl_test_main_callbacks_uwp PRIVATE "SDL_MAIN_USE_CALLBACKS")
+
set_source_files_properties(${RESOURCE_FILES} PROPERTIES VS_DEPLOYENT_LOCATION "Assets")
endif()
macro(add_sdl_test_executable TARGET)
- cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN})
+ cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90;MAIN_CALLBACKS" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN})
if(AST_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}")
endif()
@@ -73,8 +78,14 @@ macro(add_sdl_test_executable TARGET)
TARGET "${TARGET}"
)
set_property(SOURCE "${uwp_bindir}/${TARGET}.appxmanifest" PROPERTY VS_DEPLOYMENT_CONTENT 1)
+
+ if(AST_MAIN_CALLBACKS)
+ list(APPEND EXTRA_SOURCES "$")
+ else()
+ list(APPEND EXTRA_SOURCES "$")
+ endif()
+
list(APPEND EXTRA_SOURCES
- "$"
"${uwp_bindir}/${TARGET}.appxmanifest"
"uwp/logo-50x50.png"
"uwp/square-44x44.png"
@@ -213,7 +224,7 @@ endif()
add_sdl_test_executable(checkkeys SOURCES checkkeys.c)
add_sdl_test_executable(checkkeysthreads SOURCES checkkeysthreads.c)
-add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS SOURCES loopwave.c)
+add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS MAIN_CALLBACKS SOURCES loopwave.c)
add_sdl_test_executable(testsurround SOURCES testsurround.c)
add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c)
add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c)
@@ -223,7 +234,7 @@ file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c)
add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES})
add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c)
add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c)
-add_sdl_test_executable(testaudiocapture SOURCES testaudiocapture.c)
+add_sdl_test_executable(testaudiocapture MAIN_CALLBACKS SOURCES testaudiocapture.c)
add_sdl_test_executable(testatomic NONINTERACTIVE SOURCES testatomic.c)
add_sdl_test_executable(testintersections SOURCES testintersections.c)
add_sdl_test_executable(testrelative SOURCES testrelative.c)
@@ -304,7 +315,7 @@ files2headers(gamepad_image_headers
)
files2headers(icon_bmp_header icon.bmp)
-add_sdl_test_executable(testaudio NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
+add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c)
add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers})
add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c)
@@ -341,7 +352,7 @@ add_sdl_test_executable(testsem NONINTERACTIVE NONINTERACTIVE_ARGS 10 NONINTERAC
add_sdl_test_executable(testsensor SOURCES testsensor.c)
add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c)
add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c)
-add_sdl_test_executable(testsprite NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c)
+add_sdl_test_executable(testsprite MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c)
add_sdl_test_executable(testspriteminimal SOURCES testspriteminimal.c ${icon_bmp_header})
add_sdl_test_executable(teststreaming NEEDS_RESOURCES TESTUTILS SOURCES teststreaming.c)
add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c)
diff --git a/test/loopwave.c b/test/loopwave.c
index 1fa1b91e1..877fe8f39 100644
--- a/test/loopwave.c
+++ b/test/loopwave.c
@@ -17,10 +17,7 @@
*/
#include
-#ifdef __EMSCRIPTEN__
-#include
-#endif
-
+#define SDL_MAIN_USE_CALLBACKS 1
#include
#include
#include
@@ -34,68 +31,24 @@ static struct
} wave;
static SDL_AudioStream *stream;
+static SDLTest_CommonState *state;
-static void fillerup(void)
+static int fillerup(void)
{
const int minimum = (wave.soundlen / SDL_AUDIO_FRAMESIZE(wave.spec)) / 2;
if (SDL_GetAudioStreamQueued(stream) < minimum) {
SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
}
+ return 0;
}
-/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
-static void
-quit(int rc)
-{
- SDL_Quit();
- /* Let 'main()' return normally */
- if (rc != 0) {
- exit(rc);
- }
-}
-
-static void
-close_audio(void)
-{
- if (stream) {
- SDL_DestroyAudioStream(stream);
- stream = NULL;
- }
-}
-
-static void
-open_audio(void)
-{
- stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL);
- if (!stream) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError());
- SDL_free(wave.sound);
- quit(2);
- }
- SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
-}
-
-
-static int done = 0;
-
-
-
-#ifdef __EMSCRIPTEN__
-static void loop(void)
-{
- if (done) {
- emscripten_cancel_main_loop();
- } else {
- fillerup();
- }
-}
-#endif
-
-int main(int argc, char *argv[])
+int SDL_AppInit(int argc, char *argv[])
{
int i;
char *filename = NULL;
- SDLTest_CommonState *state;
+
+ /* this doesn't have to run very much, so give up tons of CPU time between iterations. */
+ SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "5");
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
@@ -129,22 +82,25 @@ int main(int argc, char *argv[])
/* Load the SDL library */
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
- return 1;
+ return -1;
}
filename = GetResourceFilename(filename, "sample.wav");
if (filename == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError());
- quit(1);
+ return -1;
}
/* Load the wave file into memory */
if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
- quit(1);
+ SDL_free(filename);
+ return -1;
}
+ SDL_free(filename);
+
/* Show the list of available drivers */
SDL_Log("Available audio drivers:");
for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
@@ -153,30 +109,30 @@ int main(int argc, char *argv[])
SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());
- open_audio();
-
-#ifdef __EMSCRIPTEN__
- emscripten_set_main_loop(loop, 0, 1);
-#else
- while (!done) {
- SDL_Event event;
-
- while (SDL_PollEvent(&event) > 0) {
- if (event.type == SDL_EVENT_QUIT) {
- done = 1;
- }
- }
-
- fillerup();
- SDL_Delay(100);
+ stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL);
+ if (!stream) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError());
+ return -1;
}
-#endif
+ SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
- /* Clean up on signal */
- close_audio();
- SDL_free(wave.sound);
- SDL_free(filename);
- SDL_Quit();
- SDLTest_CommonDestroyState(state);
return 0;
}
+
+int SDL_AppEvent(const SDL_Event *event)
+{
+ return (event->type == SDL_EVENT_QUIT) ? 1 : 0;
+}
+
+int SDL_AppIterate(void)
+{
+ return fillerup();
+}
+
+void SDL_AppQuit(void)
+{
+ SDL_DestroyAudioStream(stream);
+ SDL_free(wave.sound);
+ SDLTest_CommonDestroyState(state);
+}
+
diff --git a/test/testaudio.c b/test/testaudio.c
index e77901c94..d7b8b56ae 100644
--- a/test/testaudio.c
+++ b/test/testaudio.c
@@ -1,9 +1,4 @@
-#include
-
-#ifdef __EMSCRIPTEN__
-#include
-#endif
-
+#define SDL_MAIN_USE_CALLBACKS 1
#include
#include
#include
@@ -103,7 +98,6 @@ struct Thing
static Uint64 app_ready_ticks = 0;
-static int done = 0;
static SDLTest_CommonState *state = NULL;
static Thing *things = NULL;
@@ -124,51 +118,6 @@ static Texture *trashcan_texture = NULL;
static Texture *soundboard_texture = NULL;
static Texture *soundboard_levels_texture = NULL;
-static void DestroyTexture(Texture *tex);
-static void DestroyThing(Thing *thing);
-
-
-/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
-static void Quit(int rc)
-{
- while (things != NULL) {
- DestroyThing(things); /* make sure all the audio devices are closed, etc. */
- }
-
- DestroyTexture(physdev_texture);
- DestroyTexture(logdev_texture);
- DestroyTexture(audio_texture);
- DestroyTexture(trashcan_texture);
- DestroyTexture(soundboard_texture);
- DestroyTexture(soundboard_levels_texture);
- SDLTest_CommonQuit(state);
-
- /* Let 'main()' return normally */
- if (rc != 0) {
- exit(rc);
- }
-}
-
-static char *xstrdup(const char *str)
-{
- char *ptr = SDL_strdup(str);
- if (!ptr) {
- SDL_Log("Out of memory!");
- Quit(1);
- }
- return ptr;
-}
-
-static void *xalloc(const size_t len)
-{
- void *ptr = SDL_calloc(1, len);
- if (!ptr) {
- SDL_Log("Out of memory!");
- Quit(1);
- }
- return ptr;
-}
-
static void SetTitleBar(const char *fmt, ...)
{
@@ -232,7 +181,12 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl
Thing *i;
Thing *thing;
- thing = (Thing *) xalloc(sizeof (Thing));
+ thing = (Thing *) SDL_calloc(1, sizeof (Thing));
+ if (!thing) {
+ SDL_Log("Out of memory!");
+ return NULL;
+ }
+
if ((w < 0) || (h < 0)) {
SDL_assert(texture != NULL);
if (w < 0) {
@@ -256,7 +210,7 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl
thing->scale = 1.0f;
thing->createticks = SDL_GetTicks();
thing->texture = texture;
- thing->titlebar = titlebar ? xstrdup(titlebar) : NULL;
+ thing->titlebar = titlebar ? SDL_strdup(titlebar) : NULL; /* if allocation fails, oh well. */
/* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */
if (things == NULL) {
@@ -515,12 +469,14 @@ static Thing *CreatePoofThing(Thing *poofing_thing)
const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2);
const float z = poofing_thing->z;
Thing *thing = CreateThing(THING_POOF, poofing_thing->rect.x, poofing_thing->rect.y, z, poofing_thing->rect.w, poofing_thing->rect.h, poofing_thing->texture, NULL);
- thing->data.poof.startw = poofing_thing->rect.w;
- thing->data.poof.starth = poofing_thing->rect.h;
- thing->data.poof.centerx = centerx;
- thing->data.poof.centery = centery;
- thing->ontick = PoofThing_ontick;
- thing->ondrag = PoofThing_ondrag;
+ if (thing) {
+ thing->data.poof.startw = poofing_thing->rect.w;
+ thing->data.poof.starth = poofing_thing->rect.h;
+ thing->data.poof.centerx = centerx;
+ thing->data.poof.centery = centery;
+ thing->ontick = PoofThing_ontick;
+ thing->ondrag = PoofThing_ondrag;
+ }
return thing;
}
@@ -638,18 +594,20 @@ static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, con
{
static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL };
Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname);
- SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
- thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
- if (buf && buflen) {
- SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
- SDL_FlushAudioStream(thing->data.stream.stream);
- thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
+ if (thing) {
+ SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
+ thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
+ if (buf && buflen) {
+ SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
+ SDL_FlushAudioStream(thing->data.stream.stream);
+ thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
+ }
+ thing->ontick = StreamThing_ontick;
+ thing->ondrag = StreamThing_ondrag;
+ thing->ondrop = StreamThing_ondrop;
+ thing->ondraw = StreamThing_ondraw;
+ thing->can_be_dropped_onto = can_be_dropped_onto;
}
- thing->ontick = StreamThing_ontick;
- thing->ondrag = StreamThing_ondrag;
- thing->ondrop = StreamThing_ondrop;
- thing->ondraw = StreamThing_ondraw;
- thing->can_be_dropped_onto = can_be_dropped_onto;
return thing;
}
@@ -703,13 +661,15 @@ static Thing *LoadWavThing(const char *fname, float x, float y)
SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq);
thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar);
- SDL_free(titlebar);
- SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
- thing->data.wav.buf = buf;
- thing->data.wav.buflen = buflen;
- thing->can_be_dropped_onto = can_be_dropped_onto;
- thing->ondrag = WavThing_ondrag;
- thing->ondrop = WavThing_ondrop;
+ if (thing) {
+ SDL_free(titlebar);
+ SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
+ thing->data.wav.buf = buf;
+ thing->data.wav.buflen = buflen;
+ thing->can_be_dropped_onto = can_be_dropped_onto;
+ thing->ondrag = WavThing_ondrag;
+ thing->ondrop = WavThing_ondrop;
+ }
}
SDL_free(path);
@@ -743,17 +703,21 @@ static void DestroyTexture(Texture *tex)
static Texture *CreateTexture(const char *fname)
{
- Texture *tex = (Texture *) xalloc(sizeof (Texture));
- int texw, texh;
- tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
- if (!tex->texture) {
- SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
- SDL_free(tex);
- Quit(1);
+ Texture *tex = (Texture *) SDL_calloc(1, sizeof (Texture));
+ if (!tex) {
+ SDL_Log("Out of memory!");
+ } else {
+ int texw, texh;
+ tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
+ if (!tex->texture) {
+ SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
+ SDL_free(tex);
+ return NULL;
+ }
+ SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
+ tex->w = (float) texw;
+ tex->h = (float) texh;
}
- SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
- tex->w = (float) texw;
- tex->h = (float) texh;
return tex;
}
@@ -763,9 +727,11 @@ static void DeviceThing_ondrag(Thing *thing, int button, float x, float y)
{
if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_CAPTURE)) { /* drag out a new stream. This is a UX mess. :/ */
dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y);
- dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
- SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
- dragging_thing->line_connected_to = thing;
+ if (dragging_thing) {
+ dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
+ SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
+ dragging_thing->line_connected_to = thing;
+ }
} else if (button == SDL_BUTTON_RIGHT) { /* drag out a new logical device. */
const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_CAPTURE)) ? thing->data.logdev.devid : thing->data.physdev.devid;
const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL);
@@ -929,22 +895,24 @@ static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID wh
SDL_Log("Adding logical audio device %u", (unsigned int) which);
thing = CreateThing(iscapture ? THING_LOGDEV_CAPTURE : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL);
- thing->data.logdev.devid = which;
- thing->data.logdev.iscapture = iscapture;
- thing->data.logdev.physdev = physthing;
- thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
- thing->data.logdev.postmix_lock = SDL_CreateMutex();
- if (thing->data.logdev.visualizer) {
- SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND);
- }
- thing->line_connected_to = physthing;
- thing->ontick = LogicalDeviceThing_ontick;
- thing->ondrag = DeviceThing_ondrag;
- thing->ondrop = LogicalDeviceThing_ondrop;
- thing->ondraw = LogicalDeviceThing_ondraw;
- thing->can_be_dropped_onto = can_be_dropped_onto;
+ if (thing) {
+ thing->data.logdev.devid = which;
+ thing->data.logdev.iscapture = iscapture;
+ thing->data.logdev.physdev = physthing;
+ thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
+ thing->data.logdev.postmix_lock = SDL_CreateMutex();
+ if (thing->data.logdev.visualizer) {
+ SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND);
+ }
+ thing->line_connected_to = physthing;
+ thing->ontick = LogicalDeviceThing_ontick;
+ thing->ondrag = DeviceThing_ondrag;
+ thing->ondrop = LogicalDeviceThing_ondrop;
+ thing->ondraw = LogicalDeviceThing_ondraw;
+ thing->can_be_dropped_onto = can_be_dropped_onto;
- SetLogicalDeviceTitlebar(thing);
+ SetLogicalDeviceTitlebar(thing);
+ }
return thing;
}
@@ -1002,21 +970,23 @@ static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL
SDL_Log("Adding physical audio device %u", (unsigned int) which);
thing = CreateThing(iscapture ? THING_PHYSDEV_CAPTURE : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL);
- thing->data.physdev.devid = which;
- thing->data.physdev.iscapture = iscapture;
- thing->data.physdev.name = SDL_GetAudioDeviceName(which);
- thing->ondrag = DeviceThing_ondrag;
- thing->ondrop = PhysicalDeviceThing_ondrop;
- thing->ontick = PhysicalDeviceThing_ontick;
- thing->can_be_dropped_onto = can_be_dropped_onto;
+ if (thing) {
+ thing->data.physdev.devid = which;
+ thing->data.physdev.iscapture = iscapture;
+ thing->data.physdev.name = SDL_GetAudioDeviceName(which);
+ thing->ondrag = DeviceThing_ondrag;
+ thing->ondrop = PhysicalDeviceThing_ondrop;
+ thing->ontick = PhysicalDeviceThing_ontick;
+ thing->can_be_dropped_onto = can_be_dropped_onto;
- SetPhysicalDeviceTitlebar(thing);
- if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */
- RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */
- RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */
- next_physdev_x = 0.0f;
- } else {
- next_physdev_x += physdev_texture->w * 1.5f;
+ SetPhysicalDeviceTitlebar(thing);
+ if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */
+ RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */
+ RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */
+ next_physdev_x = 0.0f;
+ } else {
+ next_physdev_x += physdev_texture->w * 1.5f;
+ }
}
return thing;
@@ -1066,157 +1036,13 @@ static void WindowResized(const int newwinw, const int newwinh)
state->window_h = newwinh;
}
-
-static void Loop(void)
-{
- SDL_Event event;
- SDL_bool saw_event = SDL_FALSE;
-
- if (app_ready_ticks == 0) {
- app_ready_ticks = SDL_GetTicks();
- }
-
- while (SDL_PollEvent(&event)) {
- Thing *thing = NULL;
-
- saw_event = SDL_TRUE;
-
- switch (event.type) {
- case SDL_EVENT_MOUSE_MOTION:
- thing = UpdateMouseOver(event.motion.x, event.motion.y);
- if ((dragging_button == -1) && event.motion.state) {
- if (event.motion.state & SDL_BUTTON_LMASK) {
- /* for people that don't have all three buttons... */
- if (ctrl_held) {
- dragging_button = SDL_BUTTON_RIGHT;
- } else if (alt_held) {
- dragging_button = SDL_BUTTON_MIDDLE;
- } else {
- dragging_button = SDL_BUTTON_LEFT;
- }
- dragging_button_real = SDL_BUTTON_LEFT;
- } else if (event.motion.state & SDL_BUTTON_RMASK) {
- dragging_button = SDL_BUTTON_RIGHT;
- dragging_button_real = SDL_BUTTON_RIGHT;
- } else if (event.motion.state & SDL_BUTTON_MMASK) {
- dragging_button = SDL_BUTTON_MIDDLE;
- dragging_button_real = SDL_BUTTON_MIDDLE;
- }
-
-
- if (dragging_button != -1) {
- dragging_thing = thing;
- if (thing && thing->ondrag) {
- thing->ondrag(thing, dragging_button, event.motion.x, event.motion.y);
- }
- }
- }
-
- droppable_highlighted_thing = NULL;
- if (dragging_thing) {
- dragging_thing->rect.x = event.motion.x - (dragging_thing->rect.w / 2);
- dragging_thing->rect.y = event.motion.y - (dragging_thing->rect.h / 2);
- if (dragging_thing->can_be_dropped_onto) {
- thing = FindThingAtPoint(event.motion.x, event.motion.y);
- if (thing) {
- int i;
- for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
- if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
- droppable_highlighted_thing = thing;
- break;
- }
- }
- }
- }
- }
- break;
-
- case SDL_EVENT_MOUSE_BUTTON_DOWN:
- thing = UpdateMouseOver(event.button.x, event.button.y);
- break;
-
- case SDL_EVENT_MOUSE_BUTTON_UP:
- if (dragging_button_real == event.button.button) {
- Thing *dropped_thing = dragging_thing;
- dragging_thing = NULL;
- dragging_button = -1;
- dragging_button_real = -1;
- if (dropped_thing && dropped_thing->ondrop) {
- dropped_thing->ondrop(dropped_thing, event.button.button, event.button.x, event.button.y);
- }
- droppable_highlighted_thing = NULL;
- }
- thing = UpdateMouseOver(event.button.x, event.button.y);
- break;
-
- case SDL_EVENT_MOUSE_WHEEL:
- UpdateMouseOver(event.wheel.mouseX, event.wheel.mouseY);
- break;
-
- case SDL_EVENT_KEY_DOWN:
- case SDL_EVENT_KEY_UP:
- ctrl_held = ((event.key.keysym.mod & SDL_KMOD_CTRL) != 0) ? SDL_TRUE : SDL_FALSE;
- alt_held = ((event.key.keysym.mod & SDL_KMOD_ALT) != 0) ? SDL_TRUE : SDL_FALSE;
- break;
-
- case SDL_EVENT_DROP_FILE:
- SDL_Log("Drop file! '%s'", event.drop.file);
- LoadWavThing(event.drop.file, event.drop.x, event.drop.y);
- /* SDLTest_CommonEvent will free the string, below. */
- break;
-
- case SDL_EVENT_WINDOW_RESIZED:
- WindowResized(event.window.data1, event.window.data2);
- break;
-
- case SDL_EVENT_AUDIO_DEVICE_ADDED:
- CreatePhysicalDeviceThing(event.adevice.which, event.adevice.iscapture);
- break;
-
- case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
- const SDL_AudioDeviceID which = event.adevice.which;
- Thing *i, *next;
- SDL_Log("Removing audio device %u", (unsigned int) which);
- for (i = things; i != NULL; i = next) {
- next = i->next;
- if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) {
- TrashThing(i);
- next = things; /* in case we mangled the list. */
- } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) {
- TrashThing(i);
- next = things; /* in case we mangled the list. */
- }
- }
- break;
- }
-
- default: break;
- }
-
- SDLTest_CommonEvent(state, &event, &done);
- }
-
- TickThings();
- Draw();
-
- if (!saw_event) {
- SDL_Delay(10);
- }
-
- #ifdef __EMSCRIPTEN__
- if (done) {
- emscripten_cancel_main_loop();
- }
- #endif
-}
-
-int main(int argc, char *argv[])
+int SDL_AppInit(int argc, char *argv[])
{
int i;
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO);
if (state == NULL) {
- Quit(1);
+ return -1;
}
state->window_flags |= SDL_WINDOW_RESIZABLE;
@@ -1234,13 +1060,13 @@ int main(int argc, char *argv[])
NULL
};
SDLTest_CommonLogUsage(state, argv[0], options);
- Quit(1);
+ return -1;
}
i += consumed;
}
if (!SDLTest_CommonInit(state)) {
- Quit(2);
+ return -1;
}
if (state->audio_id) {
@@ -1250,27 +1076,174 @@ int main(int argc, char *argv[])
SetDefaultTitleBar();
- physdev_texture = CreateTexture("physaudiodev.bmp");
- logdev_texture = CreateTexture("logaudiodev.bmp");
- audio_texture = CreateTexture("audiofile.bmp");
- trashcan_texture = CreateTexture("trashcan.bmp");
- soundboard_texture = CreateTexture("soundboard.bmp");
- soundboard_levels_texture = CreateTexture("soundboard_levels.bmp");
+ if ((physdev_texture = CreateTexture("physaudiodev.bmp")) == NULL) { return -1; }
+ if ((logdev_texture = CreateTexture("logaudiodev.bmp")) == NULL) { return -1; }
+ if ((audio_texture = CreateTexture("audiofile.bmp")) == NULL) { return -1; }
+ if ((trashcan_texture = CreateTexture("trashcan.bmp")) == NULL) { return -1; }
+ if ((soundboard_texture = CreateTexture("soundboard.bmp")) == NULL) { return -1; }
+ if ((soundboard_levels_texture = CreateTexture("soundboard_levels.bmp")) == NULL) { return -1; }
LoadStockWavThings();
CreateTrashcanThing();
CreateDefaultPhysicalDevice(SDL_FALSE);
CreateDefaultPhysicalDevice(SDL_TRUE);
-#ifdef __EMSCRIPTEN__
- emscripten_set_main_loop(Loop, 0, 1);
-#else
- while (!done) {
- Loop();
- }
-#endif
-
- Quit(0);
return 0;
}
+
+static SDL_bool saw_event = SDL_FALSE;
+
+int SDL_AppEvent(const SDL_Event *event)
+{
+ Thing *thing = NULL;
+
+ saw_event = SDL_TRUE;
+
+ switch (event->type) {
+ case SDL_EVENT_MOUSE_MOTION:
+ thing = UpdateMouseOver(event->motion.x, event->motion.y);
+ if ((dragging_button == -1) && event->motion.state) {
+ if (event->motion.state & SDL_BUTTON_LMASK) {
+ /* for people that don't have all three buttons... */
+ if (ctrl_held) {
+ dragging_button = SDL_BUTTON_RIGHT;
+ } else if (alt_held) {
+ dragging_button = SDL_BUTTON_MIDDLE;
+ } else {
+ dragging_button = SDL_BUTTON_LEFT;
+ }
+ dragging_button_real = SDL_BUTTON_LEFT;
+ } else if (event->motion.state & SDL_BUTTON_RMASK) {
+ dragging_button = SDL_BUTTON_RIGHT;
+ dragging_button_real = SDL_BUTTON_RIGHT;
+ } else if (event->motion.state & SDL_BUTTON_MMASK) {
+ dragging_button = SDL_BUTTON_MIDDLE;
+ dragging_button_real = SDL_BUTTON_MIDDLE;
+ }
+
+ if (dragging_button != -1) {
+ dragging_thing = thing;
+ if (thing && thing->ondrag) {
+ thing->ondrag(thing, dragging_button, event->motion.x, event->motion.y);
+ }
+ }
+ }
+
+ droppable_highlighted_thing = NULL;
+ if (dragging_thing) {
+ dragging_thing->rect.x = event->motion.x - (dragging_thing->rect.w / 2);
+ dragging_thing->rect.y = event->motion.y - (dragging_thing->rect.h / 2);
+ if (dragging_thing->can_be_dropped_onto) {
+ thing = FindThingAtPoint(event->motion.x, event->motion.y);
+ if (thing) {
+ int i;
+ for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
+ if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
+ droppable_highlighted_thing = thing;
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ thing = UpdateMouseOver(event->button.x, event->button.y);
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ if (dragging_button_real == event->button.button) {
+ Thing *dropped_thing = dragging_thing;
+ dragging_thing = NULL;
+ dragging_button = -1;
+ dragging_button_real = -1;
+ if (dropped_thing && dropped_thing->ondrop) {
+ dropped_thing->ondrop(dropped_thing, event->button.button, event->button.x, event->button.y);
+ }
+ droppable_highlighted_thing = NULL;
+ }
+ thing = UpdateMouseOver(event->button.x, event->button.y);
+ break;
+
+ case SDL_EVENT_MOUSE_WHEEL:
+ UpdateMouseOver(event->wheel.mouseX, event->wheel.mouseY);
+ break;
+
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP:
+ ctrl_held = ((event->key.keysym.mod & SDL_KMOD_CTRL) != 0) ? SDL_TRUE : SDL_FALSE;
+ alt_held = ((event->key.keysym.mod & SDL_KMOD_ALT) != 0) ? SDL_TRUE : SDL_FALSE;
+ break;
+
+ case SDL_EVENT_DROP_FILE:
+ SDL_Log("Drop file! '%s'", event->drop.file);
+ LoadWavThing(event->drop.file, event->drop.x, event->drop.y);
+ /* SDL frees event->drop.file for you when you use SDL_AppEvent(). */
+ break;
+
+ case SDL_EVENT_WINDOW_RESIZED:
+ WindowResized(event->window.data1, event->window.data2);
+ break;
+
+ case SDL_EVENT_AUDIO_DEVICE_ADDED:
+ CreatePhysicalDeviceThing(event->adevice.which, event->adevice.iscapture);
+ break;
+
+ case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
+ const SDL_AudioDeviceID which = event->adevice.which;
+ Thing *i, *next;
+ SDL_Log("Removing audio device %u", (unsigned int) which);
+ for (i = things; i != NULL; i = next) {
+ next = i->next;
+ if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) {
+ TrashThing(i);
+ next = things; /* in case we mangled the list. */
+ } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) {
+ TrashThing(i);
+ next = things; /* in case we mangled the list. */
+ }
+ }
+ break;
+ }
+
+ default: break;
+ }
+
+ return SDLTest_CommonEventMainCallbacks(state, event);
+}
+
+int SDL_AppIterate(void)
+{
+ if (app_ready_ticks == 0) {
+ app_ready_ticks = SDL_GetTicks();
+ }
+
+ TickThings();
+ Draw();
+
+ if (saw_event) {
+ saw_event = SDL_FALSE; /* reset this so we know when SDL_AppEvent() runs again */
+ } else {
+ SDL_Delay(10);
+ }
+
+ return 0; /* keep going. */
+}
+
+void SDL_AppQuit(void)
+{
+ while (things != NULL) {
+ DestroyThing(things); /* make sure all the audio devices are closed, etc. */
+ }
+
+ DestroyTexture(physdev_texture);
+ DestroyTexture(logdev_texture);
+ DestroyTexture(audio_texture);
+ DestroyTexture(trashcan_texture);
+ DestroyTexture(soundboard_texture);
+ DestroyTexture(soundboard_levels_texture);
+ SDLTest_CommonQuit(state);
+}
+
diff --git a/test/testaudiocapture.c b/test/testaudiocapture.c
index efc4be101..9b44b6573 100644
--- a/test/testaudiocapture.c
+++ b/test/testaudiocapture.c
@@ -10,94 +10,20 @@
freely.
*/
-#include
-
+#define SDL_MAIN_USE_CALLBACKS 1
#include
#include
#include
-#ifdef __EMSCRIPTEN__
-#include
-#endif
-
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_AudioStream *stream_in = NULL;
static SDL_AudioStream *stream_out = NULL;
-static int done = 0;
+static SDLTest_CommonState *state = NULL;
-static void loop(void)
-{
- const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in);
- const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out);
- SDL_bool please_quit = SDL_FALSE;
- SDL_Event e;
-
- while (SDL_PollEvent(&e)) {
- if (e.type == SDL_EVENT_QUIT) {
- please_quit = SDL_TRUE;
- } else if (e.type == SDL_EVENT_KEY_DOWN) {
- if (e.key.keysym.sym == SDLK_ESCAPE) {
- please_quit = SDL_TRUE;
- }
- } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
- if (e.button.button == 1) {
- SDL_PauseAudioDevice(devid_out);
- SDL_ResumeAudioDevice(devid_in);
- }
- } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
- if (e.button.button == 1) {
- SDL_PauseAudioDevice(devid_in);
- SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */
- SDL_ResumeAudioDevice(devid_out);
- }
- }
- }
-
- if (!SDL_AudioDevicePaused(devid_in)) {
- SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
- } else {
- SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
- }
- SDL_RenderClear(renderer);
- SDL_RenderPresent(renderer);
-
- /* Feed any new data we captured to the output stream. It'll play when we unpause the device. */
- while (!please_quit && (SDL_GetAudioStreamAvailable(stream_in) > 0)) {
- Uint8 buf[1024];
- const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf));
- if (br < 0) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError());
- please_quit = 1;
- } else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError());
- please_quit = 1;
- }
- }
-
- if (please_quit) {
- /* stop playing back, quit. */
- SDL_Log("Shutting down.\n");
- SDL_CloseAudioDevice(devid_in);
- SDL_CloseAudioDevice(devid_out);
- SDL_DestroyAudioStream(stream_in);
- SDL_DestroyAudioStream(stream_out);
- SDL_DestroyRenderer(renderer);
- SDL_DestroyWindow(window);
- SDL_Quit();
-#ifdef __EMSCRIPTEN__
- emscripten_cancel_main_loop();
-#endif
- /* Let 'main()' return normally */
- done = 1;
- return;
- }
-}
-
-int main(int argc, char **argv)
+int SDL_AppInit(int argc, char **argv)
{
SDL_AudioDeviceID *devices;
- SDLTest_CommonState *state;
SDL_AudioSpec outspec;
SDL_AudioSpec inspec;
SDL_AudioDeviceID device;
@@ -105,6 +31,9 @@ int main(int argc, char **argv)
const char *devname = NULL;
int i;
+ /* this doesn't have to run very much, so give up tons of CPU time between iterations. */
+ SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "15");
+
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (state == NULL) {
@@ -128,7 +57,7 @@ int main(int argc, char **argv)
if (consumed <= 0) {
static const char *options[] = { "[device_name]", NULL };
SDLTest_CommonLogUsage(state, argv[0], options);
- exit(1);
+ return -1;
}
i += consumed;
@@ -175,20 +104,17 @@ int main(int argc, char **argv)
device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, NULL);
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for playback: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
}
SDL_PauseAudioDevice(device);
SDL_GetAudioDeviceFormat(device, &outspec, NULL);
stream_out = SDL_CreateAudioStream(&outspec, &outspec);
if (!stream_out) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for playback: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
} else if (SDL_BindAudioStream(device, stream_out) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for playback: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
}
SDL_Log("Opening capture device %s%s%s...\n",
@@ -199,38 +125,89 @@ int main(int argc, char **argv)
device = SDL_OpenAudioDevice(want_device, NULL);
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for capture: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
}
SDL_PauseAudioDevice(device);
SDL_GetAudioDeviceFormat(device, &inspec, NULL);
stream_in = SDL_CreateAudioStream(&inspec, &inspec);
if (!stream_in) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for capture: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
} else if (SDL_BindAudioStream(device, stream_in) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for capture: %s!\n", SDL_GetError());
- SDL_Quit();
- exit(1);
+ return -1;
}
SDL_SetAudioStreamFormat(stream_in, NULL, &outspec); /* make sure we output at the playback format. */
SDL_Log("Ready! Hold down mouse or finger to record!\n");
-#ifdef __EMSCRIPTEN__
- emscripten_set_main_loop(loop, 0, 1);
-#else
- while (!done) {
- loop();
- if (!done) {
- SDL_Delay(16);
- }
- }
-#endif
-
- SDLTest_CommonDestroyState(state);
-
return 0;
}
+
+int SDL_AppEvent(const SDL_Event *event)
+{
+ if (event->type == SDL_EVENT_QUIT) {
+ return 1; /* terminate as success. */
+ } else if (event->type == SDL_EVENT_KEY_DOWN) {
+ if (event->key.keysym.sym == SDLK_ESCAPE) {
+ return 1; /* terminate as success. */
+ }
+ } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
+ if (event->button.button == 1) {
+ SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_out));
+ SDL_FlushAudioStream(stream_out); /* so no samples are held back for resampling purposes. */
+ SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_in));
+ }
+ } else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
+ if (event->button.button == 1) {
+ SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_in));
+ SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */
+ SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_out));
+ }
+ }
+ return 0; /* keep going. */
+}
+
+int SDL_AppIterate(void)
+{
+ if (!SDL_AudioDevicePaused(SDL_GetAudioStreamDevice(stream_in))) {
+ SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
+ } else {
+ SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+ }
+ SDL_RenderClear(renderer);
+ SDL_RenderPresent(renderer);
+
+ /* Feed any new data we captured to the output stream. It'll play when we unpause the device. */
+ while (SDL_GetAudioStreamAvailable(stream_in) > 0) {
+ Uint8 buf[1024];
+ const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf));
+ if (br < 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError());
+ return -1; /* quit the app, report failure. */
+ } else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError());
+ return -1; /* quit the app, report failure. */
+ }
+ }
+
+ return 0; /* keep app going. */
+}
+
+void SDL_AppQuit(void)
+{
+ SDL_Log("Shutting down.\n");
+ const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in);
+ const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out);
+ SDL_CloseAudioDevice(devid_in); /* !!! FIXME: use SDL_OpenAudioDeviceStream instead so we can dump this. */
+ SDL_CloseAudioDevice(devid_out);
+ SDL_DestroyAudioStream(stream_in);
+ SDL_DestroyAudioStream(stream_out);
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDLTest_CommonDestroyState(state);
+ SDL_Quit();
+}
+
+
diff --git a/test/testsprite.c b/test/testsprite.c
index 9afa6f0da..217dc181b 100644
--- a/test/testsprite.c
+++ b/test/testsprite.c
@@ -14,10 +14,7 @@
#include
#include
-#ifdef __EMSCRIPTEN__
-#include
-#endif
-
+#define SDL_MAIN_USE_CALLBACKS 1
#include
#include
#include
@@ -48,20 +45,12 @@ static SDL_bool suspend_when_occluded;
/* -1: infinite random moves (default); >=0: enables N deterministic moves */
static int iterations = -1;
-static int done;
-
-/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
-static void
-quit(int rc)
+void SDL_AppQuit(void)
{
SDL_free(sprites);
SDL_free(positions);
SDL_free(velocities);
SDLTest_CommonQuit(state);
- /* Let 'main()' return normally */
- if (rc != 0) {
- exit(rc);
- }
}
static int LoadSprite(const char *file)
@@ -395,17 +384,17 @@ static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite)
SDL_RenderPresent(renderer);
}
-static void loop(void)
+int SDL_AppEvent(const SDL_Event *event)
+{
+ return SDLTest_CommonEventMainCallbacks(state, event);
+}
+
+int SDL_AppIterate(void)
{
Uint64 now;
int i;
int active_windows = 0;
- SDL_Event event;
- /* Check for events */
- while (SDL_PollEvent(&event)) {
- SDLTest_CommonEvent(state, &event, &done);
- }
for (i = 0; i < state->num_windows; ++i) {
if (state->windows[i] == NULL ||
(suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
@@ -414,14 +403,9 @@ static void loop(void)
++active_windows;
MoveSprites(state->renderers[i], sprites[i]);
}
-#ifdef __EMSCRIPTEN__
- if (done) {
- emscripten_cancel_main_loop();
- }
-#endif
/* If all windows are occluded, throttle the event polling to 15hz. */
- if (!done && !active_windows) {
+ if (!active_windows) {
SDL_DelayNS(SDL_NS_PER_SECOND / 15);
}
@@ -435,9 +419,11 @@ static void loop(void)
next_fps_check = now + fps_check_delay;
frames = 0;
}
+
+ return 0; /* keep going */
}
-int main(int argc, char *argv[])
+int SDL_AppInit(int argc, char *argv[])
{
int i;
Uint64 seed;
@@ -449,7 +435,7 @@ int main(int argc, char *argv[])
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (state == NULL) {
- return 1;
+ return -1;
}
for (i = 1; i < argc;) {
@@ -532,12 +518,12 @@ int main(int argc, char *argv[])
NULL
};
SDLTest_CommonLogUsage(state, argv[0], options);
- quit(1);
+ return -1;
}
i += consumed;
}
if (!SDLTest_CommonInit(state)) {
- quit(2);
+ return -1;
}
/* Create the windows, initialize the renderers, and load the textures */
@@ -545,7 +531,7 @@ int main(int argc, char *argv[])
(SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites));
if (sprites == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
- quit(2);
+ return -1;
}
for (i = 0; i < state->num_windows; ++i) {
SDL_Renderer *renderer = state->renderers[i];
@@ -553,7 +539,7 @@ int main(int argc, char *argv[])
SDL_RenderClear(renderer);
}
if (LoadSprite(icon) < 0) {
- quit(2);
+ return -1;
}
/* Allocate memory for the sprite info */
@@ -561,7 +547,7 @@ int main(int argc, char *argv[])
velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities));
if (positions == NULL || velocities == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
- quit(2);
+ return -1;
}
/* Position sprites and set their velocities using the fuzzer */
@@ -586,19 +572,10 @@ int main(int argc, char *argv[])
}
}
- /* Main render loop */
+ /* Main render loop in SDL_AppIterate will begin when this function returns. */
frames = 0;
next_fps_check = SDL_GetTicks() + fps_check_delay;
- done = 0;
-#ifdef __EMSCRIPTEN__
- emscripten_set_main_loop(loop, 0, 1);
-#else
- while (!done) {
- loop();
- }
-#endif
-
- quit(0);
return 0;
}
+