From d8d854298c1b9842a4a83ba3ddd11a3cf1a890ce Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 18 May 2013 12:48:50 -0700 Subject: [PATCH] Added mobile application events, with implementations for iOS and Android --- README.iOS | 63 +++++++++++++++++++ .../src/org/libsdl/app/SDLActivity.java | 16 +++-- include/SDL_events.h | 36 ++++++++++- src/core/android/SDL_android.cpp | 20 +++++- src/events/SDL_events.c | 22 +++++-- src/events/SDL_events_c.h | 1 + src/events/SDL_quit.c | 10 +-- src/video/uikit/SDL_uikitappdelegate.m | 52 ++++++++------- src/video/uikit/SDL_uikitopengles.m | 6 +- 9 files changed, 182 insertions(+), 44 deletions(-) diff --git a/README.iOS b/README.iOS index a1844a3fb..60f3391dc 100644 --- a/README.iOS +++ b/README.iOS @@ -54,6 +54,69 @@ Here is a more manual method: 4. Remove the ApplicationDelegate.h and ApplicationDelegate.m files -- SDL for iPhone provides its own UIApplicationDelegate. Remove MainWindow.xib -- SDL for iPhone produces its user interface programmatically. 5. Delete the contents of main.m and program your app as a regular SDL program instead. You may replace main.m with your own main.c, but you must tell XCode not to use the project prefix file, as it includes Objective-C code. +============================================================================== +Notes -- Application events +============================================================================== + +On iOS the application goes through a fixed life cycle and you will get +notifications of state changes via application events. When these events +are delivered you must handle them in an event callback because the OS may +not give you any processing time after the events are delivered. + +e.g. + +int HandleAppEvents(void *userdata, SDL_Event *event) +{ + switch (event->type) + { + case SDL_APP_TERMINATING: + /* Terminate the app. + Shut everything down before returning from this function. + */ + return 0; + case SDL_APP_LOWMEMORY: + /* You will get this when your app is paused and iOS wants more memory. + Release as much memory as possible. + */ + return 0; + case SDL_APP_WILLENTERBACKGROUND: + /* Prepare your app to go into the background. Stop loops, etc. + This gets called when the user hits the home button, or gets a call. + */ + return 0; + case SDL_APP_DIDENTERBACKGROUND: + /* This will get called if the user accepted whatever sent your app to the background. + If the user got a phone call and canceled it, you'll instead get an SDL_APP_DIDENTERFOREGROUND event and restart your loops. + When you get this, you have 5 seconds to save all your state or the app will be terminated. + Your app is NOT active at this point. + */ + return 0; + case SDL_APP_WILLENTERFOREGROUND: + /* This call happens when your app is coming back to the foreground. + Restore all your state here. + */ + return 0; + case SDL_APP_DIDENTERFOREGROUND: + /* Restart your loops here. + Your app is interactive and getting CPU again. + */ + return 0; + default: + /* No special processing, add it to the event queue */ + return 1; + } +} + +int main(int argc, char *argv[]) +{ + SDL_SetEventFilter(HandleAppEvents, NULL); + + ... run your main loop + + return 0; +} + + ============================================================================== Notes -- Accelerometer as Joystick ============================================================================== diff --git a/android-project/src/org/libsdl/app/SDLActivity.java b/android-project/src/org/libsdl/app/SDLActivity.java index fed65c510..04bbeff15 100644 --- a/android-project/src/org/libsdl/app/SDLActivity.java +++ b/android-project/src/org/libsdl/app/SDLActivity.java @@ -78,17 +78,26 @@ protected void onCreate(Bundle savedInstanceState) { } // Events - /*protected void onPause() { + @Override + protected void onPause() { Log.v("SDL", "onPause()"); super.onPause(); // Don't call SDLActivity.nativePause(); here, it will be called by SDLSurface::surfaceDestroyed } + @Override protected void onResume() { Log.v("SDL", "onResume()"); super.onResume(); // Don't call SDLActivity.nativeResume(); here, it will be called via SDLSurface::surfaceChanged->SDLActivity::startApp - }*/ + } + + @Override + public void onLowMemory() { + Log.v("SDL", "onLowMemory()"); + super.onLowMemory(); + SDLActivity.nativeLowMemory(); + } @Override protected void onDestroy() { @@ -180,6 +189,7 @@ boolean sendCommand(int command, Object data) { // C functions we call public static native void nativeInit(); + public static native void nativeLowMemory(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); @@ -600,8 +610,6 @@ public void surfaceChanged(SurfaceHolder holder, public void onDraw(Canvas canvas) {} - - // Key events @Override public boolean onKey(View v, int keyCode, KeyEvent event) { diff --git a/include/SDL_events.h b/include/SDL_events.h index 6e6a5c8dc..65ba6b158 100644 --- a/include/SDL_events.h +++ b/include/SDL_events.h @@ -61,6 +61,32 @@ typedef enum /* Application events */ SDL_QUIT = 0x100, /**< User-requested quit */ + /* These application events have special meaning on iOS, see README.iOS for details */ + SDL_APP_TERMINATING, /**< The application is being terminated by the OS + Called on iOS in applicationWillTerminate() + Called on Android in onDestroy() + */ + SDL_APP_LOWMEMORY, /**< The application is low on memory, free memory if possible. + Called on iOS in applicationDidReceiveMemoryWarning() + Called on Android in onLowMemory() + */ + SDL_APP_WILLENTERBACKGROUND, /**< The application is about to enter the background + Called on iOS in applicationWillResignActive() + Called on Android in onPause() + */ + SDL_APP_DIDENTERBACKGROUND, /**< The application did enter the background and may not get CPU for some time + Called on iOS in applicationDidEnterBackground() + Called on Android in onPause() + */ + SDL_APP_WILLENTERFOREGROUND, /**< The application is about to enter the foreground + Called on iOS in applicationWillEnterForeground() + Called on Android in onResume() + */ + SDL_APP_DIDENTERFOREGROUND, /**< The application is now interactive + Called on iOS in applicationDidBecomeActive() + Called on Android in onResume() + */ + /* Window events */ SDL_WINDOWEVENT = 0x200, /**< Window state change */ SDL_SYSWMEVENT, /**< System specific event */ @@ -109,7 +135,7 @@ typedef enum /* Drag and drop events */ SDL_DROPFILE = 0x1000, /**< The system requests a file open */ - + /** Events ::SDL_USEREVENT through ::SDL_LASTEVENT are for your use, * and should be allocated with SDL_RegisterEvents() */ @@ -427,6 +453,14 @@ typedef struct SDL_QuitEvent Uint32 timestamp; } SDL_QuitEvent; +/** + * \brief OS Specific event + */ +typedef struct SDL_OSEvent +{ + Uint32 type; /**< ::SDL_QUIT */ + Uint32 timestamp; +} SDL_OSEvent; /** * \brief A user-defined event type (event.user.*) diff --git a/src/core/android/SDL_android.cpp b/src/core/android/SDL_android.cpp index ceb3555b0..cf0e0c129 100644 --- a/src/core/android/SDL_android.cpp +++ b/src/core/android/SDL_android.cpp @@ -181,12 +181,20 @@ extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel( bHasNewData = true; } +// Low memory +extern "C" void Java_org_libsdl_app_SDLActivity_nativeLowMemory( + JNIEnv* env, jclass cls) +{ + SDL_SendAppEvent(SDL_APP_LOWMEMORY); +} + // Quit extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit( JNIEnv* env, jclass cls) { // Inject a SDL_QUIT event SDL_SendQuit(); + SDL_SendAppEvent(SDL_APP_TERMINATING); } // Pause @@ -199,12 +207,20 @@ extern "C" void Java_org_libsdl_app_SDLActivity_nativePause( SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); } + + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); + SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); + SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); } // Resume extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume( JNIEnv* env, jclass cls) { + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); + SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); + SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); + if (Android_Window) { /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context * We can't restore the GL Context here because it needs to be done on the SDL main thread @@ -616,7 +632,9 @@ static int Android_JNI_FileOpen(SDL_RWops* ctx) if (false) { fallback: - __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); + // Disabled log message because of spam on the Nexus 7 + //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); + /* Try the old method using InputStream */ ctx->hidden.androidio.assetFileDescriptorRef = NULL; diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index ecba3bb5b..d6bb22e06 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -111,6 +111,7 @@ SDL_StopEventLoop(void) SDL_event_watchers = tmp->next; SDL_free(tmp); } + SDL_EventOK = NULL; } /* This function (and associated calls) may be called more than once */ @@ -133,8 +134,7 @@ SDL_StartEventLoop(void) } #endif /* !SDL_THREADS_DISABLED */ - /* No filter to start with, process most event types */ - SDL_EventOK = NULL; + /* Process most event types */ SDL_EventState(SDL_TEXTINPUT, SDL_DISABLE); SDL_EventState(SDL_TEXTEDITING, SDL_DISABLE); SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE); @@ -365,7 +365,9 @@ int SDL_PushEvent(SDL_Event * event) { SDL_EventWatcher *curr; + event->common.timestamp = SDL_GetTicks(); + if (SDL_EventOK && !SDL_EventOK(SDL_EventOKParam, event)) { return 0; } @@ -516,8 +518,20 @@ SDL_RegisterEvents(int numevents) return event_base; } -/* This is a generic event handler. - */ +int +SDL_SendAppEvent(SDL_EventType eventType) +{ + int posted; + + posted = 0; + if (SDL_GetEventState(eventType) == SDL_ENABLE) { + SDL_Event event; + event.type = eventType; + posted = (SDL_PushEvent(&event) > 0); + } + return (posted); +} + int SDL_SendSysWMEvent(SDL_SysWMmsg * message) { diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index 7d6ab3b19..f365ee3ed 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -36,6 +36,7 @@ extern int SDL_StartEventLoop(void); extern void SDL_StopEventLoop(void); extern void SDL_QuitInterrupt(void); +extern int SDL_SendAppEvent(SDL_EventType eventType); extern int SDL_SendSysWMEvent(SDL_SysWMmsg * message); extern int SDL_QuitInit(void); diff --git a/src/events/SDL_quit.c b/src/events/SDL_quit.c index 8c1dcf574..5e9f8ad85 100644 --- a/src/events/SDL_quit.c +++ b/src/events/SDL_quit.c @@ -114,15 +114,7 @@ SDL_QuitQuit(void) int SDL_SendQuit(void) { - int posted; - - posted = 0; - if (SDL_GetEventState(SDL_QUIT) == SDL_ENABLE) { - SDL_Event event; - event.type = SDL_QUIT; - posted = (SDL_PushEvent(&event) > 0); - } - return (posted); + return SDL_SendAppEvent(SDL_QUIT); } /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 5337f619e..11e74025e 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -228,42 +228,48 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( - (void)applicationWillTerminate:(UIApplication *)application { - SDL_SendQuit(); - /* hack to prevent automatic termination. See SDL_uikitevents.m for details */ - longjmp(*(jump_env()), 1); + SDL_SendAppEvent(SDL_APP_TERMINATING); } -- (void) applicationWillResignActive:(UIApplication*)application +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - //NSLog(@"%@", NSStringFromSelector(_cmd)); + SDL_SendAppEvent(SDL_APP_LOWMEMORY); +} - // Send every window on every screen a MINIMIZED event. +- (void) applicationWillResignActive:(UIApplication*)application +{ SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (!_this) { - return; + if (_this) { + SDL_Window *window; + for (window = _this->windows; window != nil; window = window->next) { + SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); + SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); + } } + SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); +} - SDL_Window *window; - for (window = _this->windows; window != nil; window = window->next) { - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); - } +- (void) applicationDidEnterBackground:(UIApplication*)application +{ + SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); +} + +- (void) applicationWillEnterForeground:(UIApplication*)application +{ + SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); } - (void) applicationDidBecomeActive:(UIApplication*)application { - //NSLog(@"%@", NSStringFromSelector(_cmd)); + SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); - // Send every window on every screen a RESTORED event. SDL_VideoDevice *_this = SDL_GetVideoDevice(); - if (!_this) { - return; - } - - SDL_Window *window; - for (window = _this->windows; window != nil; window = window->next) { - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0); + if (_this) { + SDL_Window *window; + for (window = _this->windows; window != nil; window = window->next) { + SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); + SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0); + } } } diff --git a/src/video/uikit/SDL_uikitopengles.m b/src/video/uikit/SDL_uikitopengles.m index f3efab2a7..4fc1a2773 100644 --- a/src/video/uikit/SDL_uikitopengles.m +++ b/src/video/uikit/SDL_uikitopengles.m @@ -91,8 +91,10 @@ void UIKit_GL_SwapWindow(_THIS, SDL_Window * window) } [data->view swapBuffers]; - /* we need to let the event cycle run, or the OS won't update the OpenGL view! */ - SDL_PumpEvents(); + /* You need to pump events in order for the OS to make changes visible. + We don't pump events here because we don't want iOS application events + (low memory, terminate, etc.) to happen inside low level rendering. + */ } SDL_GLContext UIKit_GL_CreateContext(_THIS, SDL_Window * window)