1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23#include "SDL_main_callbacks.h"
24
25static SDL_AppEvent_func SDL_main_event_callback;
26static SDL_AppIterate_func SDL_main_iteration_callback;
27static SDL_AppQuit_func SDL_main_quit_callback;
28static 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.
29static void *SDL_main_appstate = NULL;
30
31// Return true if this event needs to be processed before returning from the event watcher
32static bool ShouldDispatchImmediately(SDL_Event *event)
33{
34 switch (event->type) {
35 case SDL_EVENT_TERMINATING:
36 case SDL_EVENT_LOW_MEMORY:
37 case SDL_EVENT_WILL_ENTER_BACKGROUND:
38 case SDL_EVENT_DID_ENTER_BACKGROUND:
39 case SDL_EVENT_WILL_ENTER_FOREGROUND:
40 case SDL_EVENT_DID_ENTER_FOREGROUND:
41 return true;
42 default:
43 return false;
44 }
45}
46
47static void SDL_DispatchMainCallbackEvent(SDL_Event *event)
48{
49 if (SDL_GetAtomicInt(&apprc) == SDL_APP_CONTINUE) { // if already quitting, don't send the event to the app.
50 SDL_CompareAndSwapAtomicInt(&apprc, SDL_APP_CONTINUE, SDL_main_event_callback(SDL_main_appstate, event));
51 }
52}
53
54static void SDL_DispatchMainCallbackEvents(void)
55{
56 SDL_Event events[16];
57
58 for (;;) {
59 int count = SDL_PeepEvents(events, SDL_arraysize(events), SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST);
60 if (count <= 0) {
61 break;
62 }
63 for (int i = 0; i < count; ++i) {
64 SDL_Event *event = &events[i];
65 if (!ShouldDispatchImmediately(event)) {
66 SDL_DispatchMainCallbackEvent(event);
67 }
68 }
69 }
70}
71
72static bool SDLCALL SDL_MainCallbackEventWatcher(void *userdata, SDL_Event *event)
73{
74 if (ShouldDispatchImmediately(event)) {
75 // Make sure any currently queued events are processed then dispatch this before continuing
76 SDL_DispatchMainCallbackEvents();
77 SDL_DispatchMainCallbackEvent(event);
78
79 // Make sure that we quit if we get a terminating event
80 if (event->type == SDL_EVENT_TERMINATING) {
81 SDL_CompareAndSwapAtomicInt(&apprc, SDL_APP_CONTINUE, SDL_APP_SUCCESS);
82 }
83 } else {
84 // We'll process this event later from the main event queue
85 }
86 return true;
87}
88
89bool SDL_HasMainCallbacks(void)
90{
91 if (SDL_main_iteration_callback) {
92 return true;
93 }
94 return false;
95}
96
97SDL_AppResult SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
98{
99 SDL_main_iteration_callback = appiter;
100 SDL_main_event_callback = appevent;
101 SDL_main_quit_callback = appquit;
102 SDL_SetAtomicInt(&apprc, SDL_APP_CONTINUE);
103
104 const SDL_AppResult rc = appinit(&SDL_main_appstate, argc, argv);
105 if (SDL_CompareAndSwapAtomicInt(&apprc, SDL_APP_CONTINUE, rc) && (rc == SDL_APP_CONTINUE)) { // bounce if SDL_AppInit already said abort, otherwise...
106 // make sure we definitely have events initialized, even if the app didn't do it.
107 if (!SDL_InitSubSystem(SDL_INIT_EVENTS)) {
108 SDL_SetAtomicInt(&apprc, SDL_APP_FAILURE);
109 return SDL_APP_FAILURE;
110 }
111
112 if (!SDL_AddEventWatch(SDL_MainCallbackEventWatcher, NULL)) {
113 SDL_SetAtomicInt(&apprc, SDL_APP_FAILURE);
114 return SDL_APP_FAILURE;
115 }
116 }
117
118 return (SDL_AppResult)SDL_GetAtomicInt(&apprc);
119}
120
121SDL_AppResult SDL_IterateMainCallbacks(bool pump_events)
122{
123 if (pump_events) {
124 SDL_PumpEvents();
125 }
126 SDL_DispatchMainCallbackEvents();
127
128 SDL_AppResult rc = (SDL_AppResult)SDL_GetAtomicInt(&apprc);
129 if (rc == SDL_APP_CONTINUE) {
130 rc = SDL_main_iteration_callback(SDL_main_appstate);
131 if (!SDL_CompareAndSwapAtomicInt(&apprc, SDL_APP_CONTINUE, rc)) {
132 rc = (SDL_AppResult)SDL_GetAtomicInt(&apprc); // something else already set a quit result, keep that.
133 }
134 }
135 return rc;
136}
137
138void SDL_QuitMainCallbacks(SDL_AppResult result)
139{
140 SDL_RemoveEventWatch(SDL_MainCallbackEventWatcher, NULL);
141 SDL_main_quit_callback(SDL_main_appstate, result);
142 SDL_main_appstate = NULL; // just in case.
143
144 // for symmetry, you should explicitly Quit what you Init, but we might come through here uninitialized and SDL_Quit() will clear everything anyhow.
145 //SDL_QuitSubSystem(SDL_INIT_EVENTS);
146
147 SDL_Quit();
148}
149
150