Mercurial > repos > blastem
changeset 1840:3d0b20e9a187
Merge
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 18 Apr 2019 19:48:04 -0700 |
parents | 0c1491818f4b (current diff) 78abbabfd58d (diff) |
children | 5d10b8494b02 |
files | Makefile |
diffstat | 22 files changed, 2251 insertions(+), 545 deletions(-) [+] |
line wrap: on
line diff
--- a/Android.mk Thu Apr 18 19:47:50 2019 -0700 +++ b/Android.mk Thu Apr 18 19:48:04 2019 -0700 @@ -8,14 +8,21 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include -LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DDISABLE_OPENGL +LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DUSE_GLES # Add your application source files here... LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ 68kinst.c debug.c gst.c psg.c z80_to_x86.c backend.c io.c render_sdl.c \ tern.c backend_x86.c gdb_remote.c m68k_core.c romdb.c m68k_core_x86.c \ util.c wave.c blastem.c gen.c mem.c vdp.c ym2612.c config.c gen_x86.c \ - terminal.c z80inst.c menu.c arena.c + terminal.c z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c \ + zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c \ + zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c \ + zlib/inftrees.c zlib/trees.c zlib/uncompr.c zlib/zutil.c \ + nuklear_ui/font_android.c nuklear_ui/blastem_nuklear.c nuklear_ui/sfnt.c \ + ppm.c controller_info.c png.c system.c genesis.c sms.c serialize.c \ + saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c megawifi.c \ + nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c LOCAL_SHARED_LIBRARIES := SDL2
--- a/Makefile Thu Apr 18 19:47:50 2019 -0700 +++ b/Makefile Thu Apr 18 19:48:04 2019 -0700 @@ -10,16 +10,8 @@ zlib/gzwrite.o zlib/infback.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/uncompr.o zlib/zutil.o ifeq ($(OS),Windows) -ifndef SDL2_PREFIX -SDL2_PREFIX:="sdl/i686-w64-mingw32" -endif -ifndef GLEW_PREFIX + GLEW_PREFIX:=glew -endif -ifndef GLEW32S_LIB -GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/Win32/glew32s.lib -endif - MEM:=mem_win.o TERMINAL:=terminal_win.o FONT:=nuklear_ui/font_win.o @@ -29,9 +21,16 @@ CPU:=i686 ifeq ($(CPU),i686) CC:=i686-w64-mingw32-gcc-win32 +WINDRES:=i686-w64-mingw32-windres +GLUDIR:=Win32 +SDL2_PREFIX:="sdl/i686-w64-mingw32" else CC:=x86_64-w64-mingw32-gcc-win32 +WINDRES:=x86_64-w64-mingw32-windres +SDL2_PREFIX:="sdl/x86_64-w64-mingw32" +GLUDIR:=x64 endif +GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/$(GLUDIR)/glew32s.lib CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration LDFLAGS:=-lm -lmingw32 -lws2_32 -mwindows ifneq ($(MAKECMDGOALS),libblastem.dll) @@ -151,7 +150,6 @@ endif ifdef NOGL CFLAGS+= -DDISABLE_OPENGL -NONUKLEAR:=1 endif ifdef M68030 @@ -370,7 +368,7 @@ %.bin : %.sz8 vasmz80_mot -Fbin -spaces -o $@ $< res.o : blastem.rc - i686-w64-mingw32-windres blastem.rc res.o + $(WINDRES) blastem.rc res.o arrow.tiles : arrow.png cursor.tiles : cursor.png
--- a/android/assets/default.cfg Thu Apr 18 19:47:50 2019 -0700 +++ b/android/assets/default.cfg Thu Apr 18 19:48:04 2019 -0700 @@ -14,9 +14,14 @@ f gamepads.1.mode enter gamepads.1.start + r ui.release_mouse [ ui.vdp_debug_mode - ] ui.vdp_debug_pal u ui.enter_debugger + p ui.screenshot + b ui.plane_debug + v ui.vram_debug + c ui.cram_debug + n ui.compositing_debug esc ui.exit ` ui.save_state 0 ui.set_speed.0 @@ -29,6 +34,11 @@ 7 ui.set_speed.7 = ui.next_speed - ui.prev_speed + f11 ui.toggle_fullscreen + tab ui.soft_reset + f5 ui.reload + z ui.sms_pause + rctrl ui.toggle_keyboard_captured select gamepads.1.c @@ -36,44 +46,230 @@ back ui.exit } pads { - 0 { + default { dpads { 0 { - up gamepads.1.up - down gamepads.1.down - left gamepads.1.left - right gamepads.1.right + up gamepads.n.up + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right } } buttons { - 0 gamepads.1.a - 1 gamepads.1.b - 2 gamepads.1.c - 3 gamepads.1.x - 4 gamepads.1.y - 5 gamepads.1.z - 6 gamepads.1.mode - 7 gamepads.1.start + a gamepads.n.a + b gamepads.n.b + rightshoulder gamepads.n.c + x gamepads.n.x + y gamepads.n.y + leftshoulder gamepads.n.z + back gamepads.n.mode + start gamepads.n.start + guide ui.exit + leftstick ui.save_state + } + axes { + lefty.positive gamepads.n.down + lefty.negative gamepads.n.up + leftx.positive gamepads.n.right + leftx.negative gamepads.n.left + lefttrigger ui.prev_speed + righttrigger ui.next_speed + } + } + ps4_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } } } - 1 { + ps3_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + xbox_360_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } dpads { 0 { - up gamepads.2.up - down gamepads.2.down - left gamepads.2.left - right gamepads.2.right + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up } } + } + xbone_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + genesis_6b_bumpers { + axes { + lefttrigger ui.exit + righttrigger gamepads.n.mode + } buttons { - 0 gamepads.2.a - 1 gamepads.2.b - 2 gamepads.2.c - 3 gamepads.2.x - 4 gamepads.2.y - 5 gamepads.2.z - 6 gamepads.2.mode - 7 gamepads.2.start + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.z + rightshoulder gamepads.n.c + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + saturn_6b_bumpers { + axes { + lefttrigger ui.exit + righttrigger gamepads.n.mode + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.z + rightshoulder gamepads.n.c + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + } + mice { + 0 { + motion mouse.1.motion + buttons { + 1 mouse.1.left + 2 mouse.1.middle + 3 mouse.1.right + 4 mouse.1.start + } + } + #having the second host mouse also mapped to the first emulated + #mouse is useful for laptop users with an external mouse + 1 { + motion mouse.1.motion + buttons { + 1 mouse.1.left + 2 mouse.1.middle + 3 mouse.1.right + 4 mouse.1.start } } } @@ -87,19 +283,69 @@ } video { + #special value "stretch" will cause aspect to match window aspect ratio + aspect 4:3 width 640 + #height is normally calculated automatically from width using the aspect setting + #if you would like to set it explicitly, uncomment the line below + #height 480 vertex_shader default.v.glsl fragment_shader default.f.glsl + scanlines off + vsync off + fullscreen off + #setting gl to off, will force use of the SDL2 fallback renderer + #this is useful for those running on machines with Open GL 2.0 unavailable + #so the warning doesn't display on startup + gl on + #scaling can be linear (for linear interpolation) or nearest (for nearest neighbor) + scaling linear + ntsc { + overscan { + #these values will result in square pixels in H40 mode + top 2 + bottom 1 + #if you want to completely hide the border instead + #comment out those two lines and uncomment these + #top 11 + #bottom 8 + + #these values will completely hide the horizontal border + left 13 + right 14 + } + } + pal { + overscan { + #these values will produce the same size border in V30 mode + #as the default NTSC settings will produce in V24 mode + #this results in a slightly vertically squished picture + #which is probably approximately correct on a properly calibrated TV + top 21 + bottom 17 + #for square pixels and zero border in V30 mode + #coment out those two lines and uncomment these + #top 30 + #bottom 24 + + #these values will completely hide the horizontal border + left 13 + right 14 + } + } } audio { rate 48000 buffer 512 + lowpass_cutoff 3390 } clocks { + m68k_divider 7 max_cycles 3420 speeds { + 0 100 1 150 2 200 3 300 @@ -111,8 +357,39 @@ } ui { + #specifies the ROM that implements the Menu UI rom menu.bin + #starting path for ROM browsing, accepts special variables $HOME, $EXEDIR + #and variables defined in the OS environment + initial_path $HOME + #if this is set to on, then the menu will remember the last path when visited + #if it's set to off, initial_path will always be used on startup + remember_path on + #path for storing internal screenshots, accepts the same variables as initial_path + screenshot_path $HOME + #see strftime for the format specifiers valid in screenshot_template + screenshot_template blastem_%Y%m%d_%H%M%S.png + #path template for saving SRAM, EEPROM and savestates + #accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME + save_path $USERDATA/blastem/$ROMNAME + #space delimited list of file extensions to filter against in menu + extensions bin gen md smd sms gg zip gz + #specifies the preferred save-state format, set to gst for Genecyst compatible states + state_format native } -default_region U +system { + #controls how the emulated system is synced to the host + #video provides the smoothest experience when the host and emulated system have similar refresh rates + #audio provides lower audio latency, especially when there is a refresh rate mismatch + sync_source audio + #set this to random to debug initialization bugs + ram_init zero + default_region U + #controls whether MegaWiFi support is enabled or not + #MegaWiFi allows ROMs to make connections to the internet + #so it should only be enabled for ROMs you trust + megawifi off +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/assets/images Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,1 @@ +../../images \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/assets/shaders Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,1 @@ +../../shaders \ No newline at end of file
--- a/android/jni/Application.mk Thu Apr 18 19:47:50 2019 -0700 +++ b/android/jni/Application.mk Thu Apr 18 19:48:04 2019 -0700 @@ -4,3 +4,5 @@ # APP_STL := stlport_static APP_ABI := x86 +APP_PLATFORM := android-16 +APP_OPTIM := release
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDL.java Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,37 @@ +package org.libsdl.app; + +import android.content.Context; + +/** + SDL library initialization +*/ +public class SDL { + + // This function should be called first and sets up the native code + // so it can call into the Java classes + public static void setupJNI() { + SDLActivity.nativeSetupJNI(); + SDLAudioManager.nativeSetupJNI(); + SDLControllerManager.nativeSetupJNI(); + } + + // This function should be called each time the activity is started + public static void initialize() { + setContext(null); + + SDLActivity.initialize(); + SDLAudioManager.initialize(); + SDLControllerManager.initialize(); + } + + // This function stores the current activity (SDL or not) + public static void setContext(Context context) { + mContext = context; + } + + public static Context getContext() { + return mContext; + } + + protected static Context mContext; +}
--- a/android/src/org/libsdl/app/SDLActivity.java Thu Apr 18 19:47:50 2019 -0700 +++ b/android/src/org/libsdl/app/SDLActivity.java Thu Apr 18 19:48:04 2019 -0700 @@ -1,25 +1,30 @@ package org.libsdl.app; -import java.util.ArrayList; +import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.lang.reflect.Method; +import java.util.Objects; import android.app.*; import android.content.*; +import android.text.InputType; import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.widget.AbsoluteLayout; +import android.widget.RelativeLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; import android.os.*; import android.util.Log; +import android.util.SparseArray; import android.graphics.*; -import android.media.*; +import android.graphics.drawable.Drawable; import android.hardware.*; - +import android.content.pm.ActivityInfo; /** SDL Activity @@ -27,34 +32,96 @@ public class SDLActivity extends Activity { private static final String TAG = "SDL"; - // Keep track of the paused state - public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus; + + // Handle the state of the native layer + public enum NativeState { + INIT, RESUMED, PAUSED + } + + public static NativeState mNextNativeState; + public static NativeState mCurrentNativeState; + public static boolean mExitCalledFromJava; + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static View mTextEdit; + protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; - protected static SDLJoystickHandler mJoystickHandler; + protected static SDLClipboardHandler mClipboardHandler; + // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; - - // Audio - protected static AudioTrack mAudioTrack; + + /** + * This method returns the name of the shared object with the application entry point + * It can be overridden by derived classes. + */ + protected String getMainSharedObject() { + String library; + String[] libraries = SDLActivity.mSingleton.getLibraries(); + if (libraries.length > 0) { + library = "lib" + libraries[libraries.length - 1] + ".so"; + } else { + library = "libmain.so"; + } + return library; + } + + /** + * This method returns the name of the application entry point + * It can be overridden by derived classes. + */ + protected String getMainFunction() { + return "SDL_main"; + } + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } // Load the .so - static { - System.loadLibrary("SDL2"); - //System.loadLibrary("SDL2_image"); - //System.loadLibrary("SDL2_mixer"); - //System.loadLibrary("SDL2_net"); - //System.loadLibrary("SDL2_ttf"); - System.loadLibrary("main"); + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } } - - + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values @@ -62,78 +129,172 @@ mSurface = null; mTextEdit = null; mLayout = null; - mJoystickHandler = null; + mClipboardHandler = null; mSDLThread = null; - mAudioTrack = null; mExitCalledFromJava = false; - mIsPaused = false; + mBrokenLibraries = false; + mIsResumedCalled = false; mIsSurfaceReady = false; mHasFocus = true; + mNextNativeState = NativeState.INIT; + mCurrentNativeState = NativeState.INIT; } // Setup @Override protected void onCreate(Bundle savedInstanceState) { - Log.v("SDL", "onCreate():" + mSingleton); + Log.v(TAG, "Device: " + android.os.Build.DEVICE); + Log.v(TAG, "Model: " + android.os.Build.MODEL); + Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); - - SDLActivity.initialize(); + + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up JNI + SDL.setupJNI(); + + // Initialize state + SDL.initialize(); + // So we can call stuff from static callbacks mSingleton = this; + SDL.setContext(this); + + if (Build.VERSION.SDK_INT >= 11) { + mClipboardHandler = new SDLClipboardHandler_API11(); + } else { + /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */ + mClipboardHandler = new SDLClipboardHandler_Old(); + } // Set up the surface mSurface = new SDLSurface(getApplication()); - - if(Build.VERSION.SDK_INT >= 12) { - mJoystickHandler = new SDLJoystickHandler_API12(); - } - else { - mJoystickHandler = new SDLJoystickHandler(); - } - mLayout = new AbsoluteLayout(this); + mLayout = new RelativeLayout(this); mLayout.addView(mSurface); setContentView(mLayout); + + // Get filename from "Open with" of another application + Intent intent = getIntent(); + if (intent != null && intent.getData() != null) { + String filename = intent.getData().getPath(); + if (filename != null) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } + } } // Events @Override protected void onPause() { - Log.v("SDL", "onPause()"); + Log.v(TAG, "onPause()"); super.onPause(); - SDLActivity.handlePause(); + mNextNativeState = NativeState.PAUSED; + mIsResumedCalled = false; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); } @Override protected void onResume() { - Log.v("SDL", "onResume()"); + Log.v(TAG, "onResume()"); super.onResume(); - SDLActivity.handleResume(); + mNextNativeState = NativeState.RESUMED; + mIsResumedCalled = true; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); - Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } SDLActivity.mHasFocus = hasFocus; if (hasFocus) { - SDLActivity.handleResume(); + mNextNativeState = NativeState.RESUMED; + } else { + mNextNativeState = NativeState.PAUSED; } + + SDLActivity.handleNativeState(); } @Override public void onLowMemory() { - Log.v("SDL", "onLowMemory()"); + Log.v(TAG, "onLowMemory()"); super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + SDLActivity.nativeLowMemory(); } @Override protected void onDestroy() { - Log.v("SDL", "onDestroy()"); + Log.v(TAG, "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + mNextNativeState = NativeState.PAUSED; + SDLActivity.handleNativeState(); + // Send a quit message to the application SDLActivity.mExitCalledFromJava = true; SDLActivity.nativeQuit(); @@ -143,57 +304,101 @@ try { SDLActivity.mSDLThread.join(); } catch(Exception e) { - Log.v("SDL", "Problem stopping thread: " + e); + Log.v(TAG, "Problem stopping thread: " + e); } SDLActivity.mSDLThread = null; - //Log.v("SDL", "Finished waiting for SDL thread"); + //Log.v(TAG, "Finished waiting for SDL thread"); } - + super.onDestroy(); + // Reset everything in case the user re opens the app SDLActivity.initialize(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + int keyCode = event.getKeyCode(); // Ignore certain special keys so they're handled by Android if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || - keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ - keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ + keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ ) { return false; } return super.dispatchKeyEvent(event); } - /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed - * is the first to be called, mIsSurfaceReady should still be set - * to 'true' during the call to onPause (in a usual scenario). - */ - public static void handlePause() { - if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { - SDLActivity.mIsPaused = true; - SDLActivity.nativePause(); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + /* Transition to next state */ + public static void handleNativeState() { + + if (mNextNativeState == mCurrentNativeState) { + // Already in same state, discard. + return; + } + + // Try a transition to init state + if (mNextNativeState == NativeState.INIT) { + + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to paused state + if (mNextNativeState == NativeState.PAUSED) { + nativePause(); + mSurface.handlePause(); + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to resumed state + if (mNextNativeState == NativeState.RESUMED) { + if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) { + if (mSDLThread == null) { + // This is the entry point to the C app. + // Start up the C app thread and enable sensor input for the first time + // FIXME: Why aren't we enabling sensor input at start? + + final Thread sdlThread = new Thread(new SDLMain(), "SDLThread"); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + sdlThread.start(); + + // Set up a listener thread to catch when the native thread ends + mSDLThread = new Thread(new Runnable() { + @Override + public void run() { + try { + sdlThread.join(); + } catch (Exception e) { + // Ignore any exception + } finally { + // Native thread has finished + if (!mExitCalledFromJava) { + handleNativeExit(); + } + } + } + }, "SDLThreadListener"); + + mSDLThread.start(); + } + + nativeResume(); + mSurface.handleResume(); + mCurrentNativeState = mNextNativeState; + } } } - /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. - * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume - * every time we get one of those events, only if it comes after surfaceDestroyed - */ - public static void handleResume() { - if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { - SDLActivity.mIsPaused = false; - SDLActivity.nativeResume(); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); - } - } - /* The native thread has finished */ public static void handleNativeExit() { SDLActivity.mSDLThread = null; @@ -205,6 +410,7 @@ static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_UNUSED = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; @@ -228,7 +434,7 @@ protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { - Context context = getContext(); + Context context = SDL.getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; @@ -243,13 +449,31 @@ break; case COMMAND_TEXTEDIT_HIDE: if (mTextEdit != null) { - mTextEdit.setVisibility(View.GONE); + // Note: On some devices setting view to GONE creates a flicker in landscape. + // Setting the View's sizes to 0 is similar to GONE but without the flicker. + // The sizes will be set to useful values when the keyboard is shown again. + mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + + mScreenKeyboardShown = false; } break; - + case COMMAND_SET_KEEP_SCREEN_ON: + { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + break; + } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); @@ -270,76 +494,123 @@ } // C functions we call - public static native void nativeInit(); + public static native int nativeSetupJNI(); + public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); - public static native void onNativeResize(int x, int y, int format); - public static native int onNativePadDown(int device_id, int keycode); - public static native int onNativePadUp(int device_id, int keycode); - public static native void onNativeJoy(int device_id, int axis, - float value); - public static native void onNativeHat(int device_id, int hat_id, - int x, int y); + public static native void onNativeDropFile(String filename); + public static native void onNativeResize(int x, int y, int format, float rate); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y); public static native void onNativeTouch(int touchDevId, int pointerFingerId, - int action, float x, + int action, float x, float y, float p); public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeClipboardChanged(); public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); - public static native void nativeFlipBuffers(); - public static native int nativeAddJoystick(int device_id, String name, - int is_accelerometer, int nbuttons, - int naxes, int nhats, int nballs); - public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); - public static void flipBuffers() { - SDLActivity.nativeFlipBuffers(); - } - + /** + * This method is called by SDL using JNI. + */ public static boolean setActivityTitle(String title) { // Called from SDLMain() thread and can't directly affect the view return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } - public static boolean sendMessage(int command, int param) { - return mSingleton.sendCommand(command, Integer.valueOf(param)); + /** + * This method is called by SDL using JNI. + * This is a static method for JNI convenience, it calls a non-static method + * so that is can be overridden + */ + public static void setOrientation(int w, int h, boolean resizable, String hint) + { + if (mSingleton != null) { + mSingleton.setOrientationBis(w, h, resizable, hint); + } + } + + /** + * This can be overridden + */ + public void setOrientationBis(int w, int h, boolean resizable, String hint) + { + int orientation = -1; + + if (!Objects.equals(hint, "")) { + if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else if (hint.contains("LandscapeRight")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeLeft")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } else if (hint.contains("Portrait")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } else if (hint.contains("PortraitUpsideDown")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + } + } + + /* no valid hint */ + if (orientation == -1) { + if (resizable) { + /* no fixed orientation */ + } else { + if (w > h) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } + } + } + + Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); + if (orientation != -1) { + mSingleton.setRequestedOrientation(orientation); + } } - public static Context getContext() { - return mSingleton; + + /** + * This method is called by SDL using JNI. + */ + public static boolean isScreenKeyboardShown() + { + if (mTextEdit == null) { + return false; + } + + if (!mScreenKeyboardShown) { + return false; + } + + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + return imm.isAcceptingText(); + } /** - * @return result of getSystemService(name) but executed on UI thread. + * This method is called by SDL using JNI. */ - public Object getSystemServiceFromUiThread(final String name) { - final Object lock = new Object(); - final Object[] results = new Object[2]; // array for writable variables - synchronized (lock) { - runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (lock) { - results[0] = getSystemService(name); - results[1] = Boolean.TRUE; - lock.notify(); - } - } - }); - if (results[1] == null) { - try { - lock.wait(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - } + public static boolean sendMessage(int command, int param) { + if (mSingleton == null) { + return false; } - return results[0]; + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return SDL.getContext(); } static class ShowTextInputTask implements Runnable { @@ -361,11 +632,12 @@ @Override public void run() { - AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( - w, h + HEIGHT_PADDING, x, y); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); + params.leftMargin = x; + params.topMargin = y; if (mTextEdit == null) { - mTextEdit = new DummyEdit(getContext()); + mTextEdit = new DummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { @@ -375,101 +647,47 @@ mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); + + mScreenKeyboardShown = true; } } + /** + * This method is called by SDL using JNI. + */ public static boolean showTextInput(int x, int y, int w, int h) { // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } - - public static Surface getNativeSurface() { - return SDLActivity.mSurface.getNativeSurface(); + + public static boolean isTextInputEvent(KeyEvent event) { + + // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT + if (android.os.Build.VERSION.SDK_INT >= 11) { + if (event.isCtrlPressed()) { + return false; + } + } + + return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } - // Audio - public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { - int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; - int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; - int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); - - Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - // Let the user pick a larger buffer if they really want -- but ye - // gods they probably shouldn't, the minimums are horrifyingly high - // latency already - desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); - - if (mAudioTrack == null) { - mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, - channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); - - // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid - // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java - // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() - - if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { - Log.e("SDL", "Failed during initialization of Audio Track"); - mAudioTrack = null; - return -1; - } - - mAudioTrack.play(); + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + if (SDLActivity.mSurface == null) { + return null; } - - Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - return 0; - } - - public static void audioWriteShortBuffer(short[] buffer) { - for (int i = 0; i < buffer.length; ) { - int result = mAudioTrack.write(buffer, i, buffer.length - i); - if (result > 0) { - i += result; - } else if (result == 0) { - try { - Thread.sleep(1); - } catch(InterruptedException e) { - // Nom nom - } - } else { - Log.w("SDL", "SDL audio: error return from write(short)"); - return; - } - } - } - - public static void audioWriteByteBuffer(byte[] buffer) { - for (int i = 0; i < buffer.length; ) { - int result = mAudioTrack.write(buffer, i, buffer.length - i); - if (result > 0) { - i += result; - } else if (result == 0) { - try { - Thread.sleep(1); - } catch(InterruptedException e) { - // Nom nom - } - } else { - Log.w("SDL", "SDL audio: error return from write(byte)"); - return; - } - } - } - - public static void audioQuit() { - if (mAudioTrack != null) { - mAudioTrack.stop(); - mAudioTrack = null; - } + return SDLActivity.mSurface.getNativeSurface(); } // Input /** + * This method is called by SDL using JNI. * @return an array which may be empty but is never null. */ public static int[] inputGetInputDeviceIds(int sources) { @@ -484,41 +702,331 @@ } return Arrays.copyOf(filtered, used); } - - // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance - public static boolean handleJoystickMotionEvent(MotionEvent event) { - return mJoystickHandler.handleMotionEvent(event); + + // APK expansion files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private static Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private static Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + * @return an InputStream on success or null if no expansion file was used. + * @throws IOException on errors. Message is set for the SDL error message. + */ + public static InputStream openAPKExpansionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); + if (mainHint == null) { + return null; // no expansion use if no main version was set + } + String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); + if (patchHint == null) { + return null; // no expansion use if no patch version was set + } + + Integer mainVersion; + Integer patchVersion; + try { + mainVersion = Integer.valueOf(mainHint); + patchVersion = Integer.valueOf(patchHint); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + throw new IOException("No valid file versions set for APK expansion files", ex); + } + + try { + // To avoid direct dependency on Google APK expansion library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, SDL.getContext(), mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + throw new IOException("Could not access APK expansion support library", ex); + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + // calling "getInputStream" failed + ex.printStackTrace(); + throw new IOException("Could not open stream from APK expansion file", ex); + } + + if (fileStream == null) { + // calling "getInputStream" was successful but null was returned + throw new IOException("Could not find path in APK expansion file"); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray<Button> mapping = new SparseArray<Button>(); + + LinearLayout buttons = new LinearLayout(this); + buttons.setOrientation(LinearLayout.HORIZONTAL); + buttons.setGravity(Gravity.CENTER); + for (int i = 0; i < buttonTexts.length; ++i) { + Button button = new Button(this); + final int id = buttonIds[i]; + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + messageboxSelection[0] = id; + dialog.dismiss(); + } + }); + if (buttonFlags[i] != 0) { + // see SDL_messagebox.h + if ((buttonFlags[i] & 0x00000001) != 0) { + mapping.put(KeyEvent.KEYCODE_ENTER, button); + } + if ((buttonFlags[i] & 0x00000002) != 0) { + mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */ + } + } + button.setText(buttonTexts[i]); + if (textColor != Color.TRANSPARENT) { + button.setTextColor(textColor); + } + if (buttonBorderColor != Color.TRANSPARENT) { + // TODO set color for border of messagebox button + } + if (buttonBackgroundColor != Color.TRANSPARENT) { + Drawable drawable = button.getBackground(); + if (drawable == null) { + // setting the color this way removes the style + button.setBackgroundColor(buttonBackgroundColor); + } else { + // setting the color this way keeps the style (gradient, padding, etc.) + drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY); + } + } + if (buttonSelectedColor != Color.TRANSPARENT) { + // TODO set color for selected messagebox button + } + buttons.addView(button); + } + + // create content + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + content.addView(message); + content.addView(buttons); + if (backgroundColor != Color.TRANSPARENT) { + content.setBackgroundColor(backgroundColor); + } + + // add content to dialog and return + + dialog.setContentView(content); + dialog.setOnKeyListener(new Dialog.OnKeyListener() { + @Override + public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { + Button button = mapping.get(keyCode); + if (button != null) { + if (event.getAction() == KeyEvent.ACTION_UP) { + button.performClick(); + } + return true; // also for ignored actions + } + return false; + } + }); + + return dialog; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean clipboardHasText() { + return mClipboardHandler.clipboardHasText(); } - public static void pollInputDevices() { - if (SDLActivity.mSDLThread != null) { - mJoystickHandler.pollInputDevices(); - } + /** + * This method is called by SDL using JNI. + */ + public static String clipboardGetText() { + return mClipboardHandler.clipboardGetText(); } - + + /** + * This method is called by SDL using JNI. + */ + public static void clipboardSetText(String string) { + mClipboardHandler.clipboardSetText(string); + } + } /** - Simple nativeInit() runnable + Simple runnable to start the SDL application */ class SDLMain implements Runnable { @Override public void run() { // Runs SDL_main() - SDLActivity.nativeInit(); + String library = SDLActivity.mSingleton.getMainSharedObject(); + String function = SDLActivity.mSingleton.getMainFunction(); + String[] arguments = SDLActivity.mSingleton.getArguments(); - //Log.v("SDL", "SDL thread terminated"); + Log.v("SDL", "Running main function " + function + " from library " + library); + SDLActivity.nativeRunMain(library, function, arguments); + + Log.v("SDL", "Finished main function"); } } /** SDLSurface. This is what we draw on, so we need to know when it's created - in order to do anything useful. + in order to do anything useful. Because of this, that's where we set up the SDL thread */ -class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, +class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, View.OnKeyListener, View.OnTouchListener, SensorEventListener { // Sensors @@ -528,21 +1036,21 @@ // Keep track of the surface size to normalize touch events protected static float mWidth, mHeight; - // Startup + // Startup public SDLSurface(Context context) { super(context); - getHolder().addCallback(this); - + getHolder().addCallback(this); + setFocusable(true); setFocusableInTouchMode(true); requestFocus(); - setOnKeyListener(this); - setOnTouchListener(this); + setOnKeyListener(this); + setOnTouchListener(this); mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - - if(Build.VERSION.SDK_INT >= 12) { + + if (Build.VERSION.SDK_INT >= 12) { setOnGenericMotionListener(new SDLGenericMotionListener_API12()); } @@ -550,7 +1058,20 @@ mWidth = 1.0f; mHeight = 1.0f; } - + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + public Surface getNativeSurface() { return getHolder().getSurface(); } @@ -566,8 +1087,11 @@ @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.v("SDL", "surfaceDestroyed()"); - // Call this *before* setting mIsSurfaceReady to 'false' - SDLActivity.handlePause(); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + SDLActivity.mIsSurfaceReady = false; SDLActivity.onNativeSurfaceDestroyed(); } @@ -625,66 +1149,78 @@ mWidth = width; mHeight = height; - SDLActivity.onNativeResize(width, height, sdlFormat); - Log.v("SDL", "Window size:" + width + "x"+height); + SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate()); + Log.v("SDL", "Window size: " + width + "x" + height); + + + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) + { + // Accept any + } + else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) + { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } - // Set mIsSurfaceReady to 'true' *before* making a call to handleResume + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + SDLActivity.mIsSurfaceReady = false; + return; + } + + /* Surface is ready */ SDLActivity.mIsSurfaceReady = true; + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ SDLActivity.onNativeSurfaceChanged(); - - if (SDLActivity.mSDLThread == null) { - // This is the entry point to the C app. - // Start up the C app thread and enable sensor input for the first time - - SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread"); - enableSensor(Sensor.TYPE_ACCELEROMETER, true); - SDLActivity.mSDLThread.start(); - - // Set up a listener thread to catch when the native thread ends - new Thread(new Runnable(){ - @Override - public void run(){ - try { - SDLActivity.mSDLThread.join(); - } - catch(Exception e){} - finally{ - // Native thread has finished - if (! SDLActivity.mExitCalledFromJava) { - SDLActivity.handleNativeExit(); - } - } - } - }).start(); - } + SDLActivity.handleNativeState(); } - // unused - @Override - public void onDraw(Canvas canvas) {} - - // Key events @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // Dispatch the different events depending on where they come from - // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD - // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD - - if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD + // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD + // + // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and + // SOURCE_JOYSTICK, while its key events arrive from the keyboard source + // So, retrieve the device itself and check all of its sources + if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) { + // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) { + if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) { + if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) { return true; } } } - - if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { + + if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { if (event.getAction() == KeyEvent.ACTION_DOWN) { //Log.v("SDL", "key down: " + keyCode); SDLActivity.onNativeKeyDown(keyCode); @@ -696,7 +1232,21 @@ return true; } } - + + if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) { + // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses + // they are ignored here because sending them as mouse input to SDL is messy + if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + case KeyEvent.ACTION_UP: + // mark the event as handled or it will be handled by system + // handling KEYCODE_BACK by system will call onBackPressed() + return true; + } + } + } + return false; } @@ -708,58 +1258,98 @@ final int pointerCount = event.getPointerCount(); int action = event.getActionMasked(); int pointerFingerId; + int mouseButton; int i = -1; float x,y,p; - - switch(action) { - case MotionEvent.ACTION_MOVE: - for (i = 0; i < pointerCount; i++) { + + // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14. + if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) { + if (Build.VERSION.SDK_INT < 14) { + mouseButton = 1; // all mouse buttons are the left button + } else { + try { + mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event); + } catch(Exception e) { + mouseButton = 1; // oh well. + } + } + SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0)); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + pointerFingerId = event.getPointerId(i); x = event.getX(i) / mWidth; y = event.getY(i) / mHeight; p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_DOWN: - // Primary pointer up/down, the index is always zero - i = 0; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: - // Non primary pointer up/down - if (i == -1) { - i = event.getActionIndex(); - } - - pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; - p = event.getPressure(i); - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - break; - - default: - break; + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } } return true; - } + } // Sensor events public void enableSensor(int sensortype, boolean enabled) { // TODO: This uses getDefaultSensor - what if we have >1 accels? if (enabled) { - mSensorManager.registerListener(this, - mSensorManager.getDefaultSensor(sensortype), + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), SensorManager.SENSOR_DELAY_GAME, null); } else { - mSensorManager.unregisterListener(this, + mSensorManager.unregisterListener(this, mSensorManager.getDefaultSensor(sensortype)); } } - + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO @@ -789,9 +1379,9 @@ } SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, y / SensorManager.GRAVITY_EARTH, - event.values[2] / SensorManager.GRAVITY_EARTH - 1); + event.values[2] / SensorManager.GRAVITY_EARTH); } - } + } } /* This is a fake invisible editor view that receives the input and defines the @@ -814,33 +1404,29 @@ @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - - // This handles the hardware keyboard input - if (event.isPrintingKey()) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { + /* + * This handles the hardware keyboard input + */ + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (SDLActivity.isTextInputEvent(event)) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } - return true; - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(keyCode); return true; } - return false; } - + // @Override public boolean onKeyPreIme (int keyCode, KeyEvent event) { // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not - // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear + // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { @@ -855,8 +1441,9 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { ic = new SDLInputConnection(this, true); + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI - | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; + | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; return ic; } @@ -871,20 +1458,17 @@ @Override public boolean sendKeyEvent(KeyEvent event) { - /* - * This handles the keycodes from soft keyboard (and IME-translated - * input from hardkeyboard) + * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard) */ int keyCode = event.getKeyCode(); if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (event.isPrintingKey()) { + if (SDLActivity.isTextInputEvent(event)) { commitText(String.valueOf((char) event.getUnicodeChar()), 1); } SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { - SDLActivity.onNativeKeyUp(keyCode); return true; } @@ -912,162 +1496,100 @@ public native void nativeSetComposingText(String text, int newCursorPosition); @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { + public boolean deleteSurroundingText(int beforeLength, int afterLength) { // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection - if (beforeLength == 1 && afterLength == 0) { - // backspace - return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) - && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 + if (beforeLength > 0 && afterLength == 0) { + boolean ret = true; + // backspace(s) + while (beforeLength-- > 0) { + boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + ret = ret && ret_key; + } + return ret; } return super.deleteSurroundingText(beforeLength, afterLength); } } -/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ -class SDLJoystickHandler { - - public boolean handleMotionEvent(MotionEvent event) { - return false; - } - - public void pollInputDevices() { - } +interface SDLClipboardHandler { + + public boolean clipboardHasText(); + public String clipboardGetText(); + public void clipboardSetText(String string); + } -/* Actual joystick functionality available for API >= 12 devices */ -class SDLJoystickHandler_API12 extends SDLJoystickHandler { - - class SDLJoystick { - public int device_id; - public String name; - public ArrayList<InputDevice.MotionRange> axes; - public ArrayList<InputDevice.MotionRange> hats; + +class SDLClipboardHandler_API11 implements + SDLClipboardHandler, + android.content.ClipboardManager.OnPrimaryClipChangedListener { + + protected android.content.ClipboardManager mClipMgr; + + SDLClipboardHandler_API11() { + mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + mClipMgr.addPrimaryClipChangedListener(this); } - class RangeComparator implements Comparator<InputDevice.MotionRange> - { - @Override - public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { - return arg0.getAxis() - arg1.getAxis(); - } + + @Override + public boolean clipboardHasText() { + return mClipMgr.hasText(); } - - private ArrayList<SDLJoystick> mJoysticks; - - public SDLJoystickHandler_API12() { - - mJoysticks = new ArrayList<SDLJoystick>(); + + @Override + public String clipboardGetText() { + CharSequence text; + text = mClipMgr.getText(); + if (text != null) { + return text.toString(); + } + return null; } @Override - public void pollInputDevices() { - int[] deviceIds = InputDevice.getDeviceIds(); - // It helps processing the device ids in reverse order - // For example, in the case of the XBox 360 wireless dongle, - // so the first controller seen by SDL matches what the receiver - // considers to be the first controller - - for(int i=deviceIds.length-1; i>-1; i--) { - SDLJoystick joystick = getJoystick(deviceIds[i]); - if (joystick == null) { - joystick = new SDLJoystick(); - InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); - if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - joystick.device_id = deviceIds[i]; - joystick.name = joystickDevice.getName(); - joystick.axes = new ArrayList<InputDevice.MotionRange>(); - joystick.hats = new ArrayList<InputDevice.MotionRange>(); - - List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - for (InputDevice.MotionRange range : ranges ) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || - range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joystick.hats.add(range); - } - else { - joystick.axes.add(range); - } - } - } - - mJoysticks.add(joystick); - SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, - joystick.axes.size(), joystick.hats.size()/2, 0); - } - } - } - - /* Check removed devices */ - ArrayList<Integer> removedDevices = new ArrayList<Integer>(); - for(int i=0; i < mJoysticks.size(); i++) { - int device_id = mJoysticks.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; - } - if (j == deviceIds.length) { - removedDevices.add(device_id); - } - } - - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i); - SDLActivity.nativeRemoveJoystick(device_id); - for (int j=0; j < mJoysticks.size(); j++) { - if (mJoysticks.get(j).device_id == device_id) { - mJoysticks.remove(j); - break; - } - } - } + public void clipboardSetText(String string) { + mClipMgr.removePrimaryClipChangedListener(this); + mClipMgr.setText(string); + mClipMgr.addPrimaryClipChangedListener(this); } - protected SDLJoystick getJoystick(int device_id) { - for(int i=0; i < mJoysticks.size(); i++) { - if (mJoysticks.get(i).device_id == device_id) { - return mJoysticks.get(i); - } - } - return null; - } - - @Override - public boolean handleMotionEvent(MotionEvent event) { - if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { - int actionPointerIndex = event.getActionIndex(); - int action = event.getActionMasked(); - switch(action) { - case MotionEvent.ACTION_MOVE: - SDLJoystick joystick = getJoystick(event.getDeviceId()); - if ( joystick != null ) { - for (int i = 0; i < joystick.axes.size(); i++) { - InputDevice.MotionRange range = joystick.axes.get(i); - /* Normalize the value to -1...1 */ - float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; - SDLActivity.onNativeJoy(joystick.device_id, i, value ); - } - for (int i = 0; i < joystick.hats.size(); i+=2) { - int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); - int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); - SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY ); - } - } - break; - default: - break; - } - } - return true; - } + @Override + public void onPrimaryClipChanged() { + SDLActivity.onNativeClipboardChanged(); + } + } -class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { - // Generic Motion (mouse hover, joystick...) events go here - // We only have joysticks yet +class SDLClipboardHandler_Old implements + SDLClipboardHandler { + + protected android.text.ClipboardManager mClipMgrOld; + + SDLClipboardHandler_Old() { + mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + } + @Override - public boolean onGenericMotion(View v, MotionEvent event) { - return SDLActivity.handleJoystickMotionEvent(event); + public boolean clipboardHasText() { + return mClipMgrOld.hasText(); + } + + @Override + public String clipboardGetText() { + CharSequence text; + text = mClipMgrOld.getText(); + if (text != null) { + return text.toString(); + } + return null; + } + + @Override + public void clipboardSetText(String string) { + mClipMgrOld.setText(string); } } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDLAudioManager.java Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,178 @@ +package org.libsdl.app; + +import android.media.*; +import android.util.Log; + +public class SDLAudioManager +{ + protected static final String TAG = "SDLAudio"; + + protected static AudioTrack mAudioTrack; + protected static AudioRecord mAudioRecord; + + public static void initialize() { + mAudioTrack = null; + mAudioRecord = null; + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e(TAG, "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioRecord == null) { + mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize); + + // see notes about AudioTrack state in audioOpen(), above. Probably also applies here. + if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { + Log.e(TAG, "Failed during initialization of AudioRecord"); + mAudioRecord.release(); + mAudioRecord = null; + return -1; + } + + mAudioRecord.startRecording(); + } + + Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** This method is called by SDL using JNI. */ + public static int captureReadShortBuffer(short[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + /** This method is called by SDL using JNI. */ + public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + + /** This method is called by SDL using JNI. */ + public static void audioClose() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack.release(); + mAudioTrack = null; + } + } + + /** This method is called by SDL using JNI. */ + public static void captureClose() { + if (mAudioRecord != null) { + mAudioRecord.stop(); + mAudioRecord.release(); + mAudioRecord = null; + } + } + + public static native int nativeSetupJNI(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDLControllerManager.java Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,433 @@ +package org.libsdl.app; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import android.content.Context; +import android.os.*; +import android.view.*; +import android.util.Log; + + +public class SDLControllerManager +{ + + public static native int nativeSetupJNI(); + + public static native int nativeAddJoystick(int device_id, String name, String desc, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native int nativeAddHaptic(int device_id, String name); + public static native int nativeRemoveHaptic(int device_id); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + + protected static SDLJoystickHandler mJoystickHandler; + protected static SDLHapticHandler mHapticHandler; + + private static final String TAG = "SDLControllerManager"; + + public static void initialize() { + mJoystickHandler = null; + mHapticHandler = null; + + SDLControllerManager.setup(); + } + + public static void setup() { + if (Build.VERSION.SDK_INT >= 16) { + mJoystickHandler = new SDLJoystickHandler_API16(); + } else if (Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } else { + mJoystickHandler = new SDLJoystickHandler(); + } + mHapticHandler = new SDLHapticHandler(); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + mJoystickHandler.pollInputDevices(); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollHapticDevices() { + mHapticHandler.pollHapticDevices(); + } + + /** + * This method is called by SDL using JNI. + */ + public static void hapticRun(int device_id, int length) { + mHapticHandler.run(device_id, length); + } + + // Check if a given device is considered a possible SDL joystick + public static boolean isDeviceSDLJoystick(int deviceId) { + InputDevice device = InputDevice.getDevice(deviceId); + // We cannot use InputDevice.isVirtual before API 16, so let's accept + // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) + if ((device == null) || (deviceId < 0)) { + return false; + } + int sources = device.getSources(); + + if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) { + Log.v(TAG, "Input device " + device.getName() + " is a joystick."); + } + if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { + Log.v(TAG, "Input device " + device.getName() + " is a dpad."); + } + if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); + } + + return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || + ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || + ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + ); + } + +} + +/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ +class SDLJoystickHandler { + + /** + * Handles given MotionEvent. + * @param event the event to be handled. + * @return if given event was processed. + */ + public boolean handleMotionEvent(MotionEvent event) { + return false; + } + + /** + * Handles adding and removing of input devices. + */ + public void pollInputDevices() { + } +} + +/* Actual joystick functionality available for API >= 12 devices */ +class SDLJoystickHandler_API12 extends SDLJoystickHandler { + + static class SDLJoystick { + public int device_id; + public String name; + public String desc; + public ArrayList<InputDevice.MotionRange> axes; + public ArrayList<InputDevice.MotionRange> hats; + } + static class RangeComparator implements Comparator<InputDevice.MotionRange> { + @Override + public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { + return arg0.getAxis() - arg1.getAxis(); + } + } + + private ArrayList<SDLJoystick> mJoysticks; + + public SDLJoystickHandler_API12() { + + mJoysticks = new ArrayList<SDLJoystick>(); + } + + @Override + public void pollInputDevices() { + int[] deviceIds = InputDevice.getDeviceIds(); + // It helps processing the device ids in reverse order + // For example, in the case of the XBox 360 wireless dongle, + // so the first controller seen by SDL matches what the receiver + // considers to be the first controller + + for(int i=deviceIds.length-1; i>-1; i--) { + SDLJoystick joystick = getJoystick(deviceIds[i]); + if (joystick == null) { + joystick = new SDLJoystick(); + InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); + if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { + joystick.device_id = deviceIds[i]; + joystick.name = joystickDevice.getName(); + joystick.desc = getJoystickDescriptor(joystickDevice); + joystick.axes = new ArrayList<InputDevice.MotionRange>(); + joystick.hats = new ArrayList<InputDevice.MotionRange>(); + + List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges(); + Collections.sort(ranges, new RangeComparator()); + for (InputDevice.MotionRange range : ranges ) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || + range.getAxis() == MotionEvent.AXIS_HAT_Y) { + joystick.hats.add(range); + } + else { + joystick.axes.add(range); + } + } + } + + mJoysticks.add(joystick); + SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1, + joystick.axes.size(), joystick.hats.size()/2, 0); + } + } + } + + /* Check removed devices */ + ArrayList<Integer> removedDevices = new ArrayList<Integer>(); + for(int i=0; i < mJoysticks.size(); i++) { + int device_id = mJoysticks.get(i).device_id; + int j; + for (j=0; j < deviceIds.length; j++) { + if (device_id == deviceIds[j]) break; + } + if (j == deviceIds.length) { + removedDevices.add(Integer.valueOf(device_id)); + } + } + + for(int i=0; i < removedDevices.size(); i++) { + int device_id = removedDevices.get(i).intValue(); + SDLControllerManager.nativeRemoveJoystick(device_id); + for (int j=0; j < mJoysticks.size(); j++) { + if (mJoysticks.get(j).device_id == device_id) { + mJoysticks.remove(j); + break; + } + } + } + } + + protected SDLJoystick getJoystick(int device_id) { + for(int i=0; i < mJoysticks.size(); i++) { + if (mJoysticks.get(i).device_id == device_id) { + return mJoysticks.get(i); + } + } + return null; + } + + @Override + public boolean handleMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { + int actionPointerIndex = event.getActionIndex(); + int action = event.getActionMasked(); + switch(action) { + case MotionEvent.ACTION_MOVE: + SDLJoystick joystick = getJoystick(event.getDeviceId()); + if ( joystick != null ) { + for (int i = 0; i < joystick.axes.size(); i++) { + InputDevice.MotionRange range = joystick.axes.get(i); + /* Normalize the value to -1...1 */ + float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; + SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); + } + for (int i = 0; i < joystick.hats.size(); i+=2) { + int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); + int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); + SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); + } + } + break; + default: + break; + } + } + return true; + } + + public String getJoystickDescriptor(InputDevice joystickDevice) { + return joystickDevice.getName(); + } +} + + +class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 { + + @Override + public String getJoystickDescriptor(InputDevice joystickDevice) { + String desc = joystickDevice.getDescriptor(); + + if (desc != null && !Objects.equals(desc, "")) { + return desc; + } + + return super.getJoystickDescriptor(joystickDevice); + } +} + +class SDLHapticHandler { + + class SDLHaptic { + public int device_id; + public String name; + public Vibrator vib; + } + + private ArrayList<SDLHaptic> mHaptics; + + public SDLHapticHandler() { + mHaptics = new ArrayList<SDLHaptic>(); + } + + public void run(int device_id, int length) { + SDLHaptic haptic = getHaptic(device_id); + if (haptic != null) { + haptic.vib.vibrate (length); + } + } + + public void pollHapticDevices() { + + final int deviceId_VIBRATOR_SERVICE = 999999; + boolean hasVibratorService = false; + + int[] deviceIds = InputDevice.getDeviceIds(); + // It helps processing the device ids in reverse order + // For example, in the case of the XBox 360 wireless dongle, + // so the first controller seen by SDL matches what the receiver + // considers to be the first controller + + if (Build.VERSION.SDK_INT >= 16) + { + for (int i = deviceIds.length - 1; i > -1; i--) { + SDLHaptic haptic = getHaptic(deviceIds[i]); + if (haptic == null) { + InputDevice device = InputDevice.getDevice(deviceIds[i]); + Vibrator vib = device.getVibrator(); + if (vib.hasVibrator()) { + haptic = new SDLHaptic(); + haptic.device_id = deviceIds[i]; + haptic.name = device.getName(); + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } + } + } + } + + /* Check VIBRATOR_SERVICE */ + Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE); + if (vib != null) { + if (Build.VERSION.SDK_INT >= 11) { + hasVibratorService = vib.hasVibrator(); + } else { + hasVibratorService = true; + } + + if (hasVibratorService) { + SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE); + if (haptic == null) { + haptic = new SDLHaptic(); + haptic.device_id = deviceId_VIBRATOR_SERVICE; + haptic.name = "VIBRATOR_SERVICE"; + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } + } + } + + /* Check removed devices */ + ArrayList<Integer> removedDevices = new ArrayList<Integer>(); + for(int i=0; i < mHaptics.size(); i++) { + int device_id = mHaptics.get(i).device_id; + int j; + for (j=0; j < deviceIds.length; j++) { + if (device_id == deviceIds[j]) break; + } + + if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { + // don't remove the vibrator if it is still present + } else if (j == deviceIds.length) { + removedDevices.add(device_id); + } + } + + for(int i=0; i < removedDevices.size(); i++) { + int device_id = removedDevices.get(i); + SDLControllerManager.nativeRemoveHaptic(device_id); + for (int j=0; j < mHaptics.size(); j++) { + if (mHaptics.get(j).device_id == device_id) { + mHaptics.remove(j); + break; + } + } + } + } + + protected SDLHaptic getHaptic(int device_id) { + for(int i=0; i < mHaptics.size(); i++) { + if (mHaptics.get(i).device_id == device_id) { + return mHaptics.get(i); + } + } + return null; + } +} + +class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { + // Generic Motion (mouse hover, joystick...) events go here + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + float x, y; + int action; + + switch ( event.getSource() ) { + case InputDevice.SOURCE_JOYSTICK: + case InputDevice.SOURCE_GAMEPAD: + case InputDevice.SOURCE_DPAD: + return SDLControllerManager.handleJoystickMotionEvent(event); + + case InputDevice.SOURCE_MOUSE: + if (!SDLActivity.mSeparateMouseAndTouch) { + break; + } + action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + x = event.getX(0); + y = event.getY(0); + + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + default: + break; + } + break; + + default: + break; + } + + // Event was not managed + return false; + } +} +
--- a/bindings.c Thu Apr 18 19:47:50 2019 -0700 +++ b/bindings.c Thu Apr 18 19:48:04 2019 -0700 @@ -816,7 +816,7 @@ if (hostbutton == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { - warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); + debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } return; @@ -863,7 +863,7 @@ if (axis == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { - warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); + debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } goto done;
--- a/build_release Thu Apr 18 19:47:50 2019 -0700 +++ b/build_release Thu Apr 18 19:48:04 2019 -0700 @@ -25,10 +25,16 @@ cd .. fi echo "Path is: $PATH" -make PORTABLE=1 clean all +if [ $OS = "Win64" ]; then + make PORTABLE=1 OS=Windows CPU=x86_64 clean all + SDLDLLPATH=sdl/x86_64-w64-mingw32/bin +else + make PORTABLE=1 clean all + SDLDLLPATH=sdl/i686-w64-mingw32/bin +fi make menu.bin -if [ $OS = "Windows" ]; then - binaries="dis.exe zdis.exe stateview.exe vgmplay.exe blastem.exe SDL2.dll" +if [ $OS = "Windows" -o $OS = "Win64" ]; then + binaries="dis.exe zdis.exe stateview.exe vgmplay.exe blastem.exe $SDLDLLPATH/SDL2.dll" verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' blastem.c` txt=".txt" else @@ -45,6 +51,8 @@ ver=`echo $verstr | awk '/blastem/ { gsub(/\r/, "", $2); print $2 }'` if [ $OS = "Windows" ]; then suffix='-win32' +elif [ $OS = "Win64" ]; then + suffix='-win64' elif [ $OS = "Darwin" ]; then suffix='-osx' else @@ -65,7 +73,7 @@ fi cp glew/LICENSE.txt "$dir"/GLEW-LICENSE$txt -if [ $OS = "Windows" ]; then +if [ $OS = "Windows" -o $OS = "Win64" ]; then rm -f "${dir}.zip" zip -r "${dir}.zip" "$dir" echo "${dir}.zip"
--- a/build_upload_win_nightly Thu Apr 18 19:47:50 2019 -0700 +++ b/build_upload_win_nightly Thu Apr 18 19:48:04 2019 -0700 @@ -1,13 +1,13 @@ #!/bin/sh name=$1 . "$HOME/$name.params" -cd $HOME/blastem_win +cd $HOME/$DIR hg revert -a hg pull hg up rev=`hg summary | sed -E -n 's/^parent: [^:]+:([^ ]+) .*$/\1/p'` sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" blastem.c -export OS=Windows +export OS ./build_release > /tmp/build_${name}_out.log result=$? if [ $result -ne 0 ]; then @@ -31,7 +31,7 @@ . $HOME/remote.params artifact=$(tail -n 1 /tmp/build_${name}_out.log) echo "Uploaing $artifact to $REMOTE_HOST" -scp -i "$REMOTE_IDENT" "$HOME/blastem_win/$artifact" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/nightlies +scp -i "$REMOTE_IDENT" "$HOME/$DIR/$artifact" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/nightlies curdate=`date -Iseconds` version=`echo "$artifact" | sed -E 's/[^-]+-([0-9]+\.[0-9]+\.[0-9]+[^.]*)\..*$/\1/'` curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF
--- a/gen_x86.c Thu Apr 18 19:47:50 2019 -0700 +++ b/gen_x86.c Thu Apr 18 19:48:04 2019 -0700 @@ -2111,7 +2111,12 @@ } #ifdef X86_64 uint32_t stack_args = 0; +#ifdef _WIN32 + //Microsoft is too good for the ABI that everyone else uses on x86-64 apparently + uint8_t abi_regs[] = {RCX, RDX, R8, R9}; +#else uint8_t abi_regs[] = {RDI, RSI, RDX, RCX, R8, R9}; +#endif int8_t reg_swap[R15+1]; uint32_t usage = 0; memset(reg_swap, -1, sizeof(reg_swap)); @@ -2153,6 +2158,11 @@ push_r(code, arg_arr[i]); } free(arg_arr); +#if defined(X86_64) && defined(_WIN32) + sub_ir(code, 32, RSP, SZ_PTR); + code->stack_off += 32; + adjust += 32; +#endif return stack_args * sizeof(void *) + adjust; } @@ -2218,7 +2228,8 @@ push_r(code, R13); push_r(code, R14); push_r(code, R15); -#else +#endif +#if !defined(X86_64) || defined(_WIN32) push_r(code, RDI); push_r(code, RSI); #endif @@ -2226,14 +2237,15 @@ void restore_callee_save_regs(code_info *code) { +#if !defined(X86_64) || defined(_WIN32) + pop_r(code, RSI); + pop_r(code, RDI); +#endif #ifdef X86_64 pop_r(code, R15); pop_r(code, R14); pop_r(code, R13); pop_r(code, R12); -#else - pop_r(code, RSI); - pop_r(code, RDI); #endif pop_r(code, RBP); pop_r(code, RBX);
--- a/gen_x86.h Thu Apr 18 19:47:50 2019 -0700 +++ b/gen_x86.h Thu Apr 18 19:48:04 2019 -0700 @@ -63,6 +63,13 @@ #ifdef X86_64 #define SZ_PTR SZ_Q #define MAX_INST_LEN 14 +#ifdef _WIN32 +#define FIRST_ARG_REG RCX +#define SECOND_ARG_REG RDX +#else +#define FIRST_ARG_REG RDI +#define SECOND_ARG_REG RSI +#endif #else #define SZ_PTR SZ_D #define MAX_INST_LEN 11
--- a/m68k_core_x86.c Thu Apr 18 19:47:50 2019 -0700 +++ b/m68k_core_x86.c Thu Apr 18 19:48:04 2019 -0700 @@ -2658,8 +2658,11 @@ opts->start_context = (start_fun)code->cur; save_callee_save_regs(code); #ifdef X86_64 - if (opts->gen.scratch2 != RDI) { - mov_rr(code, RDI, opts->gen.scratch2, SZ_PTR); + if (opts->gen.scratch2 != FIRST_ARG_REG) { + mov_rr(code, FIRST_ARG_REG, opts->gen.scratch2, SZ_PTR); + } + if (opts->gen.context_reg != SECOND_ARG_REG) { + mov_rr(code, SECOND_ARG_REG, opts->gen.context_reg, SZ_PTR); } #else mov_rdispr(code, RSP, 20, opts->gen.scratch2, SZ_D);
--- a/net.c Thu Apr 18 19:47:50 2019 -0700 +++ b/net.c Thu Apr 18 19:48:04 2019 -0700 @@ -19,6 +19,10 @@ uint8_t get_host_address(iface_info *out) { +#ifdef __ANDROID__ + //TODO: write an implementation for Android + return 0; +#else struct ifaddrs *entries, *current, *localhost; if (getifaddrs(&entries)) { return 0; @@ -46,4 +50,5 @@ } freeifaddrs(entries); return ret; +#endif } \ No newline at end of file
--- a/nuklear_ui/blastem_nuklear.c Thu Apr 18 19:47:50 2019 -0700 +++ b/nuklear_ui/blastem_nuklear.c Thu Apr 18 19:48:04 2019 -0700 @@ -5,6 +5,7 @@ #include <stdlib.h> #include <limits.h> +#include <math.h> #include "blastem_nuklear.h" #include "nuklear_rawfb.h" #include "font.h" @@ -2058,6 +2059,7 @@ static void context_created(void) { context = nk_sdl_init(render_get_window()); + nk_sdl_device_create(); style_init(); texture_init(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/font_android.c Thu Apr 18 19:48:04 2019 -0700 @@ -0,0 +1,194 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include "../util.h" +#include "../paths.h" +#include "sfnt.h" + +typedef enum { + STATE_DEFAULT, + STATE_DECL, + STATE_COMMENT, + STATE_TAG, + STATE_PRE_ATTRIB, + STATE_ATTRIB, + STATE_PRE_VALUE, + STATE_VALUE +} parse_state; + +#define DEFAULT_WEIGHT 400 + +char *default_font_path(void) +{ + //Would probably be better to call into Java for this, but this should do for now + FILE *f = fopen("/system/etc/fonts.xml", "rb"); + if (!f) { + return NULL; + } + long size = file_size(f); + char *font_xml = malloc(size+1); + if (size != fread(font_xml, 1, size, f)) { + free(font_xml); + fclose(f); + return NULL; + } + fclose(f); + font_xml[size] = 0; + + char *last_tag = NULL, *last_attrib = NULL, *last_value = NULL; + uint8_t last_style_was_normal = 0; + char *capture_best = NULL; + char *best = NULL; + int best_weight_diff = INT_MAX; + int last_weight = INT_MAX; + parse_state state = STATE_DEFAULT; + for(char *cur = font_xml; *cur; ++cur) { + switch (state) + { + case STATE_DEFAULT: + if (*cur == '<' && cur[1]) { + cur++; + switch(*cur) + { + case '?': + state = STATE_DECL; + break; + case '!': + if (cur[1] == '-' && cur[2] == '-') { + state = STATE_COMMENT; + cur++; + } else { + debug_message("Invalid comment\n"); + cur = font_xml + size - 1; + } + break; + default: + if (capture_best) { + cur[-1] = 0; + best = strip_ws(capture_best); + capture_best = NULL; + best_weight_diff = abs(last_weight - DEFAULT_WEIGHT); + debug_message("Found candidate %s with weight %d\n", best, last_weight); + } + state = STATE_TAG; + last_tag = cur; + last_attrib = NULL; + last_value = NULL; + last_weight = INT_MAX; + break; + } + } + break; + case STATE_DECL: + if (*cur == '?' && cur[1] == '>') { + cur++; + state = STATE_DEFAULT; + } + break; + case STATE_COMMENT: + if (*cur == '-' && cur[1] == '-' && cur[2] == '>') { + cur += 2; + state = STATE_DEFAULT; + } + break; + case STATE_TAG: + if (*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r') { + *cur = 0; + state = STATE_PRE_ATTRIB; + } else if (*cur == '>') { + *cur = 0; + state = STATE_DEFAULT; + } + break; + case STATE_PRE_ATTRIB: + if (!(*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r')) { + if (*cur == '>') { + state = STATE_DEFAULT; + if (last_style_was_normal && abs(last_weight - DEFAULT_WEIGHT) < best_weight_diff) { + capture_best = cur + 1; + } else if (best && !strcmp("/family", last_tag)) { + debug_message("found family close tag, stopping search\n"); + cur = font_xml + size - 1; + } + } else { + last_attrib = cur; + state = STATE_ATTRIB; + } + } + break; + case STATE_ATTRIB: + if (*cur == '=') { + *cur = 0; + state = STATE_PRE_VALUE; + } else if (*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r') { + *cur = 0; + } + break; + case STATE_PRE_VALUE: + if (*cur == '"') { + state = STATE_VALUE; + last_value = cur + 1; + } + break; + case STATE_VALUE: + if (*cur == '"') { + *cur = 0; + state = STATE_PRE_ATTRIB; + if (!strcmp("weight", last_attrib)) { + last_weight = atoi(last_value); + } else if (!strcmp("style", last_attrib)) { + last_style_was_normal = !strcmp("normal", last_value); + } + } + break; + } + } + if (best) { + best = path_append("/system/fonts", best); + } + free(font_xml); + return best; +} + +static uint8_t *try_load_font(char *path, uint32_t *size_out) +{ + debug_message("Trying to load font %s\n", path); + FILE *f = fopen(path, "rb"); + free(path); + if (!f) { + return NULL; + } + long size = file_size(f); + uint8_t *buffer = malloc(size); + if (size != fread(buffer, 1, size, f)) { + fclose(f); + return NULL; + } + fclose(f); + sfnt_container *sfnt = load_sfnt(buffer, size); + if (!sfnt) { + free(buffer); + return NULL; + } + return sfnt_flatten(sfnt->tables, size_out); +} + +uint8_t *default_font(uint32_t *size_out) +{ + char *path = default_font_path(); + if (!path) { + goto error; + } + uint8_t *ret = try_load_font(path, size_out); + if (ret) { + return ret; + } +error: + //try some likely suspects if we failed to parse fonts.xml or failed to find the indicated font + ret = try_load_font("/system/fonts/Roboto-Regular.ttf", size_out); + if (!ret) { + ret = try_load_font("/system/fonts/DroidSans.ttf", size_out); + } + return ret; +}
--- a/render_sdl.c Thu Apr 18 19:47:50 2019 -0700 +++ b/render_sdl.c Thu Apr 18 19:48:04 2019 -0700 @@ -497,12 +497,15 @@ static GLuint load_shader(char * fname, GLenum shader_type) { + char * shader_path; + FILE *f; + GLchar *text; + long fsize; +#ifndef __ANDROID__ char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname}; - char * shader_path = alloc_concat_m(3, parts); - FILE * f = fopen(shader_path, "rb"); + shader_path = alloc_concat_m(3, parts); + f = fopen(shader_path, "rb"); free(shader_path); - GLchar * text; - long fsize; if (f) { fsize = file_size(f); text = malloc(fsize); @@ -512,6 +515,7 @@ return 0; } } else { +#endif shader_path = path_append("shaders", fname); uint32_t fsize32; text = read_bundled_file(shader_path, &fsize32); @@ -521,7 +525,9 @@ return 0; } fsize = fsize32; +#ifndef __ANDROID__ } +#endif text[fsize] = 0; if (strncmp(text, "#version", strlen("#version"))) { @@ -1190,7 +1196,11 @@ } if (vsync) { if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { +#ifdef __ANDROID__ + debug_message("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#else warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#endif } } } else { @@ -1683,7 +1693,7 @@ if ((last_frame - start) > FPS_INTERVAL) { if (start && (last_frame-start)) { #ifdef __ANDROID__ - info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); + debug_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); #else if (!fps_caption) { fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1);
--- a/vdp.c Thu Apr 18 19:47:50 2019 -0700 +++ b/vdp.c Thu Apr 18 19:48:04 2019 -0700 @@ -51,8 +51,6 @@ #define BORDER_BOT_V28_PAL 32 #define BORDER_BOT_V30_PAL 24 -#define INVALID_LINE (PAL_INACTIVE_START+BORDER_TOP_V30_PAL+BORDER_BOT_V30_PAL) - enum { INACTIVE = 0, PREPARING, //used for line 0x1FF @@ -2075,7 +2073,7 @@ } output_line = context->output_lines++;//context->vcounter - (0x200 - context->border_top); } else { - output_line = INVALID_LINE; + context->output = NULL; } context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * output_line); #ifdef DEBUG_FB_FILL @@ -2084,7 +2082,7 @@ context->output[i] = 0xFFFF00FF; } #endif - if (output_line != INVALID_LINE && (context->regs[REG_MODE_4] & BIT_H40)) { + if (context->output && (context->regs[REG_MODE_4] & BIT_H40)) { context->h40_lines++; } } @@ -2105,7 +2103,7 @@ if (context->output_lines <= lines_max && context->output_lines > 0) { context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * (context->output_lines - 1)); } else { - context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * INVALID_LINE); + context->output = NULL; } } @@ -2332,6 +2330,9 @@ OUTPUT_PIXEL_H40(slot)\ if ((slot) == BG_START_SLOT + LINEBUF_SIZE/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if (slot == 168 || slot == 247 || slot == 248) {\ render_border_garbage(\ @@ -2367,6 +2368,9 @@ OUTPUT_PIXEL_H32(slot)\ if ((slot) == BG_START_SLOT + (256+HORIZ_BORDER)/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if (slot == 136 || slot == 247 || slot == 248) {\ render_border_garbage(\ @@ -2398,6 +2402,9 @@ if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } \ if ((slot) == BG_START_SLOT + (256+HORIZ_BORDER)/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if ((slot) == 147) {\ context->hslot = 233;\ @@ -2441,6 +2448,7 @@ render_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 5)) +static uint32_t dummy_buffer[LINEBUF_SIZE]; static void vdp_h40(vdp_context * context, uint32_t target_cycles) { uint16_t address; @@ -2448,6 +2456,11 @@ uint32_t const slot_cycles = MCLKS_SLOT_H40; uint8_t bgindex = context->regs[REG_BG_COLOR] & 0x3F; uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) @@ -2654,6 +2667,11 @@ uint32_t const slot_cycles = MCLKS_SLOT_H32; uint8_t bgindex = context->regs[REG_BG_COLOR] & 0x3F; uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) @@ -2860,6 +2878,11 @@ uint32_t const slot_cycles = MCLKS_SLOT_H32; uint8_t bgindex = 0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET; uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) @@ -3002,7 +3025,7 @@ src_off = SCROLL_BUFFER_DRAW - BORDER_LEFT; len = BORDER_LEFT; } - uint8_t *src; + uint8_t *src = NULL; if (test_layer == 2) { //plane A src_off += context->buf_a_off + context->hscroll_a; @@ -3017,9 +3040,11 @@ dst += len; len = 0; } - for (; len >=0; len--, dst++, src_off++) - { - *dst = src[src_off & SCROLL_BUFFER_MASK] & 0x3F; + if (src) { + for (; len >=0; len--, dst++, src_off++) + { + *dst = src[src_off & SCROLL_BUFFER_MASK] & 0x3F; + } } context->done_composite = dst; context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_DRAW; @@ -3095,35 +3120,19 @@ } uint32_t *dst; uint8_t *debug_dst; - if ( - ( - context->vcounter < context->inactive_start + context->border_bot - || context->vcounter >= 0x200 - context->border_top - ) && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot - ) { + if (context->output && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot) { dst = context->output + 2 * (context->hslot - BG_START_SLOT); debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); } else { dst = NULL; } - if ( - !dst && context->vcounter == context->inactive_start + context->border_bot - && context->hslot >= line_change && context->hslot < bg_end_slot - ) { - dst = context->output + 2 * (context->hslot - BG_START_SLOT); - debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); - } - uint8_t test_layer = context->test_port >> 7 & 3; while(context->cycles < target_cycles) { check_switch_inactive(context, is_h40); - if (context->hslot == BG_START_SLOT && ( - context->vcounter < context->inactive_start + context->border_bot - || context->vcounter >= 0x200 - context->border_top - )) { + if (context->hslot == BG_START_SLOT && context->output) { dst = context->output + (context->hslot - BG_START_SLOT) * 2; debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); } else if (context->hslot == bg_end_slot) {
--- a/z80_to_x86.c Thu Apr 18 19:47:50 2019 -0700 +++ b/z80_to_x86.c Thu Apr 18 19:48:04 2019 -0700 @@ -3601,7 +3601,7 @@ tmp_stack_off = code->stack_off; save_callee_save_regs(code); #ifdef X86_64 - mov_rr(code, RDI, options->gen.context_reg, SZ_PTR); + mov_rr(code, FIRST_ARG_REG, options->gen.context_reg, SZ_PTR); #else mov_rdispr(code, RSP, 5 * sizeof(int32_t), options->gen.context_reg, SZ_PTR); #endif