Added mobile application events, with implementations for iOS and Android
authorSam Lantinga <slouken@libsdl.org>
Sat, 18 May 2013 12:48:50 -0700
changeset 7190 11612d544fcd
parent 7189 414be1d64060
child 7191 75360622e65f
Added mobile application events, with implementations for iOS and Android
README.iOS
android-project/src/org/libsdl/app/SDLActivity.java
include/SDL_events.h
src/core/android/SDL_android.cpp
src/events/SDL_events.c
src/events/SDL_events_c.h
src/events/SDL_quit.c
src/video/uikit/SDL_uikitappdelegate.m
src/video/uikit/SDL_uikitopengles.m
--- a/README.iOS	Sat May 18 09:35:09 2013 -0700
+++ b/README.iOS	Sat May 18 12:48:50 2013 -0700
@@ -55,6 +55,69 @@
 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
 ==============================================================================
 
--- a/android-project/src/org/libsdl/app/SDLActivity.java	Sat May 18 09:35:09 2013 -0700
+++ b/android-project/src/org/libsdl/app/SDLActivity.java	Sat May 18 12:48:50 2013 -0700
@@ -78,17 +78,26 @@
     }
 
     // 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 @@
 
     // 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 onDraw(Canvas canvas) {}
 
 
-
-
     // Key events
     @Override
     public boolean onKey(View  v, int keyCode, KeyEvent event) {
--- a/include/SDL_events.h	Sat May 18 09:35:09 2013 -0700
+++ b/include/SDL_events.h	Sat May 18 12:48:50 2013 -0700
@@ -61,6 +61,32 @@
     /* 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 @@
 
     /* 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 @@
     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.*)
--- a/src/core/android/SDL_android.cpp	Sat May 18 09:35:09 2013 -0700
+++ b/src/core/android/SDL_android.cpp	Sat May 18 12:48:50 2013 -0700
@@ -181,12 +181,20 @@
     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 @@
         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 @@
 
     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;
 
--- a/src/events/SDL_events.c	Sat May 18 09:35:09 2013 -0700
+++ b/src/events/SDL_events.c	Sat May 18 12:48:50 2013 -0700
@@ -111,6 +111,7 @@
         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 @@
     }
 #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 @@
 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 @@
     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)
 {
--- a/src/events/SDL_events_c.h	Sat May 18 09:35:09 2013 -0700
+++ b/src/events/SDL_events_c.h	Sat May 18 12:48:50 2013 -0700
@@ -36,6 +36,7 @@
 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);
--- a/src/events/SDL_quit.c	Sat May 18 09:35:09 2013 -0700
+++ b/src/events/SDL_quit.c	Sat May 18 12:48:50 2013 -0700
@@ -114,15 +114,7 @@
 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: */
--- a/src/video/uikit/SDL_uikitappdelegate.m	Sat May 18 09:35:09 2013 -0700
+++ b/src/video/uikit/SDL_uikitappdelegate.m	Sat May 18 12:48:50 2013 -0700
@@ -228,42 +228,48 @@
 
 - (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)applicationDidReceiveMemoryWarning:(UIApplication *)application
+{
+    SDL_SendAppEvent(SDL_APP_LOWMEMORY);
 }
 
 - (void) applicationWillResignActive:(UIApplication*)application
 {
-    //NSLog(@"%@", NSStringFromSelector(_cmd));
-
-    // Send every window on every screen a MINIMIZED event.
     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);
+        }
     }
 }
 
--- a/src/video/uikit/SDL_uikitopengles.m	Sat May 18 09:35:09 2013 -0700
+++ b/src/video/uikit/SDL_uikitopengles.m	Sat May 18 12:48:50 2013 -0700
@@ -91,8 +91,10 @@
     }
     [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)