From db816ad502e9bfcddd63dd30c80dbed7efd43a86 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Tue, 5 Apr 2016 22:04:57 -0700 Subject: [PATCH] Refactor Android variant to use common shared JSON preferences - Eliminates a whole bunch of boilerplate setter/getter code in various places - Accesses preferences directly from JSON data through custom json_parse.h API - Improves code locality for Java menus/settings --- .../deadc0de/apple2ix/Apple2AbstractMenu.java | 6 + .../org/deadc0de/apple2ix/Apple2Activity.java | 85 +- .../apple2ix/Apple2AudioSettingsMenu.java | 120 +- .../deadc0de/apple2ix/Apple2CrashHandler.java | 162 +- .../deadc0de/apple2ix/Apple2DisksMenu.java | 482 +++--- .../apple2ix/Apple2JoystickCalibration.java | 44 +- .../apple2ix/Apple2JoystickSettingsMenu.java | 233 ++- .../apple2ix/Apple2KeyboardSettingsMenu.java | 136 +- .../apple2ix/Apple2KeypadChooser.java | 140 +- .../apple2ix/Apple2KeypadSettingsMenu.java | 294 +++- .../org/deadc0de/apple2ix/Apple2MainMenu.java | 16 +- .../apple2ix/Apple2PortraitCalibration.java | 117 +- .../deadc0de/apple2ix/Apple2Preferences.java | 1315 +++-------------- .../deadc0de/apple2ix/Apple2SettingsMenu.java | 92 +- .../org/deadc0de/apple2ix/Apple2Utils.java | 365 +++++ .../apple2ix/Apple2VideoSettingsMenu.java | 80 +- .../org/deadc0de/apple2ix/Apple2View.java | 88 +- Android/jni/jnihooks.c | 56 +- Android/jni/jniprefs.c | 334 ----- Android/jni/sources.mk | 6 +- src/audio/mockingboard.c | 17 + src/cpu-supp.c | 2 - src/display.c | 17 +- src/display.h | 5 +- src/interface.c | 13 +- src/interface.h | 17 +- src/joystick.c | 25 - src/joystick.h | 95 +- src/json_parse.c | 24 + src/json_parse.h | 5 +- src/keys.c | 10 - src/keys.h | 30 - src/misc.c | 7 +- src/prefs.c | 70 +- src/prefs.h | 66 +- src/test/testprefs.c | 68 +- src/timing.c | 47 +- src/video/glalert.c | 41 +- src/video/glnode.c | 26 +- src/video/glnode.h | 2 - src/video/gltouchjoy.c | 258 +--- src/video/gltouchjoy.h | 37 +- src/video/gltouchjoy_joy.c | 78 +- src/video/gltouchjoy_kpad.c | 239 ++- src/video/gltouchkbd.c | 240 ++- src/video/gltouchmenu.c | 139 +- src/video/glvideo.c | 55 +- 47 files changed, 2805 insertions(+), 2999 deletions(-) create mode 100644 Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Utils.java delete mode 100644 Android/jni/jniprefs.c diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AbstractMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AbstractMenu.java index 1da8b9e1..b5518840 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AbstractMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AbstractMenu.java @@ -82,6 +82,12 @@ public abstract class Apple2AbstractMenu implements Apple2MenuView { public String getSummary(final Apple2Activity activity); + public String getPrefDomain(); + + public String getPrefKey(); + + public Object getPrefDefault(); + public View getView(final Apple2Activity activity, View convertView); public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked); diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java index 957c8bc4..d97bc2a0 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java @@ -42,7 +42,6 @@ public class Apple2Activity extends Activity { private static volatile boolean DEBUG_STRICT = false; private Apple2View mView = null; - private Runnable mGraphicsInitializedRunnable = null; private Apple2SplashScreen mSplashScreen = null; private Apple2MainMenu mMainMenu = null; private Apple2SettingsMenu mSettingsMenu = null; @@ -78,18 +77,14 @@ public class Apple2Activity extends Activity { private static native String nativeLoadState(String path); - private static native void nativeEmulationResume(); + private static native boolean nativeEmulationResume(); - private static native void nativeEmulationPause(); + private static native boolean nativeEmulationPause(); private static native void nativeOnQuit(); private static native void nativeReboot(); - private static native void nativeChooseDisk(String path, boolean driveA, boolean readOnly); - - private static native void nativeEjectDisk(boolean driveA); - public final static boolean isNativeBarfed() { return sNativeBarfed; } @@ -119,8 +114,6 @@ public class Apple2Activity extends Activity { setContentView(new View(this)); } - Apple2Preferences.LANDSCAPE_MODE.load(this); - Apple2CrashHandler.getInstance().initializeAndSetCustomExceptionHandler(this); if (sNativeBarfed) { Log.e(TAG, "NATIVE BARFED...", sNativeBarfedThrowable); @@ -132,20 +125,17 @@ public class Apple2Activity extends Activity { int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(this, /*isStereo:*/true); Log.d(TAG, "Device sampleRate:" + sampleRate + " mono bufferSize:" + monoBufferSize + " stereo bufferSize:" + stereoBufferSize); - String dataDir = Apple2DisksMenu.getDataDir(this); + String dataDir = Apple2Utils.getDataDir(this); nativeOnCreate(dataDir, sampleRate, monoBufferSize, stereoBufferSize); - final boolean firstTime = (Apple2Preferences.EMULATOR_VERSION.intValue(this) != BuildConfig.VERSION_CODE); - if (firstTime) { - // allow for primitive migrations as needed - Apple2Preferences.EMULATOR_VERSION.saveInt(this, BuildConfig.VERSION_CODE); - Log.v(TAG, "Triggering migration to Apple2ix version : " + BuildConfig.VERSION_NAME); + // NOTE: ordering here is important! + Apple2Preferences.load(this); + final boolean firstTime = Apple2Preferences.migrate(this); + Apple2VideoSettingsMenu.SETTINGS.applyLandscapeMode(this); + Apple2Preferences.sync(this, null); - // HACK FIXME TODO 2016/01/24 REMOVE AFTER version 16 ships ... force enables Mockingboard - if (BuildConfig.VERSION_CODE == 16) { - Apple2Preferences.MOCKINGBOARD_ENABLED.saveBoolean(this, true); - } - } + Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A), /*driveA:*/true, (boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO)); + Apple2DisksMenu.insertDisk((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B), /*driveA:*/false, (boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO)); showSplashScreen(!firstTime); Apple2CrashHandler.getInstance().checkForCrashes(this); @@ -171,25 +161,15 @@ public class Apple2Activity extends Activity { } } - mGraphicsInitializedRunnable = new Runnable() { - @Override - public void run() { - if (firstTime) { - Apple2Preferences.KeypadPreset.IJKM_SPACE.apply(Apple2Activity.this); - } - Apple2Preferences.loadPreferences(Apple2Activity.this); - } - }; - // first-time initializations final boolean externalStoragePermission = extperm; if (firstTime) { new Thread(new Runnable() { @Override public void run() { - Apple2DisksMenu.exposeAPKAssets(Apple2Activity.this); + Apple2Utils.exposeAPKAssets(Apple2Activity.this); if (externalStoragePermission) { - Apple2DisksMenu.exposeAPKAssetsToExternal(Apple2Activity.this); + Apple2Utils.exposeAPKAssetsToExternal(Apple2Activity.this); } mSplashScreen.setDismissable(true); Log.d(TAG, "Finished first time copying..."); @@ -226,7 +206,7 @@ public class Apple2Activity extends Activity { } if (grantedPermissions) { // this will force copying APK files (now that we have permission - Apple2DisksMenu.exposeAPKAssetsToExternal(Apple2Activity.this); + Apple2Utils.exposeAPKAssetsToExternal(Apple2Activity.this); } // else ... we keep nagging on app startup ... } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -258,6 +238,12 @@ public class Apple2Activity extends Activity { return; } + if (isEmulationPaused()) { + Apple2Preferences.save(this); + } else { + Log.d(TAG, "Letting native save preferences..."); + } + Log.d(TAG, "onPause()"); if (mView != null) { mView.onPause(); @@ -347,19 +333,20 @@ public class Apple2Activity extends Activity { return; } - Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(Apple2Activity.this, true); + Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO, true); final int len = diskPath.length(); final String suffix = diskPath.substring(len - 3, len); if (suffix.equalsIgnoreCase(".gz")) { // HACK FIXME TODO : small amount of code duplication of Apple2DisksMenu diskPath = diskPath.substring(0, len - 3); } - Apple2Preferences.CURRENT_DISK_A.saveString(Apple2Activity.this, diskPath); + + Apple2DisksMenu.insertDisk(diskPath, /*driveA:*/true, /*readOnly:*/true); while (mDisksMenu.popPathStack() != null) { /* ... */ } - File storageDir = Apple2DisksMenu.getExternalStorageDirectory(Apple2Activity.this); + File storageDir = Apple2Utils.getExternalStorageDirectory(Apple2Activity.this); if (storageDir != null) { String storagePath = storageDir.getAbsolutePath(); if (diskPath.contains(storagePath)) { @@ -402,8 +389,7 @@ public class Apple2Activity extends Activity { boolean glViewFirstTime = false; if (mView == null) { glViewFirstTime = true; - mView = new Apple2View(this, mGraphicsInitializedRunnable); - mGraphicsInitializedRunnable = null; + mView = new Apple2View(this); mMainMenu = new Apple2MainMenu(this, mView); } @@ -424,7 +410,7 @@ public class Apple2Activity extends Activity { // mMenuStack.add(apple2MenuView); View menuView = apple2MenuView.getView(); - nativeEmulationPause(); + pauseEmulation(); addContentView(menuView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); } @@ -498,7 +484,7 @@ public class Apple2Activity extends Activity { if (dismissedSplashScreen) { setupGLView(); } else { - nativeEmulationResume(); + maybeResumeEmulation(); } } } @@ -512,12 +498,19 @@ public class Apple2Activity extends Activity { public void maybeResumeEmulation() { if (mMenuStack.size() == 0 && !mPausing.get()) { - nativeEmulationResume(); + Apple2Preferences.sync(this, null); + boolean previouslyPaused = nativeEmulationResume(); + if (BuildConfig.DEBUG && !previouslyPaused) { + throw new RuntimeException("expecting native CPU thread to have been paused"); + } } } public void pauseEmulation() { - nativeEmulationPause(); + boolean previouslyRunning = nativeEmulationPause(); + if (previouslyRunning) { + Apple2Preferences.load(this); + } } public void rebootEmulation() { @@ -532,14 +525,6 @@ public class Apple2Activity extends Activity { return Apple2Activity.nativeLoadState(stateFile); } - public void chooseDisk(String path, boolean driveA, boolean readOnly) { - nativeChooseDisk(path, driveA, readOnly); - } - - public void ejectDisk(boolean driveA) { - nativeEjectDisk(driveA); - } - public void quitEmulator() { nativeOnQuit(); finish(); diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AudioSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AudioSettingsMenu.java index 84599f58..6f73ab0d 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AudioSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2AudioSettingsMenu.java @@ -17,13 +17,19 @@ import android.widget.CompoundButton; import android.widget.TextView; import org.deadc0de.apple2ix.basic.R; +import org.json.JSONArray; public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { private final static String TAG = "Apple2AudioSettingsMenu"; + private final static int AUDIO_LATENCY_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; + + private static int sSampleRateCanary = 0; + public Apple2AudioSettingsMenu(Apple2Activity activity) { super(activity); + sSampleRateCanary = DevicePropertyCalculator.getRecommendedSampleRate(activity); } @Override @@ -49,6 +55,27 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { return position == SETTINGS.MOCKINGBOARD_ENABLED.ordinal(); } + public enum Volume { + OFF(0), + ONE(1), + TWO(2), + THREE(3), + FOUR(4), + MEDIUM(5), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8), + NINE(9), + MAX(10), + ELEVEN(11); // Ours goes to eleven... + private int vol; + + Volume(int vol) { + this.vol = vol; + } + } + enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { SPEAKER_VOLUME { @Override @@ -61,17 +88,28 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.speaker_volume_summary); } + @Override + public String getPrefKey() { + return "speakerVolume"; + } + + @Override + public Object getPrefDefault() { + return Volume.MEDIUM.ordinal(); + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, Volume.MAX.ordinal() - 1, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.SPEAKER_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]); + Apple2Preferences.setJSONPref(self, Volume.values()[progress].ordinal()); } @Override public int intValue() { - return Apple2Preferences.SPEAKER_VOLUME.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override @@ -92,14 +130,25 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.mockingboard_enable_summary); } + @Override + public String getPrefKey() { + return "mbEnabled"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.MOCKINGBOARD_ENABLED.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.MOCKINGBOARD_ENABLED.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -116,17 +165,28 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.mockingboard_volume_summary); } + @Override + public String getPrefKey() { + return "mbVolume"; + } + + @Override + public Object getPrefDefault() { + return Volume.MEDIUM.ordinal(); + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, Volume.MAX.ordinal() - 1, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.MOCKINGBOARD_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]); + Apple2Preferences.setJSONPref(self, Volume.values()[progress].ordinal()); } @Override public int intValue() { - return Apple2Preferences.MOCKINGBOARD_VOLUME.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override @@ -158,26 +218,46 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.audio_latency_summary); } + @Override + public String getPrefKey() { + return "audioLatency"; + } + + @Override + public Object getPrefDefault() { + int defaultLatency; + if (Apple2AudioSettingsMenu.sSampleRateCanary == DevicePropertyCalculator.defaultSampleRate) { + // quite possibly an audio-challenged device + defaultLatency = 8; // /AUDIO_LATENCY_NUM_CHOICES -> 0.4f + } else { + // reasonable default for high-end devices + defaultLatency = 5; // /AUDIO_LATENCY_NUM_CHOICES -> 0.25f + } + return ((float) defaultLatency) / AUDIO_LATENCY_NUM_CHOICES; + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, AUDIO_LATENCY_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { if (progress == 0) { // disallow 0-length buffer ... progress = 1; } - Apple2Preferences.AUDIO_LATENCY.saveInt(activity, progress); + Apple2Preferences.setJSONPref(self, ((float) progress) / AUDIO_LATENCY_NUM_CHOICES); } @Override public int intValue() { - return Apple2Preferences.AUDIO_LATENCY.intValue(activity); + float pref = Apple2Preferences.getFloatJSONPref(self); + return (int) (pref * AUDIO_LATENCY_NUM_CHOICES); } @Override public void showValue(int progress, final TextView seekBarValue) { - seekBarValue.setText("" + ((float) progress / Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES)); + seekBarValue.setText("" + ((float) progress / AUDIO_LATENCY_NUM_CHOICES)); } }); } @@ -185,9 +265,23 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_AUDIO; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2CrashHandler.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2CrashHandler.java index 956e4c2b..4743c2d2 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2CrashHandler.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2CrashHandler.java @@ -21,16 +21,13 @@ import android.os.Build; import android.os.Environment; import android.util.Log; import android.view.View; -import android.widget.Button; import android.widget.ProgressBar; import org.deadc0de.apple2ix.basic.BuildConfig; import org.deadc0de.apple2ix.basic.R; -import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; -import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; @@ -45,6 +42,67 @@ public class Apple2CrashHandler { return sCrashHandler; } + public enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { + GL_VENDOR { + @Override + public String getPrefKey() { + return "glVendor"; + } + + @Override + public Object getPrefDefault() { + return "unknown"; + } + }, + GL_RENDERER { + @Override + public String getPrefKey() { + return "glRenderer"; + } + + @Override + public Object getPrefDefault() { + return "unknown"; + } + }, + GL_VERSION { + @Override + public String getPrefKey() { + return "glVersion"; + } + + @Override + public Object getPrefDefault() { + return "unknown"; + } + }; + + @Override + public final String getTitle(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public final String getSummary(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_INTERFACE; + } + + @Override + public View getView(final Apple2Activity activity, View convertView) { + throw new RuntimeException(); + } + + @Override + public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + throw new RuntimeException(); + } + } + public enum CrashType { JAVA_CRASH { @Override @@ -89,7 +147,7 @@ public class Apple2CrashHandler { public synchronized void initializeAndSetCustomExceptionHandler(Apple2Activity activity) { synchronized (this) { if (homeDir == null) { - homeDir = Apple2DisksMenu.getDataDir(activity); + homeDir = Apple2Utils.getDataDir(activity); } } if (mDefaultExceptionHandler != null) { @@ -137,7 +195,7 @@ public class Apple2CrashHandler { return; } - if (!Apple2Preferences.CRASH_CHECK.booleanValue(activity)) { + if (!(boolean) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CRASH)) { return; } @@ -169,7 +227,9 @@ public class Apple2CrashHandler { } // remove previous log file - _writeTempLogFile(activity, new StringBuilder()); + File allCrashFile = _getCrashFile(activity); + Apple2Utils.writeFile(new StringBuilder(), allCrashFile); + allCrashFile.delete(); return; } @@ -216,9 +276,9 @@ public class Apple2CrashHandler { summary.append("SAMPLE RATE: ").append(sampleRate).append("\n"); summary.append("MONO BUFSIZE: ").append(monoBufferSize).append("\n"); summary.append("STEREO BUFSIZE: ").append(stereoBufferSize).append("\n"); - summary.append("GPU VENDOR: ").append(Apple2Preferences.GL_VENDOR.stringValue(activity)).append("\n"); - summary.append("GPU RENDERER: ").append(Apple2Preferences.GL_RENDERER.stringValue(activity)).append("\n"); - summary.append("GPU VERSION: ").append(Apple2Preferences.GL_VERSION.stringValue(activity)).append("\n"); + summary.append("GPU VENDOR: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_VENDOR)).append("\n"); + summary.append("GPU RENDERER: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_RENDERER)).append("\n"); + summary.append("GPU VERSION: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_VERSION)).append("\n"); try { PackageInfo pInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0); @@ -246,7 +306,7 @@ public class Apple2CrashHandler { }); if (len > 0) { - Apple2DisksMenu.exposeSymbols(activity); + Apple2Utils.exposeSymbols(activity); } activity.runOnUiThread(new Runnable() { @@ -274,7 +334,7 @@ public class Apple2CrashHandler { } StringBuilder crashData = new StringBuilder(); - if (!_readFile(new File(processedPath), crashData)) { + if (!Apple2Utils.readEntireFile(new File(processedPath), crashData)) { Log.e(TAG, "Error processing crash : " + crashPath); } allCrashData.append(">>>>>>> NATIVE CRASH [").append(crashPath).append("]\n"); @@ -336,7 +396,7 @@ public class Apple2CrashHandler { File javaCrashFile = _javaCrashFile(activity); if (javaCrashFile.exists()) { Log.d(TAG, "Reading java crashes file"); - if (!_readFile(javaCrashFile, javaCrashData)) { + if (!Apple2Utils.readEntireFile(javaCrashFile, javaCrashData)) { Log.e(TAG, "Error processing java crash : " + javaCrashFileName); } } @@ -356,7 +416,7 @@ public class Apple2CrashHandler { } }); - Apple2DisksMenu.unexposeSymbols(activity); + Apple2Utils.unexposeSymbols(activity); activity.runOnUiThread(new Runnable() { @Override public void run() { @@ -465,76 +525,15 @@ public class Apple2CrashHandler { return crashPath.substring(0, crashPath.length() - 4) + ".txt"; } - private boolean _readFile(File file, StringBuilder fileData) { - final int maxAttempts = 5; - int attempts = 0; - do { - try { - BufferedReader reader = new BufferedReader(new FileReader(file)); - char[] buf = new char[1024]; - int numRead = 0; - while ((numRead = reader.read(buf)) != -1) { - String readData = String.valueOf(buf, 0, numRead); - fileData.append(readData); - } - reader.close(); - break; - } catch (InterruptedIOException ie) { - /* EINTR, EAGAIN ... */ - } catch (IOException e) { - Log.d(TAG, "Error reading file at path : " + file.toString()); - } - - try { - Thread.sleep(100, 0); - } catch (InterruptedException e) { - /* ... */ - } - ++attempts; - } while (attempts < maxAttempts); - - return attempts < maxAttempts; - } - - private File _writeTempLogFile(Apple2Activity activity, StringBuilder allCrashData) { - - File allCrashFile = null; - + private File _getCrashFile(Apple2Activity activity) { + File allCrashFile; String storageState = Environment.getExternalStorageState(); if (storageState.equals(Environment.MEDIA_MOUNTED)) { allCrashFile = new File(Environment.getExternalStorageDirectory(), "apple2ix_crash.txt"); } else { - allCrashFile = new File(Apple2DisksMenu.getDataDir(activity), "apple2ix_crash.txt"); + allCrashFile = new File(Apple2Utils.getDataDir(activity), "apple2ix_crash.txt"); } - Log.d(TAG, "Writing all crashes to temp file : " + allCrashFile.getAbsolutePath()); - final int maxAttempts = 5; - int attempts = 0; - do { - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(allCrashFile)); - writer.append(allCrashData); - writer.flush(); - writer.close(); - break; - } catch (InterruptedIOException ie) { - /* EINTR, EAGAIN ... */ - } catch (IOException e) { - Log.e(TAG, "Exception attempting to write data : " + e); - } - - try { - Thread.sleep(100, 0); - } catch (InterruptedException e) { - /* ... */ - } - ++attempts; - } while (attempts < maxAttempts); - - if (!allCrashFile.setReadable(true, /*ownerOnly:*/false)) { - Log.d(TAG, "Oops, could not set all crash data readable!"); - } - return allCrashFile; } @@ -553,9 +552,14 @@ public class Apple2CrashHandler { int len = summary.length(); len = len < maxCharsEmail ? len : maxCharsEmail; String summaryData = summary.substring(0, len); - emailIntent.putExtra(Intent.EXTRA_TEXT, "The app crashed, please help!\n\n"+summaryData); + emailIntent.putExtra(Intent.EXTRA_TEXT, "The app crashed, please help!\n\n" + summaryData); + + File allCrashFile = _getCrashFile(activity); + Apple2Utils.writeFile(allCrashData, allCrashFile); + if (!allCrashFile.setReadable(true, /*ownerOnly:*/false)) { + Log.d(TAG, "Oops, could not set file data readable!"); + } - File allCrashFile = _writeTempLogFile(activity, allCrashData); emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(allCrashFile)); Log.d(TAG, "STARTING CHOOSER FOR EMAIL ..."); diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2DisksMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2DisksMenu.java index 031410a4..f3a9f1a6 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2DisksMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2DisksMenu.java @@ -14,11 +14,7 @@ package org.deadc0de.apple2ix; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; import android.graphics.drawable.Drawable; -import android.os.Environment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -30,39 +26,138 @@ import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.RadioButton; import org.json.JSONArray; import org.json.JSONException; import java.io.File; -import java.io.FileOutputStream; import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; -import java.util.zip.GZIPOutputStream; import org.deadc0de.apple2ix.basic.R; public class Apple2DisksMenu implements Apple2MenuView { private final static String TAG = "Apple2DisksMenu"; - private static String sDataDir = null; private Apple2Activity mActivity = null; private View mDisksView = null; private final ArrayList mPathStack = new ArrayList(); - private static File sExternalFilesDir = null; - private static File sDownloadFilesDir = null; private static boolean sInitializedPath = false; + private static native void nativeChooseDisk(String path, boolean driveA, boolean readOnly); + + private static native void nativeEjectDisk(boolean driveA); + + protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { + CURRENT_DISK_SEARCH_PATH { + @Override + public String getPrefKey() { + return "diskPathStack"; + } + + @Override + public Object getPrefDefault() { + return new JSONArray(); + } + }, + CURRENT_DRIVE_A { + @Override + public String getPrefKey() { + return "driveACurrent"; + } + + @Override + public Object getPrefDefault() { + return true; + } + }, + CURRENT_DISK_RO_BUTTON { + @Override + public String getPrefKey() { + return "driveACurrent"; + } + + @Override + public Object getPrefDefault() { + return true; + } + }, + CURRENT_DISK_PATH_A { + @Override + public String getPrefKey() { + return "driveAInsertedDisk"; + } + + @Override + public Object getPrefDefault() { + return ""; + } + }, + CURRENT_DISK_PATH_A_RO { + @Override + public String getPrefKey() { + return "driveAInsertedDiskRO"; + } + + @Override + public Object getPrefDefault() { + return true; + } + }, + CURRENT_DISK_PATH_B { + @Override + public String getPrefKey() { + return "driveBInsertedDisk"; + } + + @Override + public Object getPrefDefault() { + return ""; + } + }, + CURRENT_DISK_PATH_B_RO { + @Override + public String getPrefKey() { + return "driveBInsertedDiskRO"; + } + + @Override + public Object getPrefDefault() { + return true; + } + }; + + @Override + public final String getTitle(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public final String getSummary(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_VM; + } + + @Override + public View getView(final Apple2Activity activity, View convertView) { + throw new RuntimeException(); + } + + @Override + public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + throw new RuntimeException(); + } + } + public Apple2DisksMenu(Apple2Activity activity) { mActivity = activity; @@ -77,150 +172,7 @@ public class Apple2DisksMenu implements Apple2MenuView { } }); - getExternalStorageDirectory(activity); - } - - public static File getExternalStorageDirectory(Apple2Activity activity) { - - do { - if (sExternalFilesDir != null) { - break; - } - - String storageState = Environment.getExternalStorageState(); - if (!storageState.equals(Environment.MEDIA_MOUNTED)) { - // 2015/10/28 : do not expose sExternalFilesDir/sDownloadFilesDir unless they are writable - break; - } - - File externalStorageDir = Environment.getExternalStorageDirectory(); - if (externalStorageDir == null) { - break; - } - - File externalDir = new File(externalStorageDir, "apple2ix"); // /sdcard/apple2ix - if (!externalDir.exists()) { - boolean made = externalDir.mkdirs(); - if (!made) { - Log.d(TAG, "WARNING: could not make directory : " + sExternalFilesDir); - break; - } - } - - sExternalFilesDir = externalDir; - sDownloadFilesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - } while (false); - - return sExternalFilesDir; - } - - // HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources - // out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about - // security or DRM for these assets =) - public static String getDataDir(Apple2Activity activity) { - - if (sDataDir != null) { - return sDataDir; - } - - try { - PackageManager pm = activity.getPackageManager(); - PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), 0); - sDataDir = pi.applicationInfo.dataDir; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "" + e); - if (sDataDir == null) { - sDataDir = "/data/local/tmp"; - } - } - - return sDataDir; - } - - public static void exposeAPKAssetsToExternal(Apple2Activity activity) { - getExternalStorageDirectory(activity); - if (sExternalFilesDir == null) { - return; - } - - final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - bar.setVisibility(View.VISIBLE); - bar.setIndeterminate(true); - } catch (NullPointerException npe) { - Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #1"); - } - } - }); - - Log.v(TAG, "Overwriting system files in /sdcard/apple2ix/ (external storage) ..."); - recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/sExternalFilesDir.getAbsolutePath(), false); - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - bar.setVisibility(View.INVISIBLE); - bar.setIndeterminate(false); - } catch (NullPointerException npe) { - Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #2"); - } - } - }); - } - - public static void exposeAPKAssets(Apple2Activity activity) { - final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - bar.setVisibility(View.VISIBLE); - bar.setIndeterminate(true); - } catch (NullPointerException npe) { - Log.v(TAG, "Avoid NPE in exposeAPKAssets #1"); - } - } - }); - - getDataDir(activity); - - // FIXME TODO : Heavy-handed migration to 1.1.3 ... - recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "blanks")); - recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "demo")); - recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "eamon")); - recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "logo")); - recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "miscgame")); - - Log.d(TAG, "First time copying stuff-n-things out of APK for ease-of-NDK access..."); - - getExternalStorageDirectory(activity); - recursivelyCopyAPKAssets(activity, /*from APK directory:*/"disks", /*to location:*/new File(sDataDir, "disks").getAbsolutePath(), true); - recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/new File(sDataDir, "keyboards").getAbsolutePath(), false); - recursivelyCopyAPKAssets(activity, /*from APK directory:*/"shaders", /*to location:*/new File(sDataDir, "shaders").getAbsolutePath(), false); - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - bar.setVisibility(View.INVISIBLE); - bar.setIndeterminate(false); - } catch (NullPointerException npe) { - Log.v(TAG, "Avoid NPE in exposeAPKAssets #1"); - } - } - }); - } - - public static void exposeSymbols(Apple2Activity activity) { - recursivelyCopyAPKAssets(activity, /*from APK directory:*/"symbols", /*to location:*/new File(sDataDir, "symbols").getAbsolutePath(), false); - } - - public static void unexposeSymbols(Apple2Activity activity) { - recursivelyDelete(new File(sDataDir, "symbols")); + Apple2Utils.getExternalStorageDirectory(activity); } // ------------------------------------------------------------------------ @@ -240,7 +192,7 @@ public class Apple2DisksMenu implements Apple2MenuView { } if (!sInitializedPath) { sInitializedPath = true; - Apple2Preferences.CURRENT_DISK_PATH.load(mActivity); + setPathStackJSON((JSONArray) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH)); } dynamicSetup(); mActivity.pushApple2View(this); @@ -272,15 +224,13 @@ public class Apple2DisksMenu implements Apple2MenuView { // ------------------------------------------------------------------------ // path stack methods - public String getPathStackJSON() { - JSONArray jsonArray = new JSONArray(Arrays.asList(mPathStack.toArray())); - return jsonArray.toString(); + public JSONArray getPathStackJSON() { + return new JSONArray(Arrays.asList(mPathStack.toArray())); } - public void setPathStackJSON(String pathStackJSON) { + public void setPathStackJSON(JSONArray jsonArray) { mPathStack.clear(); try { - JSONArray jsonArray = new JSONArray(pathStackJSON); for (int i = 0, count = jsonArray.length(); i < count; i++) { String pathComponent = jsonArray.getString(i); mPathStack.add(pathComponent); @@ -292,7 +242,7 @@ public class Apple2DisksMenu implements Apple2MenuView { public void pushPathStack(String path) { mPathStack.add(path); - Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON()); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH, getPathStackJSON()); } public String popPathStack() { @@ -300,10 +250,32 @@ public class Apple2DisksMenu implements Apple2MenuView { return null; } String path = mPathStack.remove(mPathStack.size() - 1); - Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON()); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH, getPathStackJSON()); return path; } + public static void insertDisk(String fullPath, boolean isDriveA, boolean isReadOnly) { + File file = new File(fullPath); + final String imageName = fullPath; + if (!file.exists()) { + fullPath = fullPath + ".gz"; + file = new File(fullPath); + } + if (file.exists()) { + if (isDriveA) { + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A_RO, isReadOnly); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A, imageName); + } else { + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B_RO, isReadOnly); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B, imageName); + } + nativeChooseDisk(fullPath, isDriveA, isReadOnly); + } else { + Log.d(TAG, "Cannot insert: " + fullPath); + } + } + + public static boolean hasDiskExtension(String name) { // check file extensions ... sigh ... no String.endsWithIgnoreCase() ? @@ -360,119 +332,6 @@ public class Apple2DisksMenu implements Apple2MenuView { return pathBuffer.toString(); } - // TODO FIXME : WARNING : this is super dangerous if there are symlinks !!! - private static void recursivelyDelete(File file) { - if (file.isDirectory()) { - for (File f : file.listFiles()) { - recursivelyDelete(f); - } - } - if (!file.delete()) { - Log.d(TAG, "Failed to delete file: " + file); - } - } - - private static void recursivelyCopyAPKAssets(Apple2Activity activity, String srcFileOrDir, String dstFileOrDir, boolean shouldGzip) { - AssetManager assetManager = activity.getAssets(); - - final int maxAttempts = 5; - String[] files = null; - int attempts = 0; - do { - try { - files = assetManager.list(srcFileOrDir); - break; - } catch (InterruptedIOException e) { - /* EINTR, EAGAIN ... */ - } catch (IOException e) { - Log.d(TAG, "OOPS exception attempting to list APK files at : " + srcFileOrDir + " : " + e); - } - - try { - Thread.sleep(100, 0); - } catch (InterruptedException ie) { - /* ... */ - } - ++attempts; - } while (attempts < maxAttempts); - - if (files == null) { - Log.d(TAG, "OOPS, could not list APK assets at : " + srcFileOrDir); - return; - } - - if (files.length > 0) { - // ensure destination directory exists - File dstPath = new File(dstFileOrDir); - if (!dstPath.mkdirs()) { - if (!dstPath.exists()) { - Log.d(TAG, "OOPS, could not mkdirs on " + dstPath); - return; - } - } - for (String filename : files) { - // iterate on files and subdirectories - recursivelyCopyAPKAssets(activity, srcFileOrDir + File.separator + filename, dstFileOrDir + File.separator + filename, shouldGzip); - } - return; - } - - // presumably this is a file, not a subdirectory - InputStream is = null; - OutputStream os = null; - attempts = 0; - do { - try { - is = assetManager.open(srcFileOrDir); - if (shouldGzip) { - os = new GZIPOutputStream(new FileOutputStream(dstFileOrDir + ".gz")); - } else { - os = new FileOutputStream(dstFileOrDir); - } - copyFile(is, os); - break; - } catch (InterruptedIOException e) { - /* EINTR, EAGAIN */ - } catch (IOException e) { - Log.e(TAG, "Failed to copy asset file: " + srcFileOrDir, e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // NOOP - } - } - if (os != null) { - try { - os.close(); - } catch (IOException e) { - // NOOP - } - } - } - try { - Thread.sleep(100, 0); - } catch (InterruptedException ie) { - /* ... */ - } - ++attempts; - } while (attempts < maxAttempts); - } - - private static void copyFile(InputStream is, OutputStream os) throws IOException { - final int BUF_SZ = 4096; - byte[] buf = new byte[BUF_SZ]; - while (true) { - int len = is.read(buf, 0, BUF_SZ); - if (len < 0) { - break; - } - os.write(buf, 0, len); - } - os.flush(); - } - private void dynamicSetup() { final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings); @@ -482,7 +341,7 @@ public class Apple2DisksMenu implements Apple2MenuView { boolean isRootPath = false; if (disksDir == null) { isRootPath = true; - disksDir = sDataDir + File.separator + "disks"; // default path + disksDir = Apple2Utils.getDataDir(mActivity) + File.separator + "disks"; // default path } File dir = new File(disksDir); @@ -511,21 +370,22 @@ public class Apple2DisksMenu implements Apple2MenuView { Arrays.sort(files); - getExternalStorageDirectory(mActivity); - final boolean includeExternalStoragePath = (sExternalFilesDir != null && isRootPath); - final boolean includeDownloadsPath = (sDownloadFilesDir != null && isRootPath); + File extStorageDir = Apple2Utils.getExternalStorageDirectory(mActivity); + File downloadsDir = Apple2Utils.getDownloadsDirectory(mActivity); + final boolean includeExternalStoragePath = (extStorageDir != null && isRootPath); + final boolean includeDownloadsPath = (downloadsDir != null && isRootPath); final int offset = includeExternalStoragePath ? (includeDownloadsPath ? 2 : 1) : (includeDownloadsPath ? 1 : 0); final String[] fileNames = new String[files.length + offset]; final boolean[] isDirectory = new boolean[files.length + offset]; int idx = 0; if (includeExternalStoragePath) { - fileNames[idx] = sExternalFilesDir.getAbsolutePath(); + fileNames[idx] = Apple2Utils.getExternalStorageDirectory(mActivity).getAbsolutePath(); isDirectory[idx] = true; ++idx; } if (includeDownloadsPath) { - fileNames[idx] = sDownloadFilesDir.getAbsolutePath(); + fileNames[idx] = downloadsDir.getAbsolutePath(); isDirectory[idx] = true; ++idx; } @@ -570,26 +430,26 @@ public class Apple2DisksMenu implements Apple2MenuView { } String eject = mActivity.getResources().getString(R.string.disk_eject); - if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) { + if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_A))) { Button ejectButton = new Button(mActivity); ejectButton.setText(eject + " 1"); ejectButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mActivity.ejectDisk(/*driveA:*/true); - Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, ""); + nativeEjectDisk(/*driveA:*/true); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A, ""); dynamicSetup(); } }); layout.addView(ejectButton); - } else if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) { + } else if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_B))) { Button ejectButton = new Button(mActivity); ejectButton.setText(eject + " 2"); ejectButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mActivity.ejectDisk(/*driveA:*/false); - Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, ""); + nativeEjectDisk(/*driveA:*/false); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B, ""); dynamicSetup(); } }); @@ -629,15 +489,15 @@ public class Apple2DisksMenu implements Apple2MenuView { } final String imageName = str; - if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) { - mActivity.ejectDisk(/*driveA:*/true); - Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, ""); + if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_A))) { + nativeEjectDisk(/*driveA:*/true); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A, ""); dynamicSetup(); return; } - if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) { - mActivity.ejectDisk(/*driveA:*/false); - Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, ""); + if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_B))) { + nativeEjectDisk(/*driveA:*/false); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B, ""); dynamicSetup(); return; } @@ -650,29 +510,31 @@ public class Apple2DisksMenu implements Apple2MenuView { final View diskConfirmationView = inflater.inflate(R.layout.a2disk_confirmation, null, false); builder.setView(diskConfirmationView); - final RadioButton diskA = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskA); - diskA.setChecked(Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity)); - diskA.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + final RadioButton driveA = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskA); + boolean driveAChecked = (boolean) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DRIVE_A); + driveA.setChecked(driveAChecked); + driveA.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.CURRENT_DRIVE_A_BUTTON.saveBoolean(mActivity, isChecked); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DRIVE_A, isChecked); } }); - final RadioButton diskB = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskB); - diskB.setChecked(!Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity)); + final RadioButton driveB = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskB); + driveB.setChecked(!driveAChecked); final RadioButton readOnly = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readOnly); - readOnly.setChecked(Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity)); + boolean roChecked = (boolean) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_RO_BUTTON); + readOnly.setChecked(roChecked); readOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.CURRENT_DISK_RO_BUTTON.saveBoolean(mActivity, isChecked); + Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_RO_BUTTON, isChecked); } }); final RadioButton readWrite = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readWrite); - readWrite.setChecked(!Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity)); + readWrite.setChecked(!roChecked); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override @@ -683,14 +545,12 @@ public class Apple2DisksMenu implements Apple2MenuView { builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - boolean isDriveA = diskA.isChecked(); + boolean isDriveA = driveA.isChecked(); boolean diskReadOnly = readOnly.isChecked(); if (isDriveA) { - Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(mActivity, diskReadOnly); - Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, imageName); + insertDisk(imageName, /*driveA:*/true, diskReadOnly); } else { - Apple2Preferences.CURRENT_DISK_B_RO.saveBoolean(mActivity, diskReadOnly); - Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, imageName); + insertDisk(imageName, /*driveA:*/false, diskReadOnly); } dialog.dismiss(); mActivity.dismissAllMenus(); diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickCalibration.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickCalibration.java index 5c662dbf..c3fc8fc2 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickCalibration.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickCalibration.java @@ -24,23 +24,26 @@ public class Apple2JoystickCalibration implements Apple2MenuView { private final static String TAG = "Apple2JoystickCalibration"; + private final static int JOYSTICK_DIVIDER_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; + public final static String PREF_SCREEN_DIVISION = "screenDivider"; + private Apple2Activity mActivity = null; private View mSettingsView = null; private ArrayList mViewStack = null; private boolean mTouchMenuEnabled = false; - private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal(); + private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal(); - public Apple2JoystickCalibration(Apple2Activity activity, ArrayList viewStack, Apple2Preferences.TouchDeviceVariant variant) { + public Apple2JoystickCalibration(Apple2Activity activity, ArrayList viewStack, Apple2SettingsMenu.TouchDeviceVariant variant) { mActivity = activity; mViewStack = viewStack; - if (!(variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK || variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD)) { + if (!(variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK || variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD)) { throw new RuntimeException("You're doing it wrong"); } setup(variant); } - private void setup(Apple2Preferences.TouchDeviceVariant variant) { + private void setup(Apple2SettingsMenu.TouchDeviceVariant variant) { LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mSettingsView = inflater.inflate(R.layout.activity_calibrate_joystick, null, false); @@ -51,7 +54,8 @@ public class Apple2JoystickCalibration implements Apple2MenuView { if (!fromUser) { return; } - Apple2Preferences.JOYSTICK_DIVIDER.saveInt(mActivity, progress); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, (float) progress / JOYSTICK_DIVIDER_NUM_CHOICES); + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_JOYSTICK); } @Override @@ -64,20 +68,17 @@ public class Apple2JoystickCalibration implements Apple2MenuView { }); sb.setMax(0); // http://stackoverflow.com/questions/10278467/seekbar-not-setting-actual-progress-setprogress-not-working-on-early-android - sb.setMax(Apple2Preferences.JOYSTICK_DIVIDER_NUM_CHOICES); - sb.setProgress(Apple2Preferences.JOYSTICK_DIVIDER.intValue(mActivity)); + sb.setMax(JOYSTICK_DIVIDER_NUM_CHOICES); + float val = Apple2Preferences.getFloatJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, (JOYSTICK_DIVIDER_NUM_CHOICES >> 1) / (float) JOYSTICK_DIVIDER_NUM_CHOICES); + sb.setProgress((int) (val * JOYSTICK_DIVIDER_NUM_CHOICES)); - mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity); - Apple2Preferences.nativeSetTouchMenuEnabled(false); - mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity); - Apple2Preferences.nativeSetCurrentTouchDevice(variant.ordinal()); - if (variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK) { - Apple2Preferences.loadAllJoystickButtons(mActivity); - } else { - Apple2Preferences.loadAllKeypadKeys(mActivity); - } + mTouchMenuEnabled = (boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED); + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, false); + mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, variant.ordinal()); - Apple2Preferences.nativeTouchDeviceBeginCalibrationMode(); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true); + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); } public final boolean isCalibrating() { @@ -85,7 +86,6 @@ public class Apple2JoystickCalibration implements Apple2MenuView { } public void onKeyTapCalibrationEvent(char ascii, int scancode) { - /* ... */ } public void show() { @@ -102,9 +102,11 @@ public class Apple2JoystickCalibration implements Apple2MenuView { } } - Apple2Preferences.nativeTouchDeviceEndCalibrationMode(); - Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled); - Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false); + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, mTouchMenuEnabled); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice); + + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); mActivity.popApple2View(this); } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java index fe17949f..6e416308 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java @@ -11,6 +11,7 @@ package org.deadc0de.apple2ix; +import android.util.DisplayMetrics; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -24,6 +25,21 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { private final static String TAG = "Apple2JoystickSettingsMenu"; + public final static int JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; + + public final static float JOYSTICK_AXIS_SENSITIVITY_MIN = 0.25f; + public final static float JOYSTICK_AXIS_SENSITIVITY_DEFAULT = 1.f; + public final static float JOYSTICK_AXIS_SENSITIVITY_MAX = 4.f; + public final static float JOYSTICK_AXIS_SENSITIVITY_DEC_STEP = 0.05f; + public final static float JOYSTICK_AXIS_SENSITIVITY_INC_STEP = 0.25f; + public final static int JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_DEFAULT - JOYSTICK_AXIS_SENSITIVITY_MIN) / JOYSTICK_AXIS_SENSITIVITY_DEC_STEP); // 15 + public final static int JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_MAX - JOYSTICK_AXIS_SENSITIVITY_DEFAULT) / JOYSTICK_AXIS_SENSITIVITY_INC_STEP); // 12 + public final static int JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES + JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES; // 15 + 12 + + public final static int TAPDELAY_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; + public final static float TAPDELAY_SCALE = 0.5f; + + public Apple2JoystickSettingsMenu(Apple2Activity activity) { super(activity); } @@ -51,6 +67,18 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return true; } + public enum TouchJoystickButtons { + NONE(0), + BUTTON1(1), + BUTTON2(2), + BOTH(3); + private int butt; + + TouchJoystickButtons(int butt) { + this.butt = butt; + } + } + protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { JOYSTICK_TAP_BUTTON { @Override @@ -63,6 +91,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_button_tap_button_summary); } + @Override + public String getPrefKey() { + return "jsTouchDownChar"; + } + + @Override + public Object getPrefDefault() { + return TouchJoystickButtons.BUTTON1.ordinal(); + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -72,6 +110,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + final IMenuEnum self = this; _alertDialogHandleSelection(activity, R.string.joystick_button_tap_button, new String[]{ activity.getResources().getString(R.string.joystick_button_button_none), activity.getResources().getString(R.string.joystick_button_button1), @@ -80,12 +119,12 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { }, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.JOYSTICK_TAP_BUTTON.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.JOYSTICK_TAP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]); + Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal()); } }); } @@ -101,6 +140,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_button_swipe_up_button_summary); } + @Override + public String getPrefKey() { + return "jsSwipeNorthChar"; + } + + @Override + public Object getPrefDefault() { + return TouchJoystickButtons.BOTH.ordinal(); + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -110,6 +159,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + final IMenuEnum self = this; _alertDialogHandleSelection(activity, R.string.joystick_button_swipe_up_button, new String[]{ activity.getResources().getString(R.string.joystick_button_button_none), activity.getResources().getString(R.string.joystick_button_button1), @@ -118,12 +168,12 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { }, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]); + Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal()); } }); } @@ -139,6 +189,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_button_swipe_down_button_summary); } + @Override + public String getPrefKey() { + return "jsSwipeSouthChar"; + } + + @Override + public Object getPrefDefault() { + return TouchJoystickButtons.BUTTON2.ordinal(); + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -148,6 +208,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + final IMenuEnum self = this; _alertDialogHandleSelection(activity, R.string.joystick_button_swipe_down_button, new String[]{ activity.getResources().getString(R.string.joystick_button_button_none), activity.getResources().getString(R.string.joystick_button_button1), @@ -156,12 +217,12 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { }, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]); + Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal()); } }); } @@ -192,7 +253,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { } } - Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK); + Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK); // show this new view... calibration.show(); @@ -214,22 +275,33 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_button_tapdelay_summary); } + @Override + public String getPrefKey() { + return "jsTapDelaySecs"; + } + + @Override + public Object getPrefDefault() { + return ((float) 8 / TAPDELAY_NUM_CHOICES * TAPDELAY_SCALE); // -> 0.2f + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.TAPDELAY_NUM_CHOICES, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, TAPDELAY_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.JOYSTICK_TAPDELAY.saveInt(activity, progress); + Apple2Preferences.setJSONPref(self, ((float) progress / TAPDELAY_NUM_CHOICES * TAPDELAY_SCALE)); } @Override public int intValue() { - return Apple2Preferences.JOYSTICK_TAPDELAY.intValue(activity); + return (int) (Apple2Preferences.getFloatJSONPref(self) / TAPDELAY_SCALE * TAPDELAY_NUM_CHOICES); } @Override public void showValue(int progress, final TextView seekBarValue) { - seekBarValue.setText("" + (((float) progress / Apple2Preferences.TAPDELAY_NUM_CHOICES) * Apple2Preferences.TAPDELAY_SCALE)); + seekBarValue.setText("" + (((float) progress / TAPDELAY_NUM_CHOICES) * TAPDELAY_SCALE)); } }); } @@ -253,9 +325,23 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_JOYSTICK; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override @@ -316,14 +402,25 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_visible_summary); } + @Override + public String getPrefKey() { + return "showControls"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_VISIBILITY.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.JOYSTICK_VISIBILITY.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -340,14 +437,25 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_azimuth_visible_summary); } + @Override + public String getPrefKey() { + return "showAzimuth"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_AZIMUTH_VISIBILITY.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.JOYSTICK_AZIMUTH_VISIBILITY.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -364,20 +472,31 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_axisleft_summary); } + @Override + public String getPrefKey() { + return "axisIsOnLeft"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; } }, - JOYSTICK_AXIS_SENSITIVIY { + JOYSTICK_AXIS_SENSITIVITY { @Override public final String getTitle(Apple2Activity activity) { return ""; @@ -388,23 +507,51 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_axis_sensitivity_summary); } + @Override + public String getPrefKey() { + return "axisSensitivity"; + } + + @Override + public Object getPrefDefault() { + return 1.f; + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.saveInt(activity, progress); + final int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES; + float sensitivity = 1.f; + if (progress < pivot) { + int decAmount = (pivot - progress); + sensitivity -= (JOYSTICK_AXIS_SENSITIVITY_DEC_STEP * decAmount); + } else if (progress > pivot) { + int incAmount = (progress - pivot); + sensitivity += (JOYSTICK_AXIS_SENSITIVITY_INC_STEP * incAmount); + } + Apple2Preferences.setJSONPref(self, sensitivity); } @Override public int intValue() { - return Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.intValue(activity); + float sensitivity = Apple2Preferences.getFloatJSONPref(self); + int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES; + if (sensitivity < 1.f) { + pivot = Math.round((sensitivity - JOYSTICK_AXIS_SENSITIVITY_MIN) / JOYSTICK_AXIS_SENSITIVITY_DEC_STEP); + } else if (sensitivity > 1.f) { + sensitivity -= 1.f; + pivot += Math.round(sensitivity / JOYSTICK_AXIS_SENSITIVITY_INC_STEP); + } + return pivot; } @Override public void showValue(int progress, final TextView seekBarValue) { saveInt(progress); - int percent = (int) (Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.floatValue(activity) * 100.f); + int percent = (int) (Apple2Preferences.getFloatJSONPref(self) * 100.f); seekBarValue.setText("" + percent + "%"); } }); @@ -421,25 +568,38 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.joystick_button_threshold_summary); } + @Override + public String getPrefKey() { + return "switchThreshold"; + } + + @Override + public Object getPrefDefault() { + return 128; + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { if (progress == 0) { progress = 1; } - Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.saveInt(activity, progress); + progress *= getJoystickButtonSwitchThresholdScale(activity); + Apple2Preferences.setJSONPref(self, progress); } @Override public int intValue() { - return Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.intValue(activity); + int progress = (int) Apple2Preferences.getJSONPref(self); + return (progress / getJoystickButtonSwitchThresholdScale(activity)); } @Override public void showValue(int progress, final TextView seekBarValue) { - int threshold = progress * Apple2Preferences.getJoystickButtonSwitchThresholdScale(activity); + int threshold = progress * getJoystickButtonSwitchThresholdScale(activity); seekBarValue.setText("" + threshold + " pts"); } }); @@ -448,9 +608,13 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_JOYSTICK; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override @@ -468,4 +632,17 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { } } } + + public static int getJoystickButtonSwitchThresholdScale(Apple2Activity activity) { + + DisplayMetrics dm = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(dm); + + int smallScreenAxis = dm.widthPixels < dm.heightPixels ? dm.widthPixels : dm.heightPixels; + int oneThirdScreenAxis = smallScreenAxis / 3; + + // largest switch threshold value is 1/3 small dimension of screen + return oneThirdScreenAxis / JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES; + } + } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java index 1a329767..15ff0ebf 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java @@ -11,6 +11,7 @@ package org.deadc0de.apple2ix; +import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.widget.CheckBox; @@ -106,14 +107,30 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.touch_menu_enable_summary); } + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_KEYBOARD; + } + + @Override + public String getPrefKey() { + return "touchMenuEnabled"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.TOUCH_MENU_ENABLED.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -130,17 +147,28 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_visibility_inactive_summary); } + @Override + public String getPrefKey() { + return "minAlpha"; + } + + @Override + public Object getPrefDefault() { + return (float) 5 / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.saveInt(activity, progress); + Apple2Preferences.setJSONPref(self, progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES); } @Override public int intValue() { - return Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.intValue(activity); + return Math.round(Apple2Preferences.getFloatJSONPref(self) * (Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES)); } @Override @@ -161,17 +189,28 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_visibility_active_summary); } + @Override + public String getPrefKey() { + return "maxAlpha"; + } + + @Override + public Object getPrefDefault() { + return 1.f; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.saveInt(activity, progress); + Apple2Preferences.setJSONPref(self, (float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES); } @Override public int intValue() { - return Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.intValue(activity); + return Math.round(Apple2Preferences.getFloatJSONPref(self) * (Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES)); } @Override @@ -192,14 +231,25 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_click_enabled_summary); } + @Override + public String getPrefKey() { + return "keyClickEnabled"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); + final IMenuEnum self = this; cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.KEYBOARD_CLICK_ENABLED.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -216,14 +266,25 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_lowercase_enabled_summary); } + @Override + public String getPrefKey() { + return "lowercaseEnabled"; + } + + @Override + public Object getPrefDefault() { + return false; + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); + final IMenuEnum self = this; cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -240,6 +301,16 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_choose_alt_summary); } + @Override + public String getPrefKey() { + return "altPathIndex"; + } + + @Override + public Object getPrefDefault() { + return 0; + } + @Override public final View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -250,7 +321,7 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { - File extKeyboardDir = Apple2DisksMenu.getExternalStorageDirectory(activity); + File extKeyboardDir = Apple2Utils.getExternalStorageDirectory(activity); FilenameFilter kbdJsonFilter = new FilenameFilter() { public boolean accept(File dir, String name) { @@ -279,7 +350,7 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { } if (files == null) { // read keyboard data from /data/data/... - File keyboardDir = new File(Apple2DisksMenu.getDataDir(activity) + File.separator + "keyboards"); + File keyboardDir = new File(Apple2Utils.getDataDir(activity) + File.separator + "keyboards"); files = keyboardDir.listFiles(kbdJsonFilter); if (files == null) { Log.e(TAG, "OOPS, could not read keyboard data directory"); @@ -299,17 +370,18 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { final String keyboardDirName = extKeyboardDir == null ? "Keyboards" : extKeyboardDir.getPath(); + final IMenuEnum self = this; _alertDialogHandleSelection(activity, keyboardDirName, titles, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.KEYBOARD_ALT.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.KEYBOARD_ALT.saveInt(activity, value); + Apple2Preferences.setJSONPref(self, value); String path = allFiles[value].getPath(); - Apple2Preferences.KEYBOARD_ALT_PATH.saveString(activity, path); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_KEYBOARD, "altPath", path); } }); } @@ -325,15 +397,29 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keyboard_glyph_scale_summary); } + @Override + public String getPrefKey() { + return "glyphMultiplier"; + } + + @Override + public Object getPrefDefault() { + return 2; + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); - int glyphScale = Apple2Preferences.KEYBOARD_GLYPH_SCALE.intValue(activity); + int glyphScale = (int) Apple2Preferences.getJSONPref(this); + if (glyphScale <= 0) { + glyphScale = 1; + } CheckBox cb = _addCheckbox(activity, this, convertView, glyphScale > 1); + final IMenuEnum self = this; cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.KEYBOARD_GLYPH_SCALE.saveInt(activity, isChecked ? 2 : 1); + Apple2Preferences.setJSONPref(self, isChecked ? 2 : 1); } }); return convertView; @@ -342,9 +428,23 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_KEYBOARD; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java index 18cc0552..42cd5d53 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java @@ -23,6 +23,8 @@ import android.widget.TextView; import java.util.ArrayList; import org.deadc0de.apple2ix.basic.R; +import org.json.JSONArray; +import org.json.JSONException; public class Apple2KeypadChooser implements Apple2MenuView { @@ -33,12 +35,10 @@ public class Apple2KeypadChooser implements Apple2MenuView { private ArrayList mViewStack = null; private TextView mCurrentChoicePrompt = null; - private String[] foo = null; - private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_NORTHWEST; private boolean mTouchMenuEnabled = false; - private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal(); + private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal(); public Apple2KeypadChooser(Apple2Activity activity, ArrayList viewStack) { mActivity = activity; @@ -60,8 +60,10 @@ public class Apple2KeypadChooser implements Apple2MenuView { String asciiStr = asciiRepresentation(ascii); Log.d(TAG, "ascii:'" + asciiStr + "' scancode:" + scancode); - mChooserState.setValues(mActivity, ascii, scancode); - Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal()); + mChooserState.setKey(mActivity, ascii, scancode); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal()); + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); + mCurrentChoicePrompt.setText(getNextChoiceString() + asciiStr); switch (mChooserState) { case CHOOSE_TAP: @@ -88,7 +90,8 @@ public class Apple2KeypadChooser implements Apple2MenuView { public void run() { mChooserState = mChooserState.next(); mCurrentChoicePrompt.setText(getNextChoiceString()); - Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal()); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal()); + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); } }, 1000); } @@ -101,15 +104,19 @@ public class Apple2KeypadChooser implements Apple2MenuView { } public void dismiss() { + for (Apple2MenuView apple2MenuView : mViewStack) { if (apple2MenuView != this) { mActivity.pushApple2View(apple2MenuView); } } - Apple2Preferences.nativeTouchDeviceEndCalibrationMode(); - Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled); - Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false); + + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, mTouchMenuEnabled); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice); + + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); mActivity.popApple2View(this); } @@ -130,6 +137,10 @@ public class Apple2KeypadChooser implements Apple2MenuView { // internals private void setup() { + JSONArray jsonChars = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, Apple2KeypadSettingsMenu.PREF_KPAD_ROSETTE_CHAR_ARRAY, null); + JSONArray jsonScans = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, Apple2KeypadSettingsMenu.PREF_KPAD_ROSETTE_SCAN_ARRAY, null); + mChooserState.start(jsonChars, jsonScans); + LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mSettingsView = inflater.inflate(R.layout.activity_chooser_keypad, null, false); @@ -140,17 +151,18 @@ public class Apple2KeypadChooser implements Apple2MenuView { skipButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Apple2KeypadChooser.this.onKeyTapCalibrationEvent((char)Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + Apple2KeypadChooser.this.onKeyTapCalibrationEvent((char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); } }); // temporarily undo these native touch settings while calibrating... - mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity); - Apple2Preferences.nativeSetTouchMenuEnabled(false); - mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity); - Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal()); + mTouchMenuEnabled = (boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED); + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, false); + mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal()); - Apple2Preferences.nativeTouchDeviceBeginCalibrationMode(); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true); + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); } private String asciiRepresentation(char ascii) { @@ -194,140 +206,116 @@ public class Apple2KeypadChooser implements Apple2MenuView { public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_ul); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_NORTH { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_up); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_NORTHEAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_ur); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_WEST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_l); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_CENTER { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_c); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_EAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_r); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_SOUTHWEST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dl); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_SOUTH { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dn); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_SOUTHEAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dr); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_TAP { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_button_tap); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_SWIPEUP { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_button_swipeup); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, ascii, scancode); - } }, CHOOSE_SWIPEDOWN { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_button_swipedown); } - - @Override - public void setValues(Apple2Activity activity, char ascii, int scancode) { - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, ascii, scancode); - } }; public static final int size = STATE_MACHINE.values().length; - public abstract void setValues(Apple2Activity activity, char ascii, int scancode); + private static ArrayList chars = new ArrayList(); + private static ArrayList scans = new ArrayList(); + + public void setKey(Apple2Activity activity, int ascii, int scancode) { + int ord = ordinal(); + if (ord < CHOOSE_TAP.ordinal()) { + chars.set(ord, "" + ascii); + scans.set(ord, "" + scancode); + Apple2KeypadSettingsMenu.KeypadPreset.saveRosettes(chars, scans); + } else if (ord == CHOOSE_TAP.ordinal()) { + Apple2KeypadSettingsMenu.KeypadPreset.saveTouchDownKey(ascii, scancode); + } else if (ord == CHOOSE_SWIPEUP.ordinal()) { + Apple2KeypadSettingsMenu.KeypadPreset.saveSwipeNorthKey(ascii, scancode); + } else if (ord == CHOOSE_SWIPEDOWN.ordinal()) { + Apple2KeypadSettingsMenu.KeypadPreset.saveSwipeSouthKey(ascii, scancode); + } else { + throw new RuntimeException(); + } + Apple2Preferences.sync(activity, Apple2Preferences.PREF_DOMAIN_JOYSTICK); + } public abstract String getKeyName(Apple2Activity activity); + public void start(JSONArray jsonChars, JSONArray jsonScans) { + int len = jsonChars.length(); + if (len != size) { + throw new RuntimeException("jsonChars not expected length"); + } + if (len != jsonScans.length()) { + throw new RuntimeException("jsonScans not expected length"); + } + try { + for (int i = 0; i < len; i++) { + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, jsonChars.getInt(i), jsonScans.getInt(i)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + public STATE_MACHINE next() { int nextOrd = this.ordinal() + 1; if (nextOrd >= size) { diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java index 01ba3c54..4a3e981e 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java @@ -17,11 +17,23 @@ import android.widget.TextView; import java.util.ArrayList; import org.deadc0de.apple2ix.basic.R; +import org.json.JSONArray; public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { private final static String TAG = "Apple2KeypadSettingsMenu"; + private final static int KEYREPEAT_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; + + public final static String PREF_KPAD_ROSETTE_CHAR_ARRAY = "kpAxisRosetteChars"; + public final static String PREF_KPAD_ROSETTE_SCAN_ARRAY = "kpAxisRosetteScancodes"; + public final static String PREF_KPAD_SWIPE_NORTH_CHAR = "kpSwipeNorthChar"; + public final static String PREF_KPAD_SWIPE_NORTH_SCAN = "kpSwipeNorthScancode"; + public final static String PREF_KPAD_SWIPE_SOUTH_CHAR = "kpSwipeSouthChar"; + public final static String PREF_KPAD_SWIPE_SOUTH_SCAN = "kpSwipeSouthScancode"; + public final static String PREF_KPAD_TOUCHDOWN_CHAR = "kpTouchDownChar"; + public final static String PREF_KPAD_TOUCHDOWN_SCAN = "kpTouchDownScancode"; + public Apple2KeypadSettingsMenu(Apple2Activity activity) { super(activity); } @@ -49,6 +61,212 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { return true; } + public enum KeypadPreset { + ARROWS_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_arrows_space); + } + + @Override + public void apply(Apple2Activity activity) { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_UP, Apple2KeyboardSettingsMenu.SCANCODE_UP); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN, Apple2KeyboardSettingsMenu.SCANCODE_DOWN); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveRosettes(chars, scans); + + saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + } + }, + AZ_LEFT_RIGHT_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_az_left_right_space); + } + + @Override + public void apply(Apple2Activity activity) { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveRosettes(chars, scans); + + saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + } + }, + LEFT_RIGHT_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_left_right_space); + } + + @Override + public void apply(Apple2Activity activity) { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveRosettes(chars, scans); + + saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + } + }, + IJKM_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_ijkm_space); + } + + @Override + public void apply(Apple2Activity activity) { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveRosettes(chars, scans); + + saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + } + }, + WADX_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_wadx_space); + } + + @Override + public void apply(Apple2Activity activity) { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'W', Apple2KeyboardSettingsMenu.SCANCODE_W); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'X', Apple2KeyboardSettingsMenu.SCANCODE_X); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveRosettes(chars, scans); + + saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + } + }, + CRAZY_SEAFOX_KEYS { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_crazy_seafox); + } + + @Override + public void apply(Apple2Activity activity) { + // Heh, the entire purpose of the keypad-variant touch joystick is to make this possible ;-) + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, 'Y', Apple2KeyboardSettingsMenu.SCANCODE_Y); + addRosetteKey(chars, scans, 'U', Apple2KeyboardSettingsMenu.SCANCODE_U); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + addRosetteKey(chars, scans, 'H', Apple2KeyboardSettingsMenu.SCANCODE_H); + addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + addRosetteKey(chars, scans, 'N', Apple2KeyboardSettingsMenu.SCANCODE_N); + addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); + addRosetteKey(chars, scans, ',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA); + saveRosettes(chars, scans); + + saveTouchDownKey('D', Apple2KeyboardSettingsMenu.SCANCODE_D); + saveSwipeSouthKey('F', Apple2KeyboardSettingsMenu.SCANCODE_F); + saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + } + }; + + public static void addRosetteKey(ArrayList chars, ArrayList scans, int aChar, int aScan) { + chars.add("" + aChar); + scans.add("" + aScan); + } + + public static void saveRosettes(ArrayList chars, ArrayList scans) { + if (chars.size() != 9) { + throw new RuntimeException("rosette chars is not correct size"); + } + if (scans.size() != 9) { + throw new RuntimeException("rosette scans is not correct size"); + } + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_ROSETTE_CHAR_ARRAY, new JSONArray(chars)); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_ROSETTE_SCAN_ARRAY, new JSONArray(scans)); + } + + public static void saveTouchDownKey(int aChar, int aScan) { + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_CHAR, aChar); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_SCAN, aScan); + } + + public static void saveSwipeSouthKey(int aChar, int aScan) { + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_SOUTH_CHAR, aChar); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_SOUTH_SCAN, aScan); + } + + public static void saveSwipeNorthKey(int aChar, int aScan) { + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_NORTH_CHAR, aChar); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_NORTH_SCAN, aScan); + } + + public abstract String getTitle(Apple2Activity activity); + + public abstract void apply(Apple2Activity activity); + + public static final int size = KeypadPreset.values().length; + + public static String[] titles(Apple2Activity activity) { + String[] titles = new String[size]; + int i = 0; + for (KeypadPreset preset : values()) { + titles[i++] = preset.getTitle(activity); + } + return titles; + } + } + enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { KEYPAD_CHOOSE_KEYS { @Override @@ -61,6 +279,16 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keypad_choose_summary); } + @Override + public String getPrefKey() { + return "kpPresetChoice"; + } + + @Override + public Object getPrefDefault() { + return KeypadPreset.IJKM_SPACE.ordinal() + 1; + } + @Override public final View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -70,24 +298,25 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { - String[] titles = new String[Apple2Preferences.KeypadPreset.size + 1]; + final IMenuEnum self = this; + String[] titles = new String[KeypadPreset.size + 1]; titles[0] = activity.getResources().getString(R.string.keypad_preset_custom); - System.arraycopy(Apple2Preferences.KeypadPreset.titles(activity), 0, titles, 1, Apple2Preferences.KeypadPreset.size); + System.arraycopy(KeypadPreset.titles(activity), 0, titles, 1, KeypadPreset.size); _alertDialogHandleSelection(activity, R.string.keypad_choose_title, titles, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.KEYPAD_KEYS.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.KEYPAD_KEYS.saveInt(activity, value); + Apple2Preferences.setJSONPref(self, value); if (value == 0) { Apple2KeypadSettingsMenu keypadSettingsMenu = (Apple2KeypadSettingsMenu) settingsMenu; keypadSettingsMenu.chooseKeys(activity); } else { - Apple2Preferences.KeypadPreset.values()[value - 1].apply(activity); + KeypadPreset.values()[value - 1].apply(activity); } } }); @@ -119,7 +348,7 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { } } - Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD); + Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD); // show this new view... calibration.show(); @@ -149,9 +378,23 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_JOYSTICK; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override @@ -197,7 +440,7 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { } } - protected static class KeypadAdvanced extends Apple2AbstractMenu { + public static class KeypadAdvanced extends Apple2AbstractMenu { private final static String TAG = "KeypadAdvanced"; @@ -240,22 +483,33 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.keypad_repeat_summary); } + @Override + public String getPrefKey() { + return "keyRepeatThresholdSecs"; + } + + @Override + public Object getPrefDefault() { + return (float) 4 / KEYREPEAT_NUM_CHOICES; + } + @Override public View getView(final Apple2Activity activity, View convertView) { - return _sliderView(activity, this, Apple2Preferences.KEYREPEAT_NUM_CHOICES, new IPreferenceSlider() { + final IMenuEnum self = this; + return _sliderView(activity, this, KEYREPEAT_NUM_CHOICES, new IPreferenceSlider() { @Override public void saveInt(int progress) { - Apple2Preferences.KEYREPEAT_THRESHOLD.saveInt(activity, progress); + Apple2Preferences.setJSONPref(self, (float) progress / KEYREPEAT_NUM_CHOICES); } @Override public int intValue() { - return Apple2Preferences.KEYREPEAT_THRESHOLD.intValue(activity); + return (int) (Apple2Preferences.getFloatJSONPref(self) * KEYREPEAT_NUM_CHOICES); } @Override public void showValue(int progress, final TextView seekBarValue) { - seekBarValue.setText("" + ((float) progress / Apple2Preferences.KEYREPEAT_NUM_CHOICES)); + seekBarValue.setText("" + ((float) progress / KEYREPEAT_NUM_CHOICES)); } }); } @@ -279,9 +533,23 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_JOYSTICK; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java index 40852d05..5529ad7b 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java @@ -100,7 +100,7 @@ public class Apple2MainMenu { @Override public void handleSelection(Apple2MainMenu mainMenu) { if (!mainMenu.mShowingSaveRestore.compareAndSet(false, true)) { - Log.v(TAG, "OMG, avoiding nasty UI race around save/restore"); + Log.v(TAG, "OMG, avoiding nasty UI race around sync/restore"); return; } mainMenu.maybeSaveRestore(); @@ -278,7 +278,7 @@ public class Apple2MainMenu { public void maybeSaveRestore() { mActivity.pauseEmulation(); - final String quickSavePath = Apple2DisksMenu.getDataDir(mActivity) + File.separator + SAVE_FILE; + final String quickSavePath = Apple2Utils.getDataDir(mActivity) + File.separator + SAVE_FILE; final AtomicBoolean selectionAlreadyHandled = new AtomicBoolean(false); @@ -286,7 +286,7 @@ public class Apple2MainMenu { @Override public void onClick(DialogInterface dialog, int which) { if (!selectionAlreadyHandled.compareAndSet(false, true)) { - Log.v(TAG, "OMG, avoiding nasty UI race in save/restore onClick()"); + Log.v(TAG, "OMG, avoiding nasty UI race in sync/restore onClick()"); return; } mActivity.saveState(quickSavePath); @@ -296,7 +296,7 @@ public class Apple2MainMenu { @Override public void onClick(DialogInterface dialog, int which) { if (!selectionAlreadyHandled.compareAndSet(false, true)) { - Log.v(TAG, "OMG, avoiding nasty UI race in save/restore onClick()"); + Log.v(TAG, "OMG, avoiding nasty UI race in sync/restore onClick()"); return; } @@ -305,13 +305,13 @@ public class Apple2MainMenu { JSONObject map = new JSONObject(jsonData); String diskPath1 = map.getString("disk1"); boolean readOnly1 = map.getBoolean("readOnly1"); - Apple2Preferences.CURRENT_DISK_A.setPath(mActivity, diskPath1); - Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(mActivity, readOnly1); + Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A, diskPath1); + Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO, readOnly1); String diskPath2 = map.getString("disk2"); boolean readOnly2 = map.getBoolean("readOnly2"); - Apple2Preferences.CURRENT_DISK_B.setPath(mActivity, diskPath2); - Apple2Preferences.CURRENT_DISK_B_RO.saveBoolean(mActivity, readOnly2); + Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B, diskPath2); + Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO, readOnly2); } catch (JSONException je) { Log.v(TAG, "OOPS : " + je); } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2PortraitCalibration.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2PortraitCalibration.java index ca114174..95a4ee33 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2PortraitCalibration.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2PortraitCalibration.java @@ -24,32 +24,88 @@ import java.util.ArrayList; public class Apple2PortraitCalibration implements Apple2MenuView { - public enum States { - CALIBRATE_KEYBOARD_HEIGHT_SCALE(0), - CALIBRATE_FRAMEBUFFER_POSITION_SCALE(1), - CALIBRATE_KEYBOARD_POSITION_SCALE(2); - private int val; + public enum States implements Apple2AbstractMenu.IMenuEnum { + CALIBRATE_KEYBOARD_HEIGHT_SCALE { + @Override + public String getPrefKey() { + return "portraitHeightScale"; + } + + @Override + public Object getPrefDefault() { + return (float) (PORTRAIT_CALIBRATE_NUM_CHOICES >> 1) / PORTRAIT_CALIBRATE_NUM_CHOICES; + } + }, + CALIBRATE_FRAMEBUFFER_POSITION_SCALE { + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_VIDEO; + } + + @Override + public String getPrefKey() { + return "portraitPositionScale"; + } + + @Override + public Object getPrefDefault() { + return 3.f / 4; + } + }, + CALIBRATE_KEYBOARD_POSITION_SCALE { + @Override + public String getPrefKey() { + return "portraitPositionScale"; + } + + @Override + public Object getPrefDefault() { + return 0.f; + } + }; public static final int size = States.values().length; - States(int val) { - this.val = val; + States next() { + int ord = (ordinal() + 1) % size; + return States.values()[ord]; } - States next() { - int ord = (val + 1) % size; - return States.values()[ord]; + @Override + public final String getTitle(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public final String getSummary(Apple2Activity activity) { + throw new RuntimeException(); + } + + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_KEYBOARD; + } + + @Override + public View getView(final Apple2Activity activity, View convertView) { + throw new RuntimeException(); + } + + @Override + public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + throw new RuntimeException(); } } private final static String TAG = "Apple2PortraitCalibration"; + public final static int PORTRAIT_CALIBRATE_NUM_CHOICES = 100; private Apple2Activity mActivity = null; private View mSettingsView = null; private ArrayList mViewStack = null; private boolean mTouchMenuEnabled = false; - private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal(); + private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal(); private States mStateMachine = States.CALIBRATE_KEYBOARD_HEIGHT_SCALE; public Apple2PortraitCalibration(Apple2Activity activity, ArrayList viewStack) { @@ -62,7 +118,8 @@ public class Apple2PortraitCalibration implements Apple2MenuView { final Button calibrateButton = (Button) mSettingsView.findViewById(R.id.button_calibrate); final VerticalSeekBar vsb = (VerticalSeekBar) mSettingsView.findViewById(R.id.seekbar_vertical); - vsb.setProgress(Apple2Preferences.PORTRAIT_KEYBOARD_HEIGHT_SCALE.intValue(mActivity)); + final int firstProgress = Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES); + vsb.setProgress(firstProgress); calibrateButton.setOnClickListener(new View.OnClickListener() { @Override @@ -71,16 +128,16 @@ public class Apple2PortraitCalibration implements Apple2MenuView { switch (mStateMachine) { case CALIBRATE_KEYBOARD_HEIGHT_SCALE: calibrateButton.setText(R.string.portrait_calibrate_keyboard_height); - vsb.setProgress(Apple2Preferences.PORTRAIT_KEYBOARD_HEIGHT_SCALE.intValue(mActivity)); + vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES)); break; case CALIBRATE_FRAMEBUFFER_POSITION_SCALE: calibrateButton.setText(R.string.portrait_calibrate_framebuffer); - vsb.setProgress(Apple2Preferences.PORTRAIT_FRAMEBUFFER_POSITION_SCALE.intValue(mActivity)); + vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_FRAMEBUFFER_POSITION_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES)); break; case CALIBRATE_KEYBOARD_POSITION_SCALE: default: calibrateButton.setText(R.string.portrait_calibrate_keyboard_position); - vsb.setProgress(Apple2Preferences.PORTRAIT_KEYBOARD_POSITION_SCALE.intValue(mActivity)); + vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_POSITION_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES)); break; } } @@ -91,16 +148,17 @@ public class Apple2PortraitCalibration implements Apple2MenuView { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { switch (mStateMachine) { case CALIBRATE_KEYBOARD_HEIGHT_SCALE: - Apple2Preferences.PORTRAIT_KEYBOARD_HEIGHT_SCALE.saveInt(mActivity, progress); + Apple2Preferences.setJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES); break; case CALIBRATE_FRAMEBUFFER_POSITION_SCALE: - Apple2Preferences.PORTRAIT_FRAMEBUFFER_POSITION_SCALE.saveInt(mActivity, progress); + Apple2Preferences.setJSONPref(States.CALIBRATE_FRAMEBUFFER_POSITION_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES); break; case CALIBRATE_KEYBOARD_POSITION_SCALE: default: - Apple2Preferences.PORTRAIT_KEYBOARD_POSITION_SCALE.saveInt(mActivity, progress); + Apple2Preferences.setJSONPref(States.CALIBRATE_KEYBOARD_POSITION_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES); break; } + Apple2Preferences.sync(mActivity, mStateMachine.getPrefDomain()); } @Override @@ -112,11 +170,14 @@ public class Apple2PortraitCalibration implements Apple2MenuView { } }); - mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity); - Apple2Preferences.nativeSetTouchMenuEnabled(false); - mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity); - Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal()); - Apple2Preferences.nativeTouchDeviceBeginCalibrationMode(); + mTouchMenuEnabled = (boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED); + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, false); + mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal()); + + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true); + + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); } public final boolean isCalibrating() { @@ -124,7 +185,6 @@ public class Apple2PortraitCalibration implements Apple2MenuView { } public void onKeyTapCalibrationEvent(char ascii, int scancode) { - /* ... */ } public void show() { @@ -141,9 +201,12 @@ public class Apple2PortraitCalibration implements Apple2MenuView { } } - Apple2Preferences.nativeTouchDeviceEndCalibrationMode(); - Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled); - Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false); + + Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, mTouchMenuEnabled); + Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice); + + Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); mActivity.popApple2View(this); } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java index a56c4514..a2224faa 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java @@ -11,899 +11,166 @@ package org.deadc0de.apple2ix; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.ActivityInfo; -import android.util.DisplayMetrics; import android.util.Log; -import java.io.File; - -import org.deadc0de.apple2ix.basic.R; +import org.deadc0de.apple2ix.basic.BuildConfig; import org.json.JSONException; import org.json.JSONObject; -public enum Apple2Preferences { - EMULATOR_VERSION { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; - @Override - public void saveInt(Apple2Activity activity, int version) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), version).apply(); - } - }, - CURRENT_DISK_PATH { - @Override - public void load(final Apple2Activity activity) { - activity.getDisksMenu().setPathStackJSON(stringValue(activity)); - } +public class Apple2Preferences { - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), "[]"); - } + public final static String TAG = "Apple2Preferences"; - @Override - public void saveString(Apple2Activity activity, String value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), value).apply(); - //load(activity); - } - }, - CURRENT_DRIVE_A_BUTTON { - @Override - public void load(final Apple2Activity activity) { - /* ... */ - } + public final static String PREFS_JSON_FILE = ".apple2.json"; + public final static int DECENT_AMOUNT_OF_CHOICES = 20; + public final static int ALPHA_SLIDER_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } + // ------------------------------------------------------------------------ + // preference domains - @Override - public void saveBoolean(Apple2Activity activity, boolean value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), value).apply(); - //load(activity); - } - }, - CURRENT_DISK_RO_BUTTON { - @Override - public void load(final Apple2Activity activity) { - /* ... */ - } + public final static String PREF_DOMAIN_AUDIO = "audio"; + public final static String PREF_DOMAIN_INTERFACE = "interface"; + public final static String PREF_DOMAIN_JOYSTICK = "joystick"; + public final static String PREF_DOMAIN_KEYBOARD = "keyboard"; + public final static String PREF_DOMAIN_TOUCHSCREEN = "touchscreen"; + public final static String PREF_DOMAIN_VIDEO = "video"; + public final static String PREF_DOMAIN_VM = "vm"; - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } + public final static String PREF_CALIBRATING = "isCalibrating"; + public final static String PREF_DEVICE_HEIGHT = "deviceHeight"; + public final static String PREF_DEVICE_WIDTH = "deviceWidth"; + public final static String PREF_EMULATOR_VERSION = "emulatorVersion"; - @Override - public void saveBoolean(Apple2Activity activity, boolean value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), value).apply(); - //load(activity); - } - }, - CURRENT_DISK_A { - @Override - public void load(final Apple2Activity activity) { - insertDisk(activity, stringValue(activity), /*driveA:*/true, /*readOnly:*/CURRENT_DISK_A_RO.booleanValue(activity)); - } + // JSON preferences + private static JSONObject sSettings = null; + private static AtomicBoolean sNativeIsDirty = new AtomicBoolean(true); - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } + // ------------------------------------------------------------------------ - @Override - public void saveString(Apple2Activity activity, String str) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), str).apply(); - load(activity); - } - - @Override - public void setPath(Apple2Activity activity, String str) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), str).apply(); - } - }, - CURRENT_DISK_A_RO { - @Override - public void load(final Apple2Activity activity) { - /* ... */ - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - - @Override - public void saveBoolean(Apple2Activity activity, boolean value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), value).apply(); - //load(activity); - } - }, - CURRENT_DISK_B { - @Override - public void load(final Apple2Activity activity) { - insertDisk(activity, stringValue(activity), /*driveA:*/false, /*readOnly:*/CURRENT_DISK_B_RO.booleanValue(activity)); - } - - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } - - @Override - public void saveString(Apple2Activity activity, String str) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), str).apply(); - load(activity); - } - - @Override - public void setPath(Apple2Activity activity, String str) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), str).apply(); - } - }, - CURRENT_DISK_B_RO { - @Override - public void load(final Apple2Activity activity) { - /* ... */ - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - - @Override - public void saveBoolean(Apple2Activity activity, boolean value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), value).apply(); - //load(activity); - } - }, - CPU_SPEED_PERCENT { - @Override - public void load(Apple2Activity activity) { - nativeSetCPUSpeed(intValue(activity)); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 100); - } - }, - HIRES_COLOR { - @Override - public void load(Apple2Activity activity) { - nativeSetColor(intValue(activity)); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), HiresColor.INTERPOLATED.ordinal()); - } - }, - SPEAKER_VOLUME { - @Override - public void load(Apple2Activity activity) { - nativeSetSpeakerVolume(intValue(activity)); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), Volume.MEDIUM.ordinal()); - } - }, - LANDSCAPE_MODE { - @Override - public void load(Apple2Activity activity) { - if (booleanValue(activity)) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); - } else { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - MOCKINGBOARD_ENABLED { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - boolean result = nativeSetMockingboardEnabled(enabled); - if (enabled && !result) { - warnError(activity, R.string.mockingboard_disabled_title, R.string.mockingboard_disabled_mesg); - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), false).apply(); - } - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - MOCKINGBOARD_VOLUME { - @Override - public void load(Apple2Activity activity) { - nativeSetMockingboardVolume(intValue(activity)); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), Volume.MEDIUM.ordinal()); - } - }, - AUDIO_LATENCY { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - nativeSetAudioLatency(((float) tick / AUDIO_LATENCY_NUM_CHOICES)); - } - - @Override - public int intValue(Apple2Activity activity) { - - int defaultLatency = 0; - int sampleRateCanary = DevicePropertyCalculator.getRecommendedSampleRate(activity); - if (sampleRateCanary == DevicePropertyCalculator.defaultSampleRate) { - // quite possibly an audio-challenged device - defaultLatency = 8; // /AUDIO_LATENCY_NUM_CHOICES -> 0.4f - } else { - // reasonable default for high-end devices - defaultLatency = 5; // /AUDIO_LATENCY_NUM_CHOICES -> 0.25f - } - - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), defaultLatency); - } - }, - CURRENT_TOUCH_DEVICE { - @Override - public void load(Apple2Activity activity) { - int intVariant = intValue(activity); - nativeSetCurrentTouchDevice(intVariant); - TouchDeviceVariant variant = TouchDeviceVariant.values()[intVariant]; - switch (variant) { - case JOYSTICK: - loadAllJoystickButtons(activity); - break; - case JOYSTICK_KEYPAD: - loadAllKeypadKeys(activity); - break; - case KEYBOARD: - break; - default: - break; - } - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), TouchDeviceVariant.KEYBOARD.ordinal()); - } - }, - TOUCH_MENU_ENABLED { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - nativeSetTouchMenuEnabled(enabled); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - SHOW_DISK_OPERATIONS { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - nativeSetShowDiskOperationAnimation(enabled); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - JOYSTICK_AXIS_SENSITIVIY { - @Override - public void load(Apple2Activity activity) { - float sensitivity = floatValue(activity); - nativeSetTouchJoystickAxisSensitivity(sensitivity); - } - - @Override - public int intValue(Apple2Activity activity) { - final int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES; - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), pivot); - } - - @Override - public float floatValue(Apple2Activity activity) { - int tick = intValue(activity); - final int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES; - float sensitivity = 1.f; - if (tick < pivot) { - int decAmount = (pivot - tick); - sensitivity -= (JOYSTICK_AXIS_SENSITIVITY_DEC_STEP * decAmount); - } else if (tick > pivot) { - int incAmount = (tick - pivot); - sensitivity += (JOYSTICK_AXIS_SENSITIVITY_INC_STEP * incAmount); - } - return sensitivity; - } - }, - JOYSTICK_BUTTON_THRESHOLD { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - tick *= getJoystickButtonSwitchThresholdScale(activity); - nativeSetTouchJoystickButtonSwitchThreshold(tick); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 5); - } - }, - JOYSTICK_TAP_BUTTON { - @Override - public void load(Apple2Activity activity) { - loadAllJoystickButtons(activity); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), TouchJoystickButtons.BUTTON1.ordinal()); - } - }, - JOYSTICK_TAPDELAY { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - nativeSetTouchJoystickTapDelay(((float) tick / TAPDELAY_NUM_CHOICES) * TAPDELAY_SCALE); - } - - @Override - public int intValue(Apple2Activity activity) { - int defaultLatency = 8; // /TAPDELAY_NUM_CHOICES * TAPDELAY_SCALE -> 0.2f - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), defaultLatency); - } - }, - JOYSTICK_SWIPEUP_BUTTON { - @Override - public void load(Apple2Activity activity) { - loadAllJoystickButtons(activity); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), TouchJoystickButtons.BOTH.ordinal()); - } - }, - JOYSTICK_SWIPEDOWN_BUTTON { - @Override - public void load(Apple2Activity activity) { - loadAllJoystickButtons(activity); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), TouchJoystickButtons.BUTTON2.ordinal()); - } - }, - JOYSTICK_AXIS_ON_LEFT { - @Override - public void load(Apple2Activity activity) { - nativeTouchJoystickSetAxisOnLeft(booleanValue(activity)); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - JOYSTICK_DIVIDER { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - nativeTouchJoystickSetScreenDivision(((float) tick / JOYSTICK_DIVIDER_NUM_CHOICES)); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), JOYSTICK_DIVIDER_NUM_CHOICES >> 1); - } - }, - JOYSTICK_VISIBILITY { - @Override - public void load(Apple2Activity activity) { - nativeSetTouchJoystickVisibility(booleanValue(activity)); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - JOYSTICK_AZIMUTH_VISIBILITY { - @Override - public void load(Apple2Activity activity) { - nativeSetTouchJoystickAzimuthVisibility(booleanValue(activity)); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - KEYPAD_KEYS { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), KeypadPreset.IJKM_SPACE.ordinal() + 1); - } - }, - KEYPAD_NORTHWEST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_NORTH_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_NORTHEAST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_WEST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_CENTER_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_EAST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_SOUTHWEST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_SOUTH_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_SOUTHEAST_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_TAP_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_SWIPEUP_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYPAD_SWIPEDOWN_KEY { - @Override - public void load(Apple2Activity activity) { - loadAllKeypadKeys(activity); - } - }, - KEYREPEAT_THRESHOLD { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - nativeSetTouchDeviceKeyRepeatThreshold((float) tick / KEYREPEAT_NUM_CHOICES); - } - - @Override - public int intValue(Apple2Activity activity) { - int defaultLatency = KEYREPEAT_NUM_CHOICES / 4; - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), defaultLatency); - } - }, - KEYBOARD_ALT { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 0); - } - }, - KEYBOARD_ALT_PATH { - @Override - public void load(Apple2Activity activity) { - nativeLoadTouchKeyboardJSON(stringValue(activity)); - } - - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } - }, - KEYBOARD_VISIBILITY_ACTIVE { - @Override - public void load(Apple2Activity activity) { - int inactiveTick = KEYBOARD_VISIBILITY_INACTIVE.intValue(activity); - int activeTick = intValue(activity); - nativeSetTouchKeyboardVisibility((float) inactiveTick / ALPHA_SLIDER_NUM_CHOICES, (float) activeTick / ALPHA_SLIDER_NUM_CHOICES); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), ALPHA_SLIDER_NUM_CHOICES); - } - }, - KEYBOARD_VISIBILITY_INACTIVE { - @Override - public void load(Apple2Activity activity) { - int inactiveTick = intValue(activity); - int activeTick = KEYBOARD_VISIBILITY_ACTIVE.intValue(activity); - nativeSetTouchKeyboardVisibility((float) inactiveTick / ALPHA_SLIDER_NUM_CHOICES, (float) activeTick / ALPHA_SLIDER_NUM_CHOICES); - } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 5); - } - }, - KEYBOARD_LOWERCASE_ENABLED { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - nativeSetTouchKeyboardLowercaseEnabled(enabled); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), false); - } - }, - KEYBOARD_CLICK_ENABLED { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - KEYBOARD_GLYPH_SCALE { - @Override - public void load(Apple2Activity activity) { - int scale = intValue(activity); - if (scale == 0) { - scale = 1; - } - nativeSetTouchKeyboardGlyphScale(scale); - } - - @Override - public int intValue(Apple2Activity activity) { - int scale = activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 0); - - if (scale == 0) { - scale = 2; - DisplayMetrics dm = new DisplayMetrics(); - activity.getWindowManager().getDefaultDisplay().getMetrics(dm); - - /* calculating actual physical diagonal size appears to be problematic -- Samsung Galaxy Y reports 15" with this method - double x = Math.pow(dm.widthPixels / dm.xdpi, 2); - double y = Math.pow(dm.heightPixels / dm.ydpi, 2); - double screenInches = Math.sqrt(x + y); - Log.d(TAG, "Screen inches:" + screenInches + " w:" + dm.widthPixels + " h:" + dm.heightPixels); - */ - if (dm.widthPixels <= 480 || dm.heightPixels <= 480) { - scale = 1; - } - - saveInt(activity, scale); - } - - return scale; - } - }, - PORTRAIT_KEYBOARD_HEIGHT_SCALE { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - float portraitKeyboardHeightScale = ((float) tick / PORTRAIT_CALIBRATE_NUM_CHOICES); + private static JSONObject _prefDomain(String domain) { + JSONObject map = null; + try { + map = sSettings.getJSONObject(domain); + } catch (JSONException e) { + e.printStackTrace(); + } + if (map == null) { + map = new JSONObject(); try { - JSONObject prefs = new JSONObject().put(PREFNAME_PORTRAIT_HEIGHT_SCALE, portraitKeyboardHeightScale); - nativeSetTouchModelPreferences(TouchDeviceVariant.KEYBOARD.ordinal(), prefs.toString()); + sSettings.put(domain, map); } catch (JSONException e) { - Log.v(TAG, "" + e); + e.printStackTrace(); } } + return map; + } - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), PORTRAIT_CALIBRATE_NUM_CHOICES >> 1); + public static void setJSONPref(Apple2AbstractMenu.IMenuEnum menu, Object val) { + try { + JSONObject map = _prefDomain(menu.getPrefDomain()); + map.put(menu.getPrefKey(), val); + } catch (JSONException e) { + e.printStackTrace(); } - }, - PORTRAIT_FRAMEBUFFER_POSITION_SCALE { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - float portraitFramebufferPositionScale = ((float) tick / PORTRAIT_CALIBRATE_NUM_CHOICES); + sNativeIsDirty.set(true); + } + + public static void setJSONPref(String domain, String key, Object val) { + try { + JSONObject map = _prefDomain(domain); + map.put(key, val); + } catch (JSONException e) { + e.printStackTrace(); + } + sNativeIsDirty.set(true); + } + + public static Object getJSONPref(Apple2AbstractMenu.IMenuEnum menu) { + String key = null; + Object val = null; + JSONObject map = null; + try { + map = _prefDomain(menu.getPrefDomain()); + key = menu.getPrefKey(); + val = map.get(key); + } catch (JSONException e) { + Log.d(TAG, "Did not find value for domain:" + menu.getPrefDefault() + " key:" + key); + } + if (val == null && key != null) { + val = menu.getPrefDefault(); try { - JSONObject prefs = new JSONObject().put(PREFNAME_PORTRAIT_POSITION_SCALE, portraitFramebufferPositionScale); - nativeSetTouchModelPreferences(TouchDeviceVariant.FRAMEBUFFER.ordinal(), prefs.toString()); + map.put(key, val); } catch (JSONException e) { - Log.v(TAG, "" + e); + e.printStackTrace(); } } + return val; + } - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), (PORTRAIT_CALIBRATE_NUM_CHOICES*3/4)); + public static Object getJSONPref(String domain, String key, Object defaultVal) { + Object val = null; + JSONObject map = null; + try { + map = _prefDomain(domain); + val = map.get(key); + } catch (JSONException e) { + Log.d(TAG, "Did not find value for domain:" + domain + " key:" + key); } - }, - PORTRAIT_KEYBOARD_POSITION_SCALE { - @Override - public void load(Apple2Activity activity) { - int tick = intValue(activity); - float portraitKeyboardPositionScale = ((float) tick / PORTRAIT_CALIBRATE_NUM_CHOICES); + if (val == null) { + val = defaultVal; try { - JSONObject prefs = new JSONObject().put(PREFNAME_PORTRAIT_POSITION_SCALE, portraitKeyboardPositionScale); - nativeSetTouchModelPreferences(TouchDeviceVariant.KEYBOARD.ordinal(), prefs.toString()); + map.put(key, val); } catch (JSONException e) { - Log.v(TAG, "" + e); + e.printStackTrace(); } } - - @Override - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 0); - } - }, - CRASH_CHECK { - @Override - public void load(Apple2Activity activity) { - boolean enabled = booleanValue(activity); - } - - @Override - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), true); - } - }, - GL_VENDOR { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } - - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } - }, - GL_RENDERER { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } - - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } - }, - GL_VERSION { - @Override - public void load(Apple2Activity activity) { - /* ... */ - } - - @Override - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), ""); - } - }; - - public enum HiresColor { - BW, - COLOR, - INTERPOLATED + return val; } - public enum Volume { - OFF(0), - ONE(1), - TWO(2), - THREE(3), - FOUR(4), - MEDIUM(5), - FIVE(5), - SIX(6), - SEVEN(7), - EIGHT(8), - NINE(9), - MAX(10), - ELEVEN(11); - private int vol; - - Volume(int vol) { - this.vol = vol; + private static float _convertToFloat(Object obj) { + if (obj == null) { + return Float.NaN; } + try { + return (float) obj; + } catch (ClassCastException e) { + e.printStackTrace(); + } + try { + return (float) ((double) obj); + } catch (ClassCastException e) { + e.printStackTrace(); + } + try { + return (float) ((int) obj); + } catch (ClassCastException e) { + e.printStackTrace(); + } + return (float) ((long) obj); } - public enum TouchDeviceVariant { - NONE(0), - JOYSTICK(1), - JOYSTICK_KEYPAD(2), - KEYBOARD(3), - TOPMENU(4), - ALERT(5); - private int dev; - - public static final TouchDeviceVariant FRAMEBUFFER = NONE; - - public static final int size = TouchDeviceVariant.values().length; - - TouchDeviceVariant(int dev) { - this.dev = dev; - } - - static TouchDeviceVariant next(int ord) { - ord = (ord + 1) % size; - return TouchDeviceVariant.values()[ord]; - } + public static float getFloatJSONPref(Apple2AbstractMenu.IMenuEnum menu) { + return _convertToFloat(getJSONPref(menu)); } - public enum TouchJoystickButtons { - NONE(0), - BUTTON1(1), - BUTTON2(2), - BOTH(3); - private int butt; - - TouchJoystickButtons(int butt) { - this.butt = butt; - } + public static float getFloatJSONPref(String domain, String key, Object defaultVal) { + return _convertToFloat(getJSONPref(domain, key, defaultVal)); } - public enum KeypadPreset { - ARROWS_SPACE { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_arrows_space); - } + public static boolean migrate(Apple2Activity activity) { + int versionCode = (int) getJSONPref(PREF_DOMAIN_INTERFACE, PREF_EMULATOR_VERSION, 0); + final boolean firstTime = (versionCode != BuildConfig.VERSION_CODE); + if (firstTime) { + Log.v(TAG, "Triggering migration to Apple2ix version : " + BuildConfig.VERSION_NAME); + setJSONPref(PREF_DOMAIN_INTERFACE, PREF_EMULATOR_VERSION, BuildConfig.VERSION_CODE); + if (BuildConfig.VERSION_CODE == 17) { + // TODO FIXME : migrate all XML prefs to JSON here - @Override - public void apply(Apple2Activity activity) { - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_UP, Apple2KeyboardSettingsMenu.SCANCODE_UP); - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN, Apple2KeyboardSettingsMenu.SCANCODE_DOWN); - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - } - }, - AZ_LEFT_RIGHT_SPACE { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_az_left_right_space); - } - - @Override - public void apply(Apple2Activity activity) { - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - } - }, - LEFT_RIGHT_SPACE { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_left_right_space); - } - - @Override - public void apply(Apple2Activity activity) { - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - } - }, - IJKM_SPACE { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_ijkm_space); - } - - @Override - public void apply(Apple2Activity activity) { - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, (char) 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, (char) 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - } - }, - WADX_SPACE { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_wadx_space); - } - - @Override - public void apply(Apple2Activity activity) { + // special cases : + /* Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) 'W', Apple2KeyboardSettingsMenu.SCANCODE_W); Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); @@ -913,318 +180,68 @@ public enum Apple2Preferences { Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) 'X', Apple2KeyboardSettingsMenu.SCANCODE_X); Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - } - }, - CRAZY_SEAFOX_KEYS { - @Override - public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_crazy_seafox); + */ + } + save(activity); + } + return firstTime; + } - @Override - public void apply(Apple2Activity activity) { - // Heh, the entire purpose of the keypad-variant touch joystick is to make this possible ;-) - Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, (char) 'Y', Apple2KeyboardSettingsMenu.SCANCODE_Y); - Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, (char) 'U', Apple2KeyboardSettingsMenu.SCANCODE_U); - Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, (char) 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); - Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, (char) 'H', Apple2KeyboardSettingsMenu.SCANCODE_H); - Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, (char) 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); - Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, (char) 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); - Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, (char) 'N', Apple2KeyboardSettingsMenu.SCANCODE_N); - Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, (char) 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); - Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, (char) ',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA); - Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, (char) 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); - Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, (char) 'F', Apple2KeyboardSettingsMenu.SCANCODE_F); - Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, (char) Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - } - }; + public static void load(Apple2Activity activity) { + File prefsFile = new File(Apple2Utils.getDataDir(activity), PREFS_JSON_FILE); - public abstract String getTitle(Apple2Activity activity); + StringBuilder jsonString = new StringBuilder(); + if (!Apple2Utils.readEntireFile(prefsFile, jsonString)) { + Log.d(TAG, "Oops, could not read JSON file : " + prefsFile); + } - public abstract void apply(Apple2Activity activity); - - public static final int size = KeypadPreset.values().length; - - public static String[] titles(Apple2Activity activity) { - String[] titles = new String[size]; - int i = 0; - for (KeypadPreset preset : values()) { - titles[i++] = preset.getTitle(activity); - } - return titles; + try { + sSettings = new JSONObject(jsonString.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + if (sSettings == null) { + sSettings = new JSONObject(); } } - public final static String TAG = "Apple2Preferences"; + public static void save(Apple2Activity activity) { - public final static int DECENT_AMOUNT_OF_CHOICES = 20; - public final static int AUDIO_LATENCY_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - public final static int ALPHA_SLIDER_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - public final static int JOYSTICK_DIVIDER_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; + // bespoke reset temporary values + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false); - public final static int TAPDELAY_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - public final static float TAPDELAY_SCALE = 0.5f; - - public final static int KEYREPEAT_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - - public final static int JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES = DECENT_AMOUNT_OF_CHOICES; - public final static int PORTRAIT_CALIBRATE_NUM_CHOICES = 100; - - public final static float JOYSTICK_AXIS_SENSITIVITY_MIN = 0.25f; - public final static float JOYSTICK_AXIS_SENSITIVITY_DEFAULT = 1.f; - public final static float JOYSTICK_AXIS_SENSITIVITY_MAX = 4.f; - public final static float JOYSTICK_AXIS_SENSITIVITY_DEC_STEP = 0.05f; - public final static float JOYSTICK_AXIS_SENSITIVITY_INC_STEP = 0.25f; - public final static int JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_DEFAULT - JOYSTICK_AXIS_SENSITIVITY_MIN) / JOYSTICK_AXIS_SENSITIVITY_DEC_STEP); // 15 - public final static int JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_MAX - JOYSTICK_AXIS_SENSITIVITY_DEFAULT) / JOYSTICK_AXIS_SENSITIVITY_INC_STEP); // 12 - public final static int JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES + JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES; // 15 + 12 - - public final static String PREFNAME_PORTRAIT_HEIGHT_SCALE = "portraitHeightScale"; - public final static String PREFNAME_PORTRAIT_POSITION_SCALE = "portraitPositionScale"; - - // set and apply - - public void saveBoolean(Apple2Activity activity, boolean value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putBoolean(toString(), value).apply(); - load(activity); - } - - public void saveInt(Apple2Activity activity, int value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), value).apply(); - load(activity); - } - - public void saveFloat(Apple2Activity activity, float value) { - throw new RuntimeException("DENIED! You're doing it wrong! =P"); - } - - public void saveString(Apple2Activity activity, String value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putString(toString(), value).apply(); - load(activity); - } - - public void saveHiresColor(Apple2Activity activity, HiresColor value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), value.ordinal()).apply(); - load(activity); - } - - public void saveVolume(Apple2Activity activity, Volume value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), value.ordinal()).apply(); - load(activity); - } - - public void saveTouchDevice(Apple2Activity activity, TouchDeviceVariant value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), value.ordinal()).apply(); - load(activity); - } - - public void saveTouchJoystickButtons(Apple2Activity activity, TouchJoystickButtons value) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(toString(), value.ordinal()).apply(); - load(activity); - } - - public void saveChosenKey(Apple2Activity activity, char ascii, int scancode) { - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(asciiString(), ascii).apply(); - activity.getPreferences(Context.MODE_PRIVATE).edit().putInt(scancodeString(), scancode).apply(); - load(activity); - } - - public void setPath(Apple2Activity activity, String path) { - /* ... */ - } - - // accessors - - public boolean booleanValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getBoolean(toString(), false); - } - - public int intValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 0); - } - - public float floatValue(Apple2Activity activity) { - return (float) activity.getPreferences(Context.MODE_PRIVATE).getInt(toString(), 0); - } - - public String stringValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getString(toString(), null); - } - - public char asciiValue(Apple2Activity activity) { - return (char) activity.getPreferences(Context.MODE_PRIVATE).getInt(asciiString(), ' '); - } - - public int scancodeValue(Apple2Activity activity) { - return activity.getPreferences(Context.MODE_PRIVATE).getInt(scancodeString(), Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - } - - public static void loadPreferences(Apple2Activity activity) { - for (Apple2Preferences pref : Apple2Preferences.values()) { - pref.load(activity); + File prefsFile = new File(Apple2Utils.getDataDir(activity), PREFS_JSON_FILE); + String jsonString = null; + JSONException ex = null; + try { + jsonString = sSettings.toString(2); + } catch (JSONException e) { + Log.w(TAG, "Error attempting to pretty-print JSON : " + e); + ex = e; + jsonString = sSettings.toString(); + } + + if (jsonString != null) { + Apple2Utils.writeFile(new StringBuilder(jsonString), prefsFile); + } else { + Apple2Utils.writeFile(new StringBuilder("{}"), prefsFile); + throw new RuntimeException(ex); // force reset and hopefully send report to me ;-) } - // HACK FIXME TODO 2015/12/13 : native GLTouchDevice is conflating various things ... forcefully reset the current touch device here for now - Apple2Preferences.CURRENT_TOUCH_DEVICE.load(activity); } - public static void resetPreferences(Apple2Activity activity) { - activity.getPreferences(Context.MODE_PRIVATE).edit().clear().commit(); + public static void reset(Apple2Activity activity) { + sSettings = new JSONObject(); + save(activity); activity.quitEmulator(); } - public String asciiString() { - return toString() + "_ASCII"; - } - - public String scancodeString() { - return toString() + "_SCAN"; - } - - public static int getJoystickButtonSwitchThresholdScale(Apple2Activity activity) { - - DisplayMetrics dm = new DisplayMetrics(); - activity.getWindowManager().getDefaultDisplay().getMetrics(dm); - - int smallScreenAxis = dm.widthPixels < dm.heightPixels ? dm.widthPixels : dm.heightPixels; - int oneThirdScreenAxis = smallScreenAxis / 3; - - // largest switch threshold value is 1/3 small dimension of screen - return oneThirdScreenAxis / JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES; - } - - // ------------------------------------------------------------------------ - // internals ... - - protected abstract void load(Apple2Activity activity); - - protected static void warnError(Apple2Activity activity, int titleId, int mesgId) { - AlertDialog dialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(titleId).setMessage(mesgId).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }).create(); - activity.registerAndShowDialog(dialog); - } - - public static void loadAllKeypadKeys(Apple2Activity activity) { - int[] rosetteChars = new int[]{ - KEYPAD_NORTHWEST_KEY.asciiValue(activity), - KEYPAD_NORTH_KEY.asciiValue(activity), - KEYPAD_NORTHEAST_KEY.asciiValue(activity), - KEYPAD_WEST_KEY.asciiValue(activity), - KEYPAD_CENTER_KEY.asciiValue(activity), - KEYPAD_EAST_KEY.asciiValue(activity), - KEYPAD_SOUTHWEST_KEY.asciiValue(activity), - KEYPAD_SOUTH_KEY.asciiValue(activity), - KEYPAD_SOUTHEAST_KEY.asciiValue(activity), - }; - int[] rosetteScancodes = new int[]{ - KEYPAD_NORTHWEST_KEY.scancodeValue(activity), - KEYPAD_NORTH_KEY.scancodeValue(activity), - KEYPAD_NORTHEAST_KEY.scancodeValue(activity), - KEYPAD_WEST_KEY.scancodeValue(activity), - KEYPAD_CENTER_KEY.scancodeValue(activity), - KEYPAD_EAST_KEY.scancodeValue(activity), - KEYPAD_SOUTHWEST_KEY.scancodeValue(activity), - KEYPAD_SOUTH_KEY.scancodeValue(activity), - KEYPAD_SOUTHEAST_KEY.scancodeValue(activity), - }; - int[] buttonsChars = new int[]{ - KEYPAD_TAP_KEY.asciiValue(activity), - KEYPAD_SWIPEUP_KEY.asciiValue(activity), - KEYPAD_SWIPEDOWN_KEY.asciiValue(activity), - }; - int[] buttonsScancodes = new int[]{ - KEYPAD_TAP_KEY.scancodeValue(activity), - KEYPAD_SWIPEUP_KEY.scancodeValue(activity), - KEYPAD_SWIPEDOWN_KEY.scancodeValue(activity), - }; - nativeTouchJoystickSetKeypadTypes(rosetteChars, rosetteScancodes, buttonsChars, buttonsScancodes); - } - - public static void loadAllJoystickButtons(Apple2Activity activity) { - nativeSetTouchJoystickButtonTypes( - JOYSTICK_TAP_BUTTON.intValue(activity), - JOYSTICK_SWIPEUP_BUTTON.intValue(activity), - JOYSTICK_SWIPEDOWN_BUTTON.intValue(activity)); - } - - public static void insertDisk(Apple2Activity activity, String fullPath, boolean isDriveA, boolean isReadOnly) { - File file = new File(fullPath); - if (!file.exists()) { - fullPath = fullPath + ".gz"; - file = new File(fullPath); - } - if (file.exists()) { - activity.chooseDisk(fullPath, isDriveA, isReadOnly); - } else { - Log.d(TAG, "Cannot insert: " + fullPath); + public static void sync(Apple2Activity activity, String domain) { + save(activity); + if (sNativeIsDirty.getAndSet(false)) { + nativePrefsSync(domain); } } - // native hooks - - private static native void nativeSetColor(int color); - - private static native boolean nativeSetSpeakerEnabled(boolean enabled); - - private static native void nativeSetSpeakerVolume(int volume); - - private static native boolean nativeSetMockingboardEnabled(boolean enabled); - - private static native void nativeSetMockingboardVolume(int volume); - - private static native void nativeSetAudioLatency(float latencySecs); - - public static native void nativeSetCurrentTouchDevice(int device); - - private static native void nativeSetTouchJoystickButtonTypes(int down, int north, int south); - - private static native void nativeSetTouchJoystickTapDelay(float secs); - - private static native void nativeSetTouchJoystickAxisSensitivity(float multiplier); - - private static native void nativeSetTouchJoystickButtonSwitchThreshold(int delta); - - private static native void nativeSetTouchJoystickVisibility(boolean visibility); - - private static native void nativeSetTouchJoystickAzimuthVisibility(boolean visibility); - - public static native void nativeSetTouchMenuEnabled(boolean enabled); - - public static native void nativeSetShowDiskOperationAnimation(boolean enabled); - - private static native void nativeSetTouchKeyboardVisibility(float inactiveAlpha, float activeAlpha); - - private static native void nativeSetTouchKeyboardLowercaseEnabled(boolean enabled); - - private static native void nativeSetTouchKeyboardGlyphScale(int scale); - - public static native int nativeGetCurrentTouchDevice(); - - public static native int nativeGetCPUSpeed(); - - public static native void nativeSetCPUSpeed(int percentSpeed); - - public static native void nativeTouchJoystickSetScreenDivision(float division); - - public static native void nativeTouchJoystickSetAxisOnLeft(boolean axisIsOnLeft); - - public static native void nativeTouchDeviceBeginCalibrationMode(); - - public static native void nativeTouchDeviceEndCalibrationMode(); - - private static native void nativeTouchJoystickSetKeypadTypes(int[] rosetteChars, int[] rosetteScancodes, int[] buttonsChars, int[] buttonsScancodes); - - private static native void nativeSetTouchDeviceKeyRepeatThreshold(float threshold); - - private static native void nativeLoadTouchKeyboardJSON(String path); - - private static native void nativeSetTouchModelPreferences(int modelType, String jsonString); - - + private static native void nativePrefsSync(String domain); } diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java index 9d77cbcb..ae49d5bb 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2SettingsMenu.java @@ -55,6 +55,29 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { return true; } + public enum TouchDeviceVariant { + NONE(0), + JOYSTICK(1), + JOYSTICK_KEYPAD(2), + KEYBOARD(3), + TOPMENU(4), + ALERT(5); + private int dev; + + public static final TouchDeviceVariant FRAMEBUFFER = NONE; + + public static final int size = TouchDeviceVariant.values().length; + + TouchDeviceVariant(int dev) { + this.dev = dev; + } + + static TouchDeviceVariant next(int ord) { + ord = (ord + 1) % size; + return TouchDeviceVariant.values()[ord]; + } + } + enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { CURRENT_INPUT { @Override @@ -67,6 +90,21 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.input_current_summary); } + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN; + } + + @Override + public String getPrefKey() { + return "screenOwner"; + } + + @Override + public Object getPrefDefault() { + return TouchDeviceVariant.KEYBOARD.ordinal(); + } + @Override public final View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -76,6 +114,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + final IMenuEnum self = this; _alertDialogHandleSelection(activity, R.string.input_current, new String[]{ activity.getResources().getString(R.string.joystick), activity.getResources().getString(R.string.keypad), @@ -83,12 +122,13 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { }, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(activity) - 1; + int val = (int) Apple2Preferences.getJSONPref(self); + return val - 1; } @Override public void saveInt(int value) { - Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(activity, Apple2Preferences.TouchDeviceVariant.values()[value + 1]); + Apple2Preferences.setJSONPref(self, TouchDeviceVariant.values()[value].ordinal() + 1); } }); } @@ -184,14 +224,25 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.disk_show_operation_summary); } + @Override + public String getPrefKey() { + return "diskAnimationsEnabled"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.SHOW_DISK_OPERATIONS.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.SHOW_DISK_OPERATIONS.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); return convertView; @@ -233,7 +284,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - Apple2Preferences.resetPreferences(activity); + Apple2Preferences.reset(activity); } }).setNegativeButton(R.string.no, null); AlertDialog dialog = builder.create(); @@ -260,15 +311,26 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { } } + @Override + public String getPrefKey() { + return "sendCrashReports"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public View getView(final Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); if (!BuildConfig.DEBUG) { - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.CRASH_CHECK.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); + final IMenuEnum self = this; cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.CRASH_CHECK.saveBoolean(activity, isChecked); + Apple2Preferences.setJSONPref(self, isChecked); } }); } @@ -312,9 +374,23 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_INTERFACE; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Utils.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Utils.java new file mode 100644 index 00000000..dd296d99 --- /dev/null +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Utils.java @@ -0,0 +1,365 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 3 or later (your choice) as published by the Free Software + * Foundation. + * + * Copyright 2016 Aaron Culliney + * + */ + +package org.deadc0de.apple2ix; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.os.Environment; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; + +import org.deadc0de.apple2ix.basic.R; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +public class Apple2Utils { + + public final static String TAG = "Apple2Utils"; + + private static String sDataDir = null; + private static File sExternalFilesDir = null; + private static File sDownloadFilesDir = null; + + public static boolean readEntireFile(File file, StringBuilder fileData) { + final int maxAttempts = 5; + int attempts = 0; + do { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + char[] buf = new char[1024]; + int numRead = 0; + while ((numRead = reader.read(buf)) != -1) { + String readData = String.valueOf(buf, 0, numRead); + fileData.append(readData); + } + reader.close(); + break; + } catch (InterruptedIOException ie) { + /* EINTR, EAGAIN ... */ + } catch (IOException e) { + Log.d(TAG, "Error reading file at path : " + file.toString()); + } + + try { + Thread.sleep(100, 0); + } catch (InterruptedException e) { + /* ... */ + } + ++attempts; + } while (attempts < maxAttempts); + + return attempts < maxAttempts; + } + + public static boolean writeFile(final StringBuilder data, File file) { + + final int maxAttempts = 5; + int attempts = 0; + do { + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + writer.append(data); + writer.flush(); + writer.close(); + break; + } catch (InterruptedIOException ie) { + /* EINTR, EAGAIN ... */ + } catch (IOException e) { + Log.e(TAG, "Exception attempting to write data : " + e); + } + + try { + Thread.sleep(100, 0); + } catch (InterruptedException e) { + /* ... */ + } + ++attempts; + } while (attempts < maxAttempts); + + return attempts < maxAttempts; + } + + + public static File getExternalStorageDirectory(Apple2Activity activity) { + + do { + if (sExternalFilesDir != null) { + break; + } + + String storageState = Environment.getExternalStorageState(); + if (!storageState.equals(Environment.MEDIA_MOUNTED)) { + // 2015/10/28 : do not expose sExternalFilesDir/sDownloadFilesDir unless they are writable + break; + } + + File externalStorageDir = Environment.getExternalStorageDirectory(); + if (externalStorageDir == null) { + break; + } + + File externalDir = new File(externalStorageDir, "apple2ix"); // /sdcard/apple2ix + if (!externalDir.exists()) { + boolean made = externalDir.mkdirs(); + if (!made) { + Log.d(TAG, "WARNING: could not make directory : " + sExternalFilesDir); + break; + } + } + + sExternalFilesDir = externalDir; + sDownloadFilesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + } while (false); + + return sExternalFilesDir; + } + + public static File getDownloadsDirectory(Apple2Activity activity) { + getExternalStorageDirectory(activity); + return sDownloadFilesDir; + } + + // HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources + // out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about + // security or DRM for these assets =) + public static String getDataDir(Apple2Activity activity) { + + if (sDataDir != null) { + return sDataDir; + } + + try { + PackageManager pm = activity.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), 0); + sDataDir = pi.applicationInfo.dataDir; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "" + e); + if (sDataDir == null) { + sDataDir = "/data/local/tmp"; + } + } + + return sDataDir; + } + + public static void exposeAPKAssetsToExternal(Apple2Activity activity) { + getExternalStorageDirectory(activity); + if (sExternalFilesDir == null) { + return; + } + + final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + bar.setVisibility(View.VISIBLE); + bar.setIndeterminate(true); + } catch (NullPointerException npe) { + Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #1"); + } + } + }); + + Log.v(TAG, "Overwriting system files in /sdcard/apple2ix/ (external storage) ..."); + recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/sExternalFilesDir.getAbsolutePath(), false); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + bar.setVisibility(View.INVISIBLE); + bar.setIndeterminate(false); + } catch (NullPointerException npe) { + Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #2"); + } + } + }); + } + + public static void exposeAPKAssets(Apple2Activity activity) { + final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + bar.setVisibility(View.VISIBLE); + bar.setIndeterminate(true); + } catch (NullPointerException npe) { + Log.v(TAG, "Avoid NPE in exposeAPKAssets #1"); + } + } + }); + + getDataDir(activity); + + // FIXME TODO : Heavy-handed migration to 1.1.3 ... + recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "blanks")); + recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "demo")); + recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "eamon")); + recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "logo")); + recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "miscgame")); + + Log.d(TAG, "First time copying stuff-n-things out of APK for ease-of-NDK access..."); + + getExternalStorageDirectory(activity); + recursivelyCopyAPKAssets(activity, /*from APK directory:*/"disks", /*to location:*/new File(sDataDir, "disks").getAbsolutePath(), true); + recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/new File(sDataDir, "keyboards").getAbsolutePath(), false); + recursivelyCopyAPKAssets(activity, /*from APK directory:*/"shaders", /*to location:*/new File(sDataDir, "shaders").getAbsolutePath(), false); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + bar.setVisibility(View.INVISIBLE); + bar.setIndeterminate(false); + } catch (NullPointerException npe) { + Log.v(TAG, "Avoid NPE in exposeAPKAssets #1"); + } + } + }); + } + + public static void exposeSymbols(Apple2Activity activity) { + recursivelyCopyAPKAssets(activity, /*from APK directory:*/"symbols", /*to location:*/new File(sDataDir, "symbols").getAbsolutePath(), false); + } + + public static void unexposeSymbols(Apple2Activity activity) { + recursivelyDelete(new File(sDataDir, "symbols")); + } + + // TODO FIXME : WARNING : this is super dangerous if there are symlinks !!! + private static void recursivelyDelete(File file) { + if (file.isDirectory()) { + for (File f : file.listFiles()) { + recursivelyDelete(f); + } + } + if (!file.delete()) { + Log.d(TAG, "Failed to delete file: " + file); + } + } + + private static void recursivelyCopyAPKAssets(Apple2Activity activity, String srcFileOrDir, String dstFileOrDir, boolean shouldGzip) { + AssetManager assetManager = activity.getAssets(); + + final int maxAttempts = 5; + String[] files = null; + int attempts = 0; + do { + try { + files = assetManager.list(srcFileOrDir); + break; + } catch (InterruptedIOException e) { + /* EINTR, EAGAIN ... */ + } catch (IOException e) { + Log.d(TAG, "OOPS exception attempting to list APK files at : " + srcFileOrDir + " : " + e); + } + + try { + Thread.sleep(100, 0); + } catch (InterruptedException ie) { + /* ... */ + } + ++attempts; + } while (attempts < maxAttempts); + + if (files == null) { + Log.d(TAG, "OOPS, could not list APK assets at : " + srcFileOrDir); + return; + } + + if (files.length > 0) { + // ensure destination directory exists + File dstPath = new File(dstFileOrDir); + if (!dstPath.mkdirs()) { + if (!dstPath.exists()) { + Log.d(TAG, "OOPS, could not mkdirs on " + dstPath); + return; + } + } + for (String filename : files) { + // iterate on files and subdirectories + recursivelyCopyAPKAssets(activity, srcFileOrDir + File.separator + filename, dstFileOrDir + File.separator + filename, shouldGzip); + } + return; + } + + // presumably this is a file, not a subdirectory + InputStream is = null; + OutputStream os = null; + attempts = 0; + do { + try { + is = assetManager.open(srcFileOrDir); + if (shouldGzip) { + os = new GZIPOutputStream(new FileOutputStream(dstFileOrDir + ".gz")); + } else { + os = new FileOutputStream(dstFileOrDir); + } + copyFile(is, os); + break; + } catch (InterruptedIOException e) { + /* EINTR, EAGAIN */ + } catch (IOException e) { + Log.e(TAG, "Failed to copy asset file: " + srcFileOrDir, e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // NOOP + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + // NOOP + } + } + } + try { + Thread.sleep(100, 0); + } catch (InterruptedException ie) { + /* ... */ + } + ++attempts; + } while (attempts < maxAttempts); + } + + private static void copyFile(InputStream is, OutputStream os) throws IOException { + final int BUF_SZ = 4096; + byte[] buf = new byte[BUF_SZ]; + while (true) { + int len = is.read(buf, 0, BUF_SZ); + if (len < 0) { + break; + } + os.write(buf, 0, len); + } + os.flush(); + } +} + diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2VideoSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2VideoSettingsMenu.java index f6a70c71..2a11e8a3 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2VideoSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2VideoSettingsMenu.java @@ -11,10 +11,10 @@ package org.deadc0de.apple2ix; +import android.content.pm.ActivityInfo; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; -import android.widget.TextView; import java.util.ArrayList; @@ -49,13 +49,19 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { throw new ArrayIndexOutOfBoundsException(); } if (position == SETTINGS.PORTRAIT_CALIBRATE.ordinal()) { - if (Apple2Preferences.LANDSCAPE_MODE.booleanValue(mActivity)) { + if ((boolean) Apple2Preferences.getJSONPref(SETTINGS.LANDSCAPE_MODE)) { return false; } } return true; } + public enum HiresColor { + BW, + COLOR, + INTERPOLATED + } + protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { LANDSCAPE_MODE { @Override @@ -68,15 +74,31 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.mode_landscape_summary); } + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_INTERFACE; + } + + @Override + public String getPrefKey() { + return "landscapeEnabled"; + } + + @Override + public Object getPrefDefault() { + return true; + } + @Override public final View getView(final Apple2Activity activity, View convertView) { + final Object self = this; convertView = _basicView(activity, this, convertView); - CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.LANDSCAPE_MODE.booleanValue(activity)); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Apple2Preferences.LANDSCAPE_MODE.saveBoolean(activity, isChecked); - Apple2Preferences.LANDSCAPE_MODE.load(activity); + Apple2Preferences.setJSONPref((IMenuEnum)self, isChecked); + applyLandscapeMode(activity); } }); return convertView; @@ -108,7 +130,9 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { } } - Apple2Preferences.LANDSCAPE_MODE.saveBoolean(activity, false); + // switch to portrait + Apple2Preferences.setJSONPref(SETTINGS.LANDSCAPE_MODE, false); + applyLandscapeMode(activity); Apple2PortraitCalibration calibration = new Apple2PortraitCalibration(activity, viewStack); // show this new view... @@ -131,6 +155,21 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { return activity.getResources().getString(R.string.color_configure_summary); } + @Override + public String getPrefDomain() { + return Apple2Preferences.PREF_DOMAIN_VIDEO; + } + + @Override + public String getPrefKey() { + return "colorMode"; + } + + @Override + public Object getPrefDefault() { + return HiresColor.INTERPOLATED.ordinal(); + } + @Override public View getView(Apple2Activity activity, View convertView) { convertView = _basicView(activity, this, convertView); @@ -140,6 +179,7 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) { + final Apple2AbstractMenu.IMenuEnum self = this; _alertDialogHandleSelection(activity, R.string.video_configure, new String[]{ settingsMenu.mActivity.getResources().getString(R.string.color_bw), settingsMenu.mActivity.getResources().getString(R.string.color_color), @@ -147,12 +187,12 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { }, new IPreferenceLoadSave() { @Override public int intValue() { - return Apple2Preferences.HIRES_COLOR.intValue(activity); + return (int) Apple2Preferences.getJSONPref(self); } @Override public void saveInt(int value) { - Apple2Preferences.HIRES_COLOR.saveHiresColor(settingsMenu.mActivity, Apple2Preferences.HiresColor.values()[value]); + Apple2Preferences.setJSONPref(self, HiresColor.values()[value].ordinal()); } }); } @@ -160,9 +200,23 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { public static final int size = SETTINGS.values().length; + @Override + public String getPrefDomain() { + return null; + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - /* ... */ } @Override @@ -170,6 +224,14 @@ public class Apple2VideoSettingsMenu extends Apple2AbstractMenu { return _basicView(activity, this, convertView); } + public static void applyLandscapeMode(final Apple2Activity activity) { + if ((boolean) Apple2Preferences.getJSONPref(SETTINGS.LANDSCAPE_MODE)) { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } else { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + public static String[] titles(Apple2Activity activity) { String[] titles = new String[size]; int i = 0; diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java index 93f9cea3..65f84ac5 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2View.java @@ -50,8 +50,6 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice public final static long NATIVE_TOUCH_JOY_KPAD = (1 << 8); public final static long NATIVE_TOUCH_INPUT_DEVICE_CHANGED = (1 << 16); - public final static long NATIVE_TOUCH_CPU_SPEED_DEC = (1 << 17); - public final static long NATIVE_TOUCH_CPU_SPEED_INC = (1 << 18); public final static long NATIVE_TOUCH_ASCII_SCANCODE_SHIFT = 32; public final static long NATIVE_TOUCH_ASCII_SCANCODE_MASK = 0xFFFFL; @@ -60,16 +58,15 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice private Apple2Activity mActivity; - private Runnable mGraphicsInitializedRunnable; private final InputManagerCompat mInputManager; private float[] mXCoords = new float[MAX_FINGERS]; private float[] mYCoords = new float[MAX_FINGERS]; + private int mWidth = 0; + private int mHeight = 0; - private static native void nativeGraphicsInitialized(int width, int height, boolean landscape); - - private static native void nativeGraphicsChanged(int width, int height, boolean landscape); + private static native void nativeGraphicsInitialized(); private static native void nativeRender(); @@ -78,10 +75,9 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice public static native long nativeOnTouch(int action, int pointerCount, int pointerIndex, float[] xCoords, float[] yCoords); - public Apple2View(Apple2Activity activity, Runnable graphicsInitializedRunnable) { + public Apple2View(Apple2Activity activity) { super(activity.getApplication()); mActivity = activity; - mGraphicsInitializedRunnable = graphicsInitializedRunnable; setFocusable(true); setFocusableInTouchMode(true); @@ -123,13 +119,12 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice Apple2View.this.getWindowVisibleDisplayFrame(rect); int h = rect.height(); int w = rect.width(); - if (w < h) { - // assure landscape dimensions - final int w_ = w; - w = h; - h = w_; + if (w != mWidth || h != mHeight) { + mWidth = w; + mHeight = h; + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_WIDTH, mWidth); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_HEIGHT, mHeight); } - nativeGraphicsChanged(w, h, Apple2Preferences.LANDSCAPE_MODE.booleanValue(Apple2View.this.mActivity)); } }); @@ -358,25 +353,17 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice @Override public void onSurfaceChanged(GL10 gl, int width, int height) { - Apple2Preferences.GL_VENDOR.saveString(mActivity, GLES20.glGetString(GLES20.GL_VENDOR)); - Apple2Preferences.GL_RENDERER.saveString(mActivity, GLES20.glGetString(GLES20.GL_RENDERER)); - Apple2Preferences.GL_VERSION.saveString(mActivity, GLES20.glGetString(GLES20.GL_VERSION)); + Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_VENDOR, GLES20.glGetString(GLES20.GL_VENDOR)); + Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_RENDERER, GLES20.glGetString(GLES20.GL_RENDERER)); + Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_VERSION, GLES20.glGetString(GLES20.GL_VERSION)); Log.v(TAG, "graphicsInitialized(" + width + ", " + height + ")"); - if (width < height) { - // assure landscape dimensions - final int w_ = width; - width = height; - height = w_; - } - - nativeGraphicsInitialized(width, height, Apple2Preferences.LANDSCAPE_MODE.booleanValue(Apple2View.this.mActivity)); - - if (Apple2View.this.mGraphicsInitializedRunnable != null) { - Apple2View.this.mGraphicsInitializedRunnable.run(); - Apple2View.this.mGraphicsInitializedRunnable = null; - } + Apple2View.this.mWidth = width; + Apple2View.this.mHeight = height; + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_WIDTH, mWidth); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_HEIGHT, mHeight); + nativeGraphicsInitialized(); Apple2View.this.mActivity.maybeResumeEmulation(); } @@ -517,7 +504,7 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice } if ((nativeFlags & NATIVE_TOUCH_KEY_TAP) != 0) { - if (Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(mActivity)) { + if ((boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.KEYBOARD_ENABLE_CLICK)) { AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE); if (am != null) { am.playSoundEffect(AudioManager.FX_KEY_CLICK); @@ -532,45 +519,6 @@ class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDevice apple2MenuView.onKeyTapCalibrationEvent(ascii, scancode); } } - - if ((nativeFlags & NATIVE_TOUCH_MENU) == 0) { - break; - } - - // handle menu-specific actions - - if ((nativeFlags & NATIVE_TOUCH_INPUT_DEVICE_CHANGED) != 0) { - Apple2Preferences.TouchDeviceVariant nextVariant; - if ((nativeFlags & NATIVE_TOUCH_KBD) != 0) { - nextVariant = Apple2Preferences.TouchDeviceVariant.KEYBOARD; - } else if ((nativeFlags & NATIVE_TOUCH_JOY) != 0) { - nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK; - } else if ((nativeFlags & NATIVE_TOUCH_JOY_KPAD) != 0) { - nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD; - } else { - int touchDevice = Apple2Preferences.nativeGetCurrentTouchDevice(); - nextVariant = Apple2Preferences.TouchDeviceVariant.next(touchDevice); - } - Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(mActivity, nextVariant); - } else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_DEC) != 0) { - int percentSpeed = Apple2Preferences.nativeGetCPUSpeed(); - if (percentSpeed > 400) { // HACK: max value from native side - percentSpeed = 375; - } else if (percentSpeed > 100) { - percentSpeed -= 25; - } else { - percentSpeed -= 5; - } - Apple2Preferences.CPU_SPEED_PERCENT.saveInt(mActivity, percentSpeed); - } else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_INC) != 0) { - int percentSpeed = Apple2Preferences.nativeGetCPUSpeed(); - if (percentSpeed >= 100) { - percentSpeed += 25; - } else { - percentSpeed += 5; - } - Apple2Preferences.CPU_SPEED_PERCENT.saveInt(mActivity, percentSpeed); - } } while (false); return true; diff --git a/Android/jni/jnihooks.c b/Android/jni/jnihooks.c index 299f5af8..bdef8c35 100644 --- a/Android/jni/jnihooks.c +++ b/Android/jni/jnihooks.c @@ -182,29 +182,24 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jclas FREE(trfile); #endif -#if !TESTING +#if TESTING + cpu_scale_factor = CPU_SCALE_FASTEST; + cpu_altscale_factor = CPU_SCALE_FASTEST; + timing_initialize(); +#else cpu_pause(); emulator_start(); #endif } -void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsChanged(JNIEnv *env, jclass cls, jint width, jint height, jboolean landscape) { - // WARNING : this can happen on non-GL thread - LOG("width:%d height:%d landscape:%d", width, height, landscape); - video_reshape(width, height, landscape); -} - -void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsInitialized(JNIEnv *env, jclass cls, jint width, jint height, jboolean landscape) { - // WARNING : this needs to happen on the GL thread only - LOG("width:%d height:%d landscape:%d", width, height, landscape); - _video_setRenderThread(pthread_self()); // Assume Android knows what it's doing ;-P - +void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsInitialized(JNIEnv *env, jclass cls) { + LOG("..."); + _video_setRenderThread(pthread_self()); // by definition, this method is called on the render thread ... video_shutdown(false); - video_reshape(width, height, landscape); video_init(); } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env, jclass cls) { +jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env, jclass cls) { #if TESTING // test driver thread is managing CPU if (!running_tests) { @@ -214,16 +209,18 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env } #else if (!cpu_isPaused()) { - return; + return false; } LOG("..."); cpu_resume(); #endif + + return true; } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, jclass cls) { +jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, jclass cls) { if (appState != APP_RUNNING) { - return; + return false; } #if DO_CPU65_TRACING @@ -234,7 +231,7 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, disk6_flush(1); if (cpu_isPaused()) { - return; + return false; } LOG("..."); @@ -242,7 +239,10 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, // test driver thread is managing CPU #else cpu_pause(); + prefs_save(); #endif + + return true; } void Java_org_deadc0de_apple2ix_Apple2View_nativeRender(JNIEnv *env, jclass cls) { @@ -341,7 +341,7 @@ jlong Java_org_deadc0de_apple2ix_Apple2View_nativeOnTouch(JNIEnv *env, jclass cl return flags; } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, jclass cls, jstring jPath, jboolean driveA, jboolean readOnly) { +void Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeChooseDisk(JNIEnv *env, jclass cls, jstring jPath, jboolean driveA, jboolean readOnly) { const char *path = (*env)->GetStringUTFChars(env, jPath, NULL); int drive = driveA ? 0 : 1; int ro = readOnly ? 1 : 0; @@ -366,7 +366,7 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, jcl (*env)->ReleaseStringUTFChars(env, jPath, path); } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jclass cls, jboolean driveA) { +void Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeEjectDisk(JNIEnv *env, jclass cls, jboolean driveA) { LOG("..."); disk6_eject(!driveA); } @@ -413,3 +413,19 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j return jstr; } +void Java_org_deadc0de_apple2ix_Apple2Preferences_nativePrefsSync(JNIEnv *env, jclass cls, jstring jDomain) { + const char *domain = NULL; + + if (jDomain) { + domain = (*env)->GetStringUTFChars(env, jDomain, 0); + } + + LOG("... domain: %s", domain); + prefs_load(); + prefs_sync(domain); + + if (jDomain) { + (*env)->ReleaseStringUTFChars(env, jDomain, domain); + } +} + diff --git a/Android/jni/jniprefs.c b/Android/jni/jniprefs.c deleted file mode 100644 index 395f7c10..00000000 --- a/Android/jni/jniprefs.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Apple // emulator for *ix - * - * This software package is subject to the GNU General Public License - * version 3 or later (your choice) as published by the Free Software - * Foundation. - * - * Copyright 2015 Aaron Culliney - * - */ - -#include "common.h" - -#include - -typedef enum AndroidTouchJoystickButtonValues { - //ANDROID_TOUCHJOY_NONE = 0, - ANDROID_TOUCHJOY_BUTTON0 = 1, - ANDROID_TOUCHJOY_BUTTON1, - ANDROID_TOUCHJOY_BUTTON_BOTH, -} AndroidTouchJoystickButtonValues; - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetColor(JNIEnv *env, jclass cls, jint color) { - LOG("color : %d", color); -#if TESTING - color_mode = COLOR; -#else - if (color < COLOR_NONE || color > COLOR_INTERP) { - return; - } - color_mode = color; - - video_reset(); -#endif -} - -jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerEnabled(JNIEnv *env, jclass cls, jboolean enabled) { - LOG("enabled : %d", true); - // NO-OP ... speaker should always be enabled (but volume could be zero) - return true; -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerVolume(JNIEnv *env, jclass cls, jint goesToTen) { - LOG("volume : %d", goesToTen); - assert(goesToTen >= 0); - sound_volume = goesToTen; -#warning FIXME TODO refactor/remove sound_volume ? - vm_reinitializeAudio(); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetAudioLatency(JNIEnv *env, jclass cls, jfloat latencySecs) { -#if !TESTING - LOG("audio latency : %fsecs", latencySecs); - assert(cpu_isPaused()); - audio_setLatency(latencySecs); - timing_reinitializeAudio(); -#endif -} - -jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardEnabled(JNIEnv *env, jclass cls, jboolean enabled) { -#if !TESTING - LOG("mockingboard enabled : %d", enabled); - assert(cpu_isPaused()); - MB_SetEnabled(enabled); - timing_reinitializeAudio(); -#endif - return enabled; -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardVolume(JNIEnv *env, jclass cls, jint goesToTen) { - LOG("mockingboard volume : %d", goesToTen); - assert(goesToTen >= 0); - MB_SetVolumeZeroToTen(goesToTen); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCurrentTouchDevice(JNIEnv *env, jclass cls, jint touchDevice) { - LOG("current touch device : %d", touchDevice); - assert(touchDevice >= 0 && touchDevice < TOUCH_DEVICE_DEVICE_MAX); - switch (touchDevice) { - case TOUCH_DEVICE_JOYSTICK: - keydriver_setTouchKeyboardOwnsScreen(false); - joydriver_setTouchJoystickOwnsScreen(true); - joydriver_setTouchVariant(EMULATED_JOYSTICK); - video_animations->animation_showTouchJoystick(); - break; - - case TOUCH_DEVICE_JOYSTICK_KEYPAD: - keydriver_setTouchKeyboardOwnsScreen(false); - joydriver_setTouchJoystickOwnsScreen(true); - joydriver_setTouchVariant(EMULATED_KEYPAD); - video_animations->animation_showTouchJoystick(); - break; - - case TOUCH_DEVICE_KEYBOARD: - keydriver_setTouchKeyboardOwnsScreen(true); - joydriver_setTouchJoystickOwnsScreen(false); - video_animations->animation_showTouchKeyboard(); - break; - - case TOUCH_DEVICE_NONE: - default: - break; - } -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickVisibility(JNIEnv *env, jclass cls, jboolean visibility) { - LOG("visibility: %d", visibility); - joydriver_setShowControls(visibility); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickAzimuthVisibility(JNIEnv *env, jclass cls, jboolean visibility) { - LOG("visibility: %d", visibility); - joydriver_setShowAzimuth(visibility); -} - -jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCurrentTouchDevice(JNIEnv *env, jclass cls) { - LOG("%s", ""); - if (joydriver_ownsScreen()) { - touchjoy_variant_t variant = joydriver_getTouchVariant(); - if (variant == EMULATED_JOYSTICK) { - return TOUCH_DEVICE_JOYSTICK; - } else if (variant == EMULATED_KEYPAD) { - return TOUCH_DEVICE_JOYSTICK_KEYPAD; - } - } else if (keydriver_ownsScreen()) { - return TOUCH_DEVICE_KEYBOARD; - } - return TOUCH_DEVICE_NONE; -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchMenuEnabled(JNIEnv *env, jclass cls, jboolean enabled) { - LOG("enabled : %d", enabled); - interface_setTouchMenuEnabled(enabled); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetShowDiskOperationAnimation(JNIEnv *env, jclass cls, jboolean enabled) { - LOG("enabled : %d", enabled); - if (video_animations && video_animations->animation_setEnableShowTrackSector) { - video_animations->animation_setEnableShowTrackSector(enabled); - } -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardLowercaseEnabled(JNIEnv *env, jclass cls, jboolean enabled) { - LOG("enabled : %d", enabled); - keydriver_setLowercaseEnabled(enabled); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardVisibility(JNIEnv *env, jclass cls, jfloat inactiveAlpha, jfloat activeAlpha) { - LOG("inactive:%f active:%f", inactiveAlpha, activeAlpha); - keydriver_setVisibilityWhenOwnsScreen(inactiveAlpha, activeAlpha); - interface_setTouchMenuVisibility(inactiveAlpha, activeAlpha); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardGlyphScale(JNIEnv *env, jclass cls, jint glyphScale) { - LOG("glyphScale:%d", glyphScale); - keydriver_setGlyphScale(glyphScale); - interface_setGlyphScale(glyphScale); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonTypes(JNIEnv *env, jclass cls, jint touchDownButton, jint northButton, jint southButton) { - LOG(": %d,%d,%d", touchDownButton, northButton, southButton); - - touchDownButton -= 1; - northButton -= 1; - southButton -= 1; - if (touchDownButton < TOUCH_NONE || touchDownButton > TOUCH_BOTH) { - ERRLOG("OOPS, invalid parameter!"); - return; - } - if (northButton < TOUCH_NONE || northButton > TOUCH_BOTH) { - ERRLOG("OOPS, invalid parameter!"); - return; - } - if (southButton < TOUCH_NONE || southButton > TOUCH_BOTH) { - ERRLOG("OOPS, invalid parameter!"); - return; - } - - uint8_t rosetteChars[ROSETTE_COLS * ROSETTE_ROWS]; - int rosetteScancodes[ROSETTE_COLS * ROSETTE_ROWS]; - rosetteChars[ROSETTE_NORTHWEST] = ' '; rosetteScancodes[ROSETTE_NORTHWEST] = -1; - rosetteChars[ROSETTE_NORTH] = (uint8_t)MOUSETEXT_UP; rosetteScancodes[ROSETTE_NORTH] = -1; - rosetteChars[ROSETTE_NORTHEAST] = ' '; rosetteScancodes[ROSETTE_NORTHEAST] = -1; - rosetteChars[ROSETTE_WEST] = (uint8_t)MOUSETEXT_LEFT; rosetteScancodes[ROSETTE_WEST] = -1; - rosetteChars[ROSETTE_CENTER] = ICONTEXT_MENU_TOUCHJOY; rosetteScancodes[ROSETTE_CENTER] = -1; - rosetteChars[ROSETTE_EAST] = (uint8_t)MOUSETEXT_RIGHT; rosetteScancodes[ROSETTE_EAST] = -1; - rosetteChars[ROSETTE_SOUTHWEST] = ' '; rosetteScancodes[ROSETTE_SOUTHWEST] = -1; - rosetteChars[ROSETTE_SOUTH] = (uint8_t)MOUSETEXT_DOWN; rosetteScancodes[ROSETTE_SOUTH] = -1; - rosetteChars[ROSETTE_SOUTHEAST] = ' '; rosetteScancodes[ROSETTE_SOUTHEAST] = -1; - joydriver_setTouchAxisTypes(rosetteChars, rosetteScancodes); - joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, -1, (touchjoy_button_type_t)northButton, -1, (touchjoy_button_type_t)southButton, -1); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickTapDelay(JNIEnv *env, jclass cls, jfloat secs) { - LOG("tap delay : %f", secs); - joydriver_setTapDelay(secs); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickAxisSensitivity(JNIEnv *env, jclass cls, jfloat multiplier) { - LOG("axis sensitivity : %f", multiplier); - joydriver_setTouchAxisSensitivity(multiplier); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonSwitchThreshold(JNIEnv *env, jclass cls, jint delta) { - LOG("delta : %d", delta); - joydriver_setButtonSwitchThreshold(delta); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetScreenDivision(JNIEnv *env, jclass cls, jfloat division) { - LOG("division : %f", division); - joydriver_setScreenDivision(division); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetAxisOnLeft(JNIEnv *env, jclass cls, jboolean axisIsOnLeft) { - LOG("axis on left : %d", axisIsOnLeft); - joydriver_setAxisOnLeft(axisIsOnLeft); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetKeypadTypes(JNIEnv *env, jclass cls, - jintArray jRosetteChars, jintArray jRosetteScans, jintArray jButtonsChars, jintArray jButtonsScans) -{ - jint *rosetteChars = (*env)->GetIntArrayElements(env, jRosetteChars, 0); - jint *rosetteScans = (*env)->GetIntArrayElements(env, jRosetteScans, 0); - jint *buttonsChars = (*env)->GetIntArrayElements(env, jButtonsChars, 0); - jint *buttonsScans = (*env)->GetIntArrayElements(env, jButtonsScans, 0); - - LOG("NW:%c/%d, N:%c/%d, NE:%c/%d, ... SWIPEUP:%c/%d", - (char)rosetteChars[0], rosetteScans[0], (char)rosetteChars[1], rosetteScans[1], (char)rosetteChars[2], rosetteScans[2], - (char)buttonsChars[1], buttonsScans[1]); - LOG(" W:%c/%d, C:%c/%d, E:%c/%d, ... TAP:%c/%d", - (char)rosetteChars[3], rosetteScans[3], (char)rosetteChars[4], rosetteScans[4], (char)rosetteChars[5], rosetteScans[5], - (char)buttonsChars[0], buttonsScans[0]); - LOG("SW:%c/%d, S:%c/%d, SE:%c/%d, ... SWIPEDN:%c/%d", - (char)rosetteChars[6], rosetteScans[6], (char)rosetteChars[7], rosetteScans[7], (char)rosetteChars[8], rosetteScans[8], - (char)buttonsChars[2], buttonsScans[2]); - - // we could just pass these as jcharArray ... but this isn't a tight loop =P - uint8_t actualChars[ROSETTE_ROWS * ROSETTE_COLS]; - for (unsigned int i=0; i<(ROSETTE_ROWS * ROSETTE_COLS); i++) { - actualChars[i] = (uint8_t)rosetteChars[i]; - } - if (actualChars[4] == ICONTEXT_NONACTIONABLE) { - actualChars[4] = ICONTEXT_MENU_TOUCHJOY; - } - joydriver_setTouchAxisTypes(actualChars, rosetteScans); - joydriver_setTouchButtonTypes( - (touchjoy_button_type_t)buttonsChars[0], buttonsScans[0], - (touchjoy_button_type_t)buttonsChars[1], buttonsScans[1], - (touchjoy_button_type_t)buttonsChars[2], buttonsScans[2]); - - (*env)->ReleaseIntArrayElements(env, jRosetteChars, rosetteChars, 0); - (*env)->ReleaseIntArrayElements(env, jRosetteScans, rosetteScans, 0); - (*env)->ReleaseIntArrayElements(env, jButtonsChars, buttonsChars, 0); - (*env)->ReleaseIntArrayElements(env, jButtonsScans, buttonsScans, 0); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceBeginCalibrationMode(JNIEnv *env, jclass cls) { - LOG("%s", ""); - if (joydriver_ownsScreen()) { - joydriver_beginCalibration(); - } else if (keydriver_ownsScreen()) { - keydriver_beginCalibration(); - } -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceEndCalibrationMode(JNIEnv *env, jclass cls) { - LOG("%s", ""); - if (joydriver_ownsScreen()) { - joydriver_endCalibration(); - } else if (keydriver_ownsScreen()) { - keydriver_endCalibration(); - } -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchDeviceKeyRepeatThreshold(JNIEnv *env, jclass cls, jfloat threshold) { - LOG("threshold : %f", threshold); - joydriver_setKeyRepeatThreshold(threshold); -} - -jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCPUSpeed(JNIEnv *env, jclass cls) { - LOG("%s", ""); - return (jint)round(cpu_scale_factor * 100.0); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCPUSpeed(JNIEnv *env, jclass cls, jint percentSpeed) { - LOG("percentSpeed : %d%%", percentSpeed); -#if TESTING - cpu_scale_factor = CPU_SCALE_FASTEST; - cpu_altscale_factor = CPU_SCALE_FASTEST; - timing_initialize(); -#else - bool wasPaused = cpu_isPaused(); - - if (!wasPaused) { - cpu_pause(); - } - - cpu_scale_factor = percentSpeed/100.0; - if (cpu_scale_factor > CPU_SCALE_FASTEST) { - cpu_scale_factor = CPU_SCALE_FASTEST; - } - if (cpu_scale_factor < CPU_SCALE_SLOWEST) { - cpu_scale_factor = CPU_SCALE_SLOWEST; - } - - if (video_animations->animation_showCPUSpeed) { - video_animations->animation_showCPUSpeed(); - } - - timing_initialize(); - - if (!wasPaused) { - cpu_resume(); - } -#endif -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeLoadTouchKeyboardJSON(JNIEnv *env, jclass cls, jstring j_jsonPath) { - const char *jsonPath = (*env)->GetStringUTFChars(env, j_jsonPath, 0); - LOG("jsonPath: %s", jsonPath); - keydriver_loadAltKbd(jsonPath); - (*env)->ReleaseStringUTFChars(env, j_jsonPath, jsonPath); -} - -void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchModelPreferences(JNIEnv *env, jclass cls, jint modelType, jstring j_jsonString) { - const char *jsonString = (*env)->GetStringUTFChars(env, j_jsonString, 0); - LOG("model: %d", modelType); - - void (*setData)(const char *) = interface_getModelDataSetter((interface_device_t)modelType); - setData(jsonString); - - (*env)->ReleaseStringUTFChars(env, j_jsonString, jsonString); -} - diff --git a/Android/jni/sources.mk b/Android/jni/sources.mk index e70bae32..1e5f8cb9 100644 --- a/Android/jni/sources.mk +++ b/Android/jni/sources.mk @@ -31,13 +31,13 @@ APPLE2_AUDIO_SRC = \ APPLE2_META_SRC = \ $(APPLE2_SRC_PATH)/meta/debug.c $(APPLE2_SRC_PATH)/meta/debugger.c $(APPLE2_SRC_PATH)/meta/opcodes.c \ $(APPLE2_SRC_PATH)/meta/lintrace.c $(APPLE2_SRC_PATH)/test/sha1.c $(APPLE2_SRC_PATH)/json_parse.c \ - $(APPLE2_SRC_PATH)/../externals/jsmn/jsmn.c + $(APPLE2_SRC_PATH)/memmngt.c $(APPLE2_SRC_PATH)/../externals/jsmn/jsmn.c APPLE2_MAIN_SRC = \ $(APPLE2_SRC_PATH)/font.c $(APPLE2_SRC_PATH)/rom.c $(APPLE2_SRC_PATH)/misc.c $(APPLE2_SRC_PATH)/display.c $(APPLE2_SRC_PATH)/vm.c \ $(APPLE2_SRC_PATH)/timing.c $(APPLE2_SRC_PATH)/zlib-helpers.c $(APPLE2_SRC_PATH)/joystick.c $(APPLE2_SRC_PATH)/keys.c \ - $(APPLE2_SRC_PATH)/interface.c $(APPLE2_SRC_PATH)/disk.c $(APPLE2_SRC_PATH)/cpu-supp.c \ - jnihooks.c jniprefs.c androidkeys.c + $(APPLE2_SRC_PATH)/interface.c $(APPLE2_SRC_PATH)/disk.c $(APPLE2_SRC_PATH)/cpu-supp.c $(APPLE2_SRC_PATH)/prefs.c \ + jnihooks.c androidkeys.c APPLE2_OPTIM_CFLAGS := -O2 APPLE2_BASE_CFLAGS := -DAPPLE2IX=1 -DINTERFACE_TOUCH=1 -DMOBILE_DEVICE=1 -DVIDEO_OPENGL=1 -DDEBUGGER=1 -DAUDIO_ENABLED=1 -std=gnu11 -DPREVENT_TEXTREL=1 -fPIC $(APPLE2_OPTIM_CFLAGS) -I$(APPLE2_SRC_PATH) diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index e68d9aed..33b01c30 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -2370,3 +2370,20 @@ unsigned long MB_SetSnapshot(SS_CARD_MOCKINGBOARD* pSS, unsigned long dwSlot_unu #endif } +// ---------------------------------------------------------------------------- + +static void mb_prefsChanged(const char *domain) { + long goesToTen = 0; + prefs_parseLongValue(domain, PREF_MOCKINGBOARD_VOLUME, &goesToTen, /*base:*/10); // expected range 0-10 + if (goesToTen < 0) { + goesToTen = 0; + } + if (goesToTen > 10) { + goesToTen = 10; + } + MB_SetVolumeZeroToTen(goesToTen); +} + +static __attribute__((constructor)) void _init_mockingboard(void) { + prefs_registerListener(PREF_DOMAIN_AUDIO, &mb_prefsChanged); +} diff --git a/src/cpu-supp.c b/src/cpu-supp.c index 09db99fc..96cf1f99 100644 --- a/src/cpu-supp.c +++ b/src/cpu-supp.c @@ -648,8 +648,6 @@ void cpu65_uninterrupt(int reason) } void cpu65_reboot(void) { - timing_initialize(); - video_reset(); joy_button0 = 0xff; // OpenApple cpu65_interrupt(ResetSig); } diff --git a/src/display.c b/src/display.c index 9c6a612a..073ca892 100644 --- a/src/display.c +++ b/src/display.c @@ -477,13 +477,13 @@ static void _initialize_color() { } static void video_prefsChanged(const char *domain) { - long val = 0; + long val = COLOR_INTERP; prefs_parseLongValue(domain, PREF_COLOR_MODE, &val, /*base:*/10); if (val < 0) { - val = 0; + val = COLOR_INTERP; } if (val >= NUM_COLOROPTS) { - val = NUM_COLOROPTS-1; + val = COLOR_INTERP; } color_mode = (color_mode_t)val; video_reset(); @@ -1212,26 +1212,25 @@ void _video_setRenderThread(pthread_t id) { render_thread_id = id; } +bool video_isRenderThread(void) { + return (pthread_self() == render_thread_id); +} + void video_shutdown(bool emulatorShuttingDown) { #if MOBILE_DEVICE // WARNING : shutdown should occur on the render thread. Platform code (iOS, Android) should ensure this is called // from within a render pass... - assert(pthread_self() == render_thread_id); + assert(!render_thread_id || pthread_self() == render_thread_id); #endif video_backend->shutdown(emulatorShuttingDown); - render_thread_id = 0; if (pthread_self() == render_thread_id) { FREE(video__fb); } } -void video_reshape(int w, int h, bool landscape) { - video_backend->reshape(w, h, landscape); -} - void video_render(void) { assert(pthread_self() == render_thread_id); video_backend->render(); diff --git a/src/display.h b/src/display.h index a736a0d1..12a8aa6f 100644 --- a/src/display.h +++ b/src/display.h @@ -39,7 +39,6 @@ typedef struct video_animation_s { void (*animation_showCPUSpeed)(void); void (*animation_showDiskChosen)(int drive); void (*animation_showTrackSector)(int drive, int track, int sect); - void (*animation_setEnableShowTrackSector)(bool enabled); } video_animation_s; @@ -78,9 +77,9 @@ void video_render(void); void _video_setRenderThread(pthread_t id); /* - * Reshape the display to particular dimensions. + * Check if running on render thread. */ -void video_reshape(int w, int h, bool landscape); +bool video_isRenderThread(void); /* * Setup the display. This may be called multiple times in a run, and is diff --git a/src/interface.c b/src/interface.c index 5d4e8e30..1e711dc7 100644 --- a/src/interface.c +++ b/src/interface.c @@ -18,11 +18,6 @@ #if INTERFACE_TOUCH // touch interface managed elsewhere int64_t (*interface_onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) = NULL; -bool (*interface_isTouchMenuAvailable)(void) = NULL; -void (*interface_setTouchMenuEnabled)(bool enabled) = NULL; -void (*interface_setTouchMenuVisibility)(float inactiveAlpha, float activeAlpha) = NULL; -void (*interface_setGlyphScale)(int glyphScale) = NULL; -void (*(*interface_getModelDataSetter)(interface_device_t device))(const char *jsonData) = NULL; #endif static char disk_path[PATH_MAX] = { 0 }; @@ -976,7 +971,7 @@ void c_interface_parameters() { cpu_scale_factor = CPU_SCALE_SLOWEST; } - prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, cpu_scale_factor); + prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, roundf(cpu_scale_factor * 100.f)); break; case OPT_ALTCPU: @@ -985,7 +980,7 @@ void c_interface_parameters() { cpu_altscale_factor = CPU_SCALE_SLOWEST; } - prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, cpu_altscale_factor); + prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, roundf(cpu_altscale_factor * 100.f)); break; case OPT_PATH: @@ -1061,7 +1056,7 @@ void c_interface_parameters() { cpu_scale_factor = CPU_SCALE_FASTEST; } - prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, cpu_scale_factor); + prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, roundf(cpu_scale_factor * 100.f)); break; case OPT_ALTCPU: @@ -1070,7 +1065,7 @@ void c_interface_parameters() { cpu_altscale_factor = CPU_SCALE_FASTEST; } - prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, cpu_altscale_factor); + prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, roundf(cpu_altscale_factor * 100.f)); break; case OPT_PATH: diff --git a/src/interface.h b/src/interface.h index 069b5d00..6a088ddc 100644 --- a/src/interface.h +++ b/src/interface.h @@ -82,22 +82,7 @@ typedef enum interface_touch_event_flags { // handle touch event extern int64_t (*interface_onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords); - -// is the touch menu module itself available? -extern bool (*interface_isTouchMenuAvailable)(void); - -// enable/disable touch menu HUD element -extern void (*interface_setTouchMenuEnabled)(bool enabled); - -// set min/max alpha visibility of touch menu HUD element -extern void (*interface_setTouchMenuVisibility)(float inactiveAlpha, float activeAlpha); - -// set a finer-grained font size (if glyphScale > 1) -extern void (*interface_setGlyphScale)(int glyphScale); - -// get model-specific functions -extern void (*(*interface_getModelDataSetter)(interface_device_t device))(const char *jsonData); -#endif +#endif // INTERFACE_TOUCH #define EXT_GZ ".gz" #define _GZLEN (sizeof(EXT_GZ)-1) diff --git a/src/joystick.c b/src/joystick.c index 04551b9f..3efd7e49 100644 --- a/src/joystick.c +++ b/src/joystick.c @@ -330,28 +330,3 @@ void joydriver_setButton1Pressed(bool pressed) { joy_button1 = (pressed) ? 0x80 : 0x0; } -#if INTERFACE_TOUCH -bool (*joydriver_isTouchJoystickAvailable)(void) = NULL; -void (*joydriver_setTouchJoystickEnabled)(bool enabled) = NULL; -void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd) = NULL; -bool (*joydriver_ownsScreen)(void) = NULL; -void (*joydriver_setTouchButtonTypes)( - touchjoy_button_type_t touchDownChar, int downScancode, - touchjoy_button_type_t northChar, int northScancode, - touchjoy_button_type_t southChar, int southScancode) = NULL; -void (*joydriver_setTapDelay)(float secs) = NULL; -void (*joydriver_setTouchAxisSensitivity)(float multiplier) = NULL; -void (*joydriver_setButtonSwitchThreshold)(int delta) = NULL; -void (*joydriver_setTouchVariant)(touchjoy_variant_t variant) = NULL; -touchjoy_variant_t (*joydriver_getTouchVariant)(void) = NULL; -void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) = NULL; -void (*joydriver_setScreenDivision)(float division) = NULL; -void (*joydriver_setAxisOnLeft)(bool axisIsOnLeft) = NULL; -void (*joydriver_beginCalibration)(void) = NULL; -void (*joydriver_endCalibration)(void) = NULL; -bool (*joydriver_isCalibrating)(void) = NULL; -void (*joydriver_setShowControls)(bool showControls) = NULL; -void (*joydriver_setShowAzimuth)(bool showAzimuth) = NULL; -void (*joydriver_setKeyRepeatThreshold)(float repeatThresholdSecs) = NULL; -#endif - diff --git a/src/joystick.h b/src/joystick.h index ac759f62..2a94ebde 100644 --- a/src/joystick.h +++ b/src/joystick.h @@ -64,103 +64,14 @@ void joydriver_setButton1Pressed(bool pressed); #if INTERFACE_TOUCH -typedef enum touchjoy_variant_t { - EMULATED_NONE = 0, - EMULATED_JOYSTICK, // touch interface emulates a physical joystick device - EMULATED_KEYPAD, // touch interface generates key presses -} touchjoy_variant_t; - typedef enum touchjoy_button_type_t { - TOUCH_NONE = -1, - TOUCH_BUTTON0 = 0, - TOUCH_BUTTON1, + TOUCH_NONE = 0, + TOUCH_BUTTON1 = 1, + TOUCH_BUTTON2, TOUCH_BOTH, // --or-- an ASCII/fonttext value ... } touchjoy_button_type_t; -#define ROSETTE_ROWS 3 -#define ROSETTE_COLS 3 - -enum { - ROSETTE_NORTHWEST=0, - ROSETTE_NORTH, - ROSETTE_NORTHEAST, - ROSETTE_WEST, - ROSETTE_CENTER, - ROSETTE_EAST, - ROSETTE_SOUTHWEST, - ROSETTE_SOUTH, - ROSETTE_SOUTHEAST, -}; - -// is the touch joystick available -extern bool (*joydriver_isTouchJoystickAvailable)(void); - -// enable/disable touch joystick -extern void (*joydriver_setTouchJoystickEnabled)(bool enabled); - -// grant/remove ownership of touch screen -extern void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd); - -// query touch screen ownership -extern bool (*joydriver_ownsScreen)(void); - -/* - * set the joystick button types/visuals (scancodes are fired for EMULATED_KEYPAD variant) - * - * - for EMULATED_JOYSTICK, there is an implicit extra layer-of-indirection for the touchjoy_button_type_t, which maps - * to the open apple, closed apple, or "both" visual keys - * - * - for EMULATED_KEYPAD, the touchjoy_button_type_t is the displayed visual (as ASCII value and lookup into font - * table) - */ -extern void (*joydriver_setTouchButtonTypes)( - touchjoy_button_type_t touchDownChar, int downScancode, - touchjoy_button_type_t northChar, int northScancode, - touchjoy_button_type_t southChar, int southScancode); - -// set the button tap delay (to differentiate between single tap and north/south/etc swipe) -extern void (*joydriver_setTapDelay)(float secs); - -// set the sensitivity multiplier -extern void (*joydriver_setTouchAxisSensitivity)(float multiplier); - -// set the touch button switch threshold -extern void (*joydriver_setButtonSwitchThreshold)(int delta); - -// set the joystick variant -extern void (*joydriver_setTouchVariant)(touchjoy_variant_t variant); - -// get the joystick variant -extern touchjoy_variant_t (*joydriver_getTouchVariant)(void); - -// set the axis visuals (scancodes are fired for EMULATED_KEYPAD variant) -extern void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]); - -// set screen divide between axis and buttons -extern void (*joydriver_setScreenDivision)(float division); - -// swap axis and buttons sides -extern void (*joydriver_setAxisOnLeft)(bool axisIsOnLeft); - -// begin calibration mode -extern void (*joydriver_beginCalibration)(void); - -// end calibration mode -extern void (*joydriver_endCalibration)(void); - -// end calibration mode -extern bool (*joydriver_isCalibrating)(void); - -// set controls visibility -extern void (*joydriver_setShowControls)(bool showControls); - -// set azimuth visibility -extern void (*joydriver_setShowAzimuth)(bool showAzimuth); - -// set key repeat threshold (keypad joystick) -extern void (*joydriver_setKeyRepeatThreshold)(float repeatThresholdSecs); - #endif // INTERFACE_TOUCH #endif // whole file diff --git a/src/json_parse.c b/src/json_parse.c index a2a4aa71..7cf85d28 100644 --- a/src/json_parse.c +++ b/src/json_parse.c @@ -1065,6 +1065,30 @@ bool json_serialize(JSON_ref jsonRef, int fd, bool pretty) { } } +bool json_unescapeSlashes(char **kbdPath) { + // A big "fhank-you" to the Java org.json.JSONStringer API which "helpfully" escapes slash '/' characters + if (!kbdPath) { + return false; + } + + char *str = *kbdPath; + size_t len = strlen(str) + 1; // include termination \0 + char *p0 = NULL; + char *p = str; + while (*p) { + if (*p == '/') { + if (p0 && *p0 == '\\') { + memmove(p0, p, ((str+len)-p)); + p = p0; + } + } + p0 = p; + ++p; + } + + return true; +} + void json_destroy(JSON_ref *jsonRef) { if (!jsonRef) { return; diff --git a/src/json_parse.h b/src/json_parse.h index 5ef59cdb..24f75e92 100644 --- a/src/json_parse.h +++ b/src/json_parse.h @@ -101,11 +101,14 @@ bool json_arrayParseFloatValueAtIndex(const JSON_ref array, unsigned long index, //bool json_arraySetFloatValue(const JSON_ref array, unsigned long index, float val); // ---------------------------------------------------------------------------- -// serialization +// serialization & misc // serialize to file descriptor bool json_serialize(JSON_ref json, int fd, bool pretty); +// unescape all \/ characters ( a big fhank you to Java org.json.JSONXXX !) +bool json_unescapeSlashes(char **kbdPath); + // ---------------------------------------------------------------------------- // destructor void json_destroy(JSON_ref *jsonRef); diff --git a/src/keys.c b/src/keys.c index 4bf3f2dd..b4254f9a 100644 --- a/src/keys.c +++ b/src/keys.c @@ -494,16 +494,6 @@ static __attribute__((constructor)) void __init_keys(void) { } #if INTERFACE_TOUCH -bool (*keydriver_isTouchKeyboardAvailable)(void) = NULL; -void (*keydriver_setTouchKeyboardEnabled)(bool enabled) = NULL; -void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd) = NULL; -bool (*keydriver_ownsScreen)(void) = NULL; -void (*keydriver_setGlyphScale)(int glyphScale) = NULL; -void (*keydriver_setVisibilityWhenOwnsScreen)(float inactiveAlpha, float activeAlpha) = NULL; -void (*keydriver_setLowercaseEnabled)(bool enabled) = NULL; void (*keydriver_keyboardReadCallback)(void) = NULL; -void (*keydriver_beginCalibration)(void) = NULL; -void (*keydriver_endCalibration)(void) = NULL; -void (*keydriver_loadAltKbd)(const char *kbdPath) = NULL; #endif diff --git a/src/keys.h b/src/keys.h index 6dfbb26d..01c132df 100644 --- a/src/keys.h +++ b/src/keys.h @@ -149,38 +149,8 @@ int c_keys_ascii_to_scancode(int ch); void c_keys_handle_input(int scancode, int pressed, int is_cooked); #if INTERFACE_TOUCH -// is the touch keyboard module itself available? -extern bool (*keydriver_isTouchKeyboardAvailable)(void); - -// enable/disable touch keyboard HUD element -extern void (*keydriver_setTouchKeyboardEnabled)(bool enabled); - -// grant/remove ownership of touch screen -extern void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd); - -// query touch screen ownership -extern bool (*keydriver_ownsScreen)(void); - -// set a finer-grained font size (if glyphScale > 1) -extern void (*keydriver_setGlyphScale)(int glyphScale); - -// set visibility -extern void (*keydriver_setVisibilityWhenOwnsScreen)(float inactiveAlpha, float activeAlpha); - -// set lowercase enabled -extern void (*keydriver_setLowercaseEnabled)(bool enabled); - // keyboard read callback extern void (*keydriver_keyboardReadCallback)(void); - -// begin calibration mode -extern void (*keydriver_beginCalibration)(void); - -// end calibration mode -extern void (*keydriver_endCalibration)(void); - -// load an alternate keyboard variant -extern void (*keydriver_loadAltKbd)(const char *kbdPath); #endif #endif diff --git a/src/misc.c b/src/misc.c index 1c316ae2..a762c116 100644 --- a/src/misc.c +++ b/src/misc.c @@ -273,12 +273,13 @@ void emulator_start(void) { } head = NULL; -#ifdef INTERFACE_CLASSIC +#if defined(INTERFACE_CLASSIC) && !TESTING + cpu_pause(); prefs_load(); // user prefs -#if !TESTING + prefs_sync(NULL); + cpu_resume(); c_keys_set_key(kF8); // show credits before emulation start #endif -#endif #if !defined(__APPLE__) && !defined(ANDROID) video_init(); diff --git a/src/prefs.c b/src/prefs.c index cba26e2d..40366e74 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -30,12 +30,15 @@ typedef struct prefs_domain_s { static JSON_ref jsonPrefs = NULL; static prefs_domain_s *domains = NULL; static char *prefsFile = NULL; +static unsigned long listenerCount = 0; static pthread_mutex_t prefsLock = PTHREAD_MUTEX_INITIALIZER; // ---------------------------------------------------------------------------- void prefs_load(void) { + pthread_mutex_lock(&prefsLock); + FREE(prefsFile); const char *apple2JSON = getenv("APPLE2IX_JSON"); @@ -44,7 +47,11 @@ void prefs_load(void) { } if (!prefsFile) { +#ifdef ANDROID + ASPRINTF(&prefsFile, "%s/.apple2.json", data_dir); +#else ASPRINTF(&prefsFile, "%s/.apple2.json", getenv("HOME")); +#endif } assert(prefsFile); @@ -55,21 +62,22 @@ void prefs_load(void) { assert(tokCount > 0); } - prefs_sync(NULL); + pthread_mutex_unlock(&prefsLock); } void prefs_loadString(const char *jsonString) { + pthread_mutex_lock(&prefsLock); json_destroy(&jsonPrefs); int tokCount = json_createFromString(jsonString, &jsonPrefs); if (tokCount < 0) { tokCount = json_createFromString("{}", &jsonPrefs); assert(tokCount > 0); } - - prefs_sync(NULL); + pthread_mutex_unlock(&prefsLock); } bool prefs_save(void) { + pthread_mutex_lock(&prefsLock); int fd = -1; bool success = false; @@ -112,7 +120,7 @@ bool prefs_save(void) { "||||||||||||||||||||||||||||||||||||||||" }; #endif - TEMP_FAILURE_RETRY(fd = open(prefsFile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)); + TEMP_FAILURE_RETRY(fd = open(prefsFile, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR)); if (fd == -1) { PREFS_ERRPRINT(); #if defined(INTERFACE_CLASSIC) && !TESTING @@ -132,9 +140,31 @@ bool prefs_save(void) { TEMP_FAILURE_RETRY(close(fd)); } + pthread_mutex_unlock(&prefsLock); + return success; } +bool prefs_copyJSONValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT JSON_ref *jsonVal) { + pthread_mutex_lock(&prefsLock); + + bool ret = false; + JSON_ref jsonRef = NULL; + do { + int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef); + if (errCount < 0) { + break; + } + + ret = json_mapCopyJSON(jsonRef, key, jsonVal); + } while (0); + + pthread_mutex_unlock(&prefsLock); + json_destroy(&jsonRef); + + return ret; +} + bool prefs_copyStringValue(const char *domain, const char *key, INOUT char **val) { pthread_mutex_lock(&prefsLock); @@ -362,11 +392,13 @@ void prefs_registerListener(const char *domain, prefs_change_callback_f callback newL->nextListener = oldL; newL->prefsChanged = callback; + ++listenerCount; + pthread_mutex_unlock(&prefsLock); } void prefs_sync(const char *domain) { - static int syncCount = 0; + static unsigned long syncCount = 0; pthread_mutex_lock(&prefsLock); ++syncCount; if (syncCount > 1) { @@ -375,16 +407,34 @@ void prefs_sync(const char *domain) { } pthread_mutex_unlock(&prefsLock); + void **alreadySynced = MALLOC(listenerCount * sizeof(void *)); + unsigned long idx = 0; + prefs_domain_s *dom = domains; do { while (dom) { if (domain && (strcmp(domain, dom->domain) != 0)) { + dom = dom->nextDomain; continue; } prefs_listener_s *listener = dom->listeners; while (listener) { - listener->prefsChanged(dom->domain); + + bool foundAlready = false; + for (unsigned long i = 0; i < idx; i++) { + if (alreadySynced[i] == (void *)listener->prefsChanged) { + LOG("ignoring already synced listener %p for domain %s", alreadySynced[i], dom->domain); + foundAlready = true; + break; + } + } + + if (!foundAlready) { + alreadySynced[idx++] = (void *)listener->prefsChanged; + assert(idx <= listenerCount); + listener->prefsChanged(dom->domain); + } listener = listener->nextListener; } @@ -404,6 +454,8 @@ void prefs_sync(const char *domain) { pthread_mutex_unlock(&prefsLock); } while (1); + + FREE(alreadySynced); } void prefs_shutdown(bool emulatorShuttingDown) { @@ -411,10 +463,10 @@ void prefs_shutdown(bool emulatorShuttingDown) { return; } - FREE(prefsFile); - pthread_mutex_lock(&prefsLock); + FREE(prefsFile); + prefs_domain_s *dom = domains; domains = NULL; @@ -433,6 +485,8 @@ void prefs_shutdown(bool emulatorShuttingDown) { FREE(dead); } + listenerCount = 0; + pthread_mutex_unlock(&prefsLock); } diff --git a/src/prefs.h b/src/prefs.h index ae2176b2..e1a6b963 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -23,27 +23,66 @@ #define PREF_DOMAIN_INTERFACE "interface" #define PREF_DOMAIN_JOYSTICK "joystick" #define PREF_DOMAIN_KEYBOARD "keyboard" +#define PREF_DOMAIN_TOUCHSCREEN "touchscreen" #define PREF_DOMAIN_VIDEO "video" #define PREF_DOMAIN_VM "vm" // audio -#define PREF_SPEAKER_VOLUME "speakerVolume" +#define PREF_MOCKINGBOARD_ENABLED "mbEnabled" #define PREF_MOCKINGBOARD_VOLUME "mbVolume" +#define PREF_SPEAKER_VOLUME "speakerVolume" +#define PREF_AUDIO_LATENCY "audioLatency" + +// interface +#define PREF_DEVICE_HEIGHT "deviceHeight" +#define PREF_DEVICE_WIDTH "deviceWidth" +#define PREF_DEVICE_LANDSCAPE "landscapeEnabled" +#define PREF_DISK_PATH "diskPath" +#define PREF_DISK_ANIMATIONS_ENABLED "diskAnimationsEnabled" + +// joystick +#define PREF_JOYSTICK_KPAD_AUTO_RECENTER "kpAutoRecenter" +#define PREF_JOYSTICK_KPAD_STEP "kpStep" +#define PREF_JOYSTICK_MODE "joystickMode" +// joystick (touchscreen) +#define PREF_AXIS_ON_LEFT "axisIsOnLeft" +#define PREF_AXIS_SENSITIVITY "axisSensitivity" +#define PREF_JOY_SWIPE_NORTH_CHAR "jsSwipeNorthChar" +#define PREF_JOY_SWIPE_SOUTH_CHAR "jsSwipeSouthChar" +#define PREF_JOY_TAP_DELAY "jsTapDelaySecs" +#define PREF_JOY_TOUCHDOWN_CHAR "jsTouchDownChar" +#define PREF_KPAD_REPEAT_THRESH "keyRepeatThresholdSecs" +#define PREF_KPAD_ROSETTE_CHAR_ARRAY "kpAxisRosetteChars" +#define PREF_KPAD_ROSETTE_SCAN_ARRAY "kpAxisRosetteScancodes" +#define PREF_KPAD_SWIPE_NORTH_CHAR "kpSwipeNorthChar" +#define PREF_KPAD_SWIPE_NORTH_SCAN "kpSwipeNorthScancode" +#define PREF_KPAD_SWIPE_SOUTH_CHAR "kpSwipeSouthChar" +#define PREF_KPAD_SWIPE_SOUTH_SCAN "kpSwipeSouthScancode" +#define PREF_KPAD_TOUCHDOWN_CHAR "kpTouchDownChar" +#define PREF_KPAD_TOUCHDOWN_SCAN "kpTouchDownScancode" +#define PREF_SCREEN_DIVISION "screenDivider" +#define PREF_SHOW_CONTROLS "showControls" +#define PREF_SHOW_AZIMUTH "showAzimuth" +#define PREF_SWITCH_THRESHOLD "switchThreshold" + +// keyboard +#define PREF_GLYPH_MULTIPLIER "glyphMultiplier" +#define PREF_KEYBOARD_CAPS "caps" +#define PREF_KEYBOARD_ALT_PATH "altPath" +#define PREF_LOWERCASE_ENABLED "lowercaseEnabled" +#define PREF_PORTRAIT_HEIGHT_SCALE "portraitHeightScale" +#define PREF_PORTRAIT_POSITION_SCALE "portraitPositionScale" + +// touchscreen +#define PREF_CALIBRATING "isCalibrating" +#define PREF_MAX_ALPHA "maxAlpha" +#define PREF_MIN_ALPHA "minAlpha" +#define PREF_SCREEN_OWNER "screenOwner" +#define PREF_TOUCHMENU_ENABLED "touchMenuEnabled" // video #define PREF_COLOR_MODE "colorMode" -// joystick -#define PREF_JOYSTICK_MODE "joystickMode" -#define PREF_JOYSTICK_KPAD_AUTO_RECENTER "kpAutoRecenter" -#define PREF_JOYSTICK_KPAD_STEP "kpStep" - -// keyboard -#define PREF_KEYBOARD_CAPS "caps" - -// interface -#define PREF_DISK_PATH "diskPath" - // vm #define PREF_CPU_SCALE "cpuScale" #define PREF_CPU_SCALE_ALT "cpuScaleAlt" @@ -62,6 +101,9 @@ extern bool prefs_save(void); // copy string value for key in prefs domain, returns true upon success and strdup()'d value in *val extern bool prefs_copyStringValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT char ** _NONNULL val); +// create JSON_ref value for key in prefs domain, returns true upon success and newly allocated value in jsonVal +extern bool prefs_copyJSONValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT JSON_ref *jsonVal); + // get long value for key in prefs domain, returns true upon success extern bool prefs_parseLongValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT long *val, const long base); diff --git a/src/test/testprefs.c b/src/test/testprefs.c index 3ba5861f..2890fb6d 100644 --- a/src/test/testprefs.c +++ b/src/test/testprefs.c @@ -35,7 +35,8 @@ static const char *get_default_preferences(void) { " \"joystick\" : {" " \"joystickMode\" : 1," " \"pcJoystickParms\" : \"128 128 255 1 255 1\"," - " \"kpJoystickParms\" : \"8 1\"" + " \"kpJoystickParms\" : \"8 1\"," + " \"kpAxisRosetteChars\" : [ 121, 255, 122, 255, 123, 255, 124, 255, 125 ]" " }," " \"keyboard\" : {" " \"caps\" : true" @@ -1729,9 +1730,32 @@ TEST test_json_array() { PASS(); } +TEST test_json_unescaping() { + JSON_ref jsonRef = NULL; + int errCount = json_createFromString("\"http:\\/\\/deadc0de.org\\/apple2ix\\/android\\/\"", &jsonRef); + ASSERT(errCount == 1); + ASSERT(jsonRef); + + JSON_s parsedData = *((JSON_s *)jsonRef); + ASSERT(strcmp(parsedData.jsonString, "\"http:\\/\\/deadc0de.org\\/apple2ix\\/android\\/\"") == 0); + ASSERT(parsedData.jsonLen == strlen(parsedData.jsonString)); + + bool ok = json_unescapeSlashes(&parsedData.jsonString); + ASSERT(ok); + ASSERT(strcmp(parsedData.jsonString, "\"http://deadc0de.org/apple2ix/android/\"") == 0); + size_t newLen = strlen(parsedData.jsonString); + ASSERT(parsedData.jsonLen != newLen); + ASSERT(newLen == 39); + + json_destroy(&jsonRef); + + PASS(); +} + TEST test_prefs_loadString_1() { const char *prefsJSON = get_default_preferences(); prefs_loadString(prefsJSON); + prefs_sync(NULL); char *val = NULL; bool bVal = false; @@ -1766,6 +1790,44 @@ TEST test_prefs_loadString_1() { ASSERT(strcmp(val, "8 1") == 0); FREE(val); + JSON_ref jsonVal = NULL; + ok = prefs_copyJSONValue(PREF_DOMAIN_JOYSTICK, PREF_KPAD_ROSETTE_CHAR_ARRAY, &jsonVal); + ASSERT(ok); + ASSERT(json_isArray(jsonVal)); + ok = json_arrayCount(jsonVal, &lVal); + ASSERT(ok); + ok = json_arrayParseLongValueAtIndex(jsonVal, 0, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 121); // 121, 255, 122, 255, 123, 255, 124, 255, 125 + ok = json_arrayParseLongValueAtIndex(jsonVal, 1, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 255); + ok = json_arrayParseLongValueAtIndex(jsonVal, 2, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 122); + ok = json_arrayParseLongValueAtIndex(jsonVal, 3, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 255); + ok = json_arrayParseLongValueAtIndex(jsonVal, 4, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 123); + ok = json_arrayParseLongValueAtIndex(jsonVal, 5, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 255); + ok = json_arrayParseLongValueAtIndex(jsonVal, 6, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 124); + ok = json_arrayParseLongValueAtIndex(jsonVal, 7, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 255); + ok = json_arrayParseLongValueAtIndex(jsonVal, 8, &lVal, /*base:*/10); + ASSERT(ok); + ASSERT(lVal == 125); + ok = json_arrayParseLongValueAtIndex(jsonVal, 9, &lVal, /*base:*/10); + ASSERT(!ok); + ASSERT(lVal == 125); + json_destroy(&jsonVal); + ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, &bVal); ASSERT(ok); ASSERT(bVal == true); @@ -1789,6 +1851,7 @@ TEST test_prefs_loadString_1() { TEST test_prefs_set_props() { const char *prefsJSON = get_default_preferences(); prefs_loadString(prefsJSON); + prefs_sync(NULL); char *val = NULL; bool bVal = true; @@ -1830,6 +1893,7 @@ TEST test_prefs_load_and_save() { unlink(TEST_JSON); putenv("APPLE2IX_JSON=" TEST_JSON); prefs_load(); + prefs_sync(NULL); prefs_save(); bool ok = false; @@ -1958,6 +2022,8 @@ GREATEST_SUITE(test_suite_prefs) { RUN_TESTp(test_json_map_mutation_1); RUN_TESTp(test_json_array); + RUN_TESTp(test_json_unescaping); + RUN_TESTp(test_prefs_loadString_1); RUN_TESTp(test_prefs_set_props); RUN_TESTp(test_prefs_load_and_save); diff --git a/src/timing.c b/src/timing.c index 761a8393..53d11dba 100644 --- a/src/timing.c +++ b/src/timing.c @@ -188,7 +188,9 @@ void timing_toggleCPUSpeed(void) { void timing_reinitializeAudio(void) { SPINLOCK_ACQUIRE(&_pause_spinLock); assert(pthread_self() != cpu_thread_id); +#if !TESTING assert(cpu_isPaused()); +#endif emul_reinitialize_audio = true; emul_pause_audio = false; emul_resume_audio = false; @@ -589,16 +591,49 @@ void timing_testCyclesCountOverflow(void) { // ---------------------------------------------------------------------------- static void vm_prefsChanged(const char *domain) { - assert(strcmp(domain, PREF_DOMAIN_VM) == 0); - float fVal = 1.0; - prefs_parseFloatValue(domain, PREF_CPU_SCALE, &fVal); - cpu_scale_factor = fVal; - prefs_parseFloatValue(domain, PREF_CPU_SCALE_ALT, &fVal); - cpu_altscale_factor = fVal; + + if (strcmp(domain, PREF_DOMAIN_VM) == 0) { + cpu_scale_factor = prefs_parseFloatValue(domain, PREF_CPU_SCALE, &fVal) ? fVal / 100.f : 1.f; + if (cpu_scale_factor < CPU_SCALE_SLOWEST) { + cpu_scale_factor = CPU_SCALE_SLOWEST; + } + if (cpu_scale_factor > CPU_SCALE_FASTEST) { + cpu_scale_factor = CPU_SCALE_FASTEST; + } + cpu_altscale_factor = prefs_parseFloatValue(domain, PREF_CPU_SCALE_ALT, &fVal) ? fVal / 100.f : 1.f; + if (cpu_altscale_factor < CPU_SCALE_SLOWEST) { + cpu_altscale_factor = CPU_SCALE_SLOWEST; + } + if (cpu_altscale_factor > CPU_SCALE_FASTEST) { + cpu_altscale_factor = CPU_SCALE_FASTEST; + } +#ifdef AUDIO_ENABLED + } else if (strcmp(domain, PREF_DOMAIN_AUDIO) == 0) { + + static float audioLatency = 0.f; + float latency = prefs_parseFloatValue(domain, PREF_AUDIO_LATENCY, &fVal) ? fVal : 0.25f; +#define SMALL_EPSILON (1.f/1024.f) + if (fabsf(audioLatency - latency) > SMALL_EPSILON) { + audioLatency = latency; + audio_setLatency(latency); + timing_reinitializeAudio(); + } + + static bool mbEnabled = false; + bool bVal = false; + bool enabled = prefs_parseBoolValue(domain, PREF_MOCKINGBOARD_ENABLED, &bVal) ? bVal : true; + if (enabled != mbEnabled) { + mbEnabled = enabled; + MB_SetEnabled(enabled); + timing_reinitializeAudio(); + } + } +#endif } static __attribute__((constructor)) void _init_vm(void) { prefs_registerListener(PREF_DOMAIN_VM, &vm_prefsChanged); + prefs_registerListener(PREF_DOMAIN_AUDIO, &vm_prefsChanged); } diff --git a/src/video/glalert.c b/src/video/glalert.c index 4cf8773b..e517089a 100644 --- a/src/video/glalert.c +++ b/src/video/glalert.c @@ -25,6 +25,9 @@ static unsigned int nextMessageRows = 0; static struct timespec messageTimingBegin = { 0 }; static GLModel *messageModel = NULL; static GLfloat landscapeScale = 1.f; +static bool prefsChanged = true; + +static void alert_applyPrefs(void); // ---------------------------------------------------------------------------- @@ -147,6 +150,9 @@ static void _alertToModel(char *message, unsigned int messageCols, unsigned int static void alert_init(void) { // no-op + if (prefsChanged) { + alert_applyPrefs(); + } } static void alert_shutdown(void) { @@ -173,6 +179,10 @@ static void alert_render(void) { return; } + if (prefsChanged) { + alert_applyPrefs(); + } + struct timespec now = { 0 }; clock_gettime(CLOCK_MONOTONIC, &now); @@ -202,6 +212,7 @@ static void alert_render(void) { } static void alert_reshape(int w, int h, bool landscape) { + assert(video_isRenderThread()); swizzleDimensions(&w, &h, landscape); landscapeScale = landscape ? 1.f : ((GLfloat)w/h); } @@ -269,13 +280,13 @@ static void _animation_showCPUSpeed(void) { char buf[8] = { 0 }; double scale = (alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); - int percentScale = scale * 100; - if (percentScale < 100.0) { + int percentScale = roundf(scale * 100); + if (percentScale < 100) { snprintf(buf, 3, "%d", percentScale); cpuTemplate[0][1] = ' '; cpuTemplate[0][2] = buf[0]; cpuTemplate[0][3] = buf[1]; - } else if (scale == CPU_SCALE_FASTEST) { + } else if (scale >= CPU_SCALE_FASTEST) { cpuTemplate[0][1] = 'm'; cpuTemplate[0][2] = 'a'; cpuTemplate[0][3] = 'x'; @@ -361,12 +372,29 @@ static void _animation_showTrackSector(int drive, int track, int sect) { _animation_showMessage(template, DISK_TRACK_SECTOR_COLS, DISK_TRACK_SECTOR_ROWS); } -static void _animation_setEnableShowTrackSector(bool enabled) { +static void alert_applyPrefs(void) { + assert(video_isRenderThread()); + + prefsChanged = false; + + bool bVal = false; + bool enabled = prefs_parseBoolValue(PREF_DOMAIN_INTERFACE, PREF_DISK_ANIMATIONS_ENABLED, &bVal) ? bVal : true; if (enabled) { video_animations->animation_showTrackSector = &_animation_showTrackSector; } else { video_animations->animation_showTrackSector = NULL; } + + long lVal = 0; + long width = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, &lVal, 10) ? lVal : (long)(SCANWIDTH*1.5); + long height = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, &lVal, 10) ? lVal : (long)(SCANHEIGHT*1.5); + bool isLandscape = prefs_parseBoolValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, &bVal) ? bVal : true; + + alert_reshape(width, height, isLandscape); +} + +static void alert_prefsChanged(const char *domain) { + prefsChanged = true; } static void _init_glalert(void) { @@ -377,19 +405,18 @@ static void _init_glalert(void) { video_animations->animation_showCPUSpeed = &_animation_showCPUSpeed; video_animations->animation_showDiskChosen = &_animation_showDiskChosen; video_animations->animation_showTrackSector = &_animation_showTrackSector; - video_animations->animation_setEnableShowTrackSector = &_animation_setEnableShowTrackSector; glnode_registerNode(RENDER_MIDDLE, (GLNode){ .setup = &alert_init, .shutdown = &alert_shutdown, .render = &alert_render, - .reshape = &alert_reshape, #if INTERFACE_TOUCH .type = TOUCH_DEVICE_ALERT, .onTouchEvent = &alert_onTouchEvent, - .setData = NULL, #endif }); + + prefs_registerListener(PREF_DOMAIN_INTERFACE, &alert_prefsChanged); } static __attribute__((constructor)) void __init_glalert(void) { diff --git a/src/video/glnode.c b/src/video/glnode.c index fbf33c4e..3c78d76b 100644 --- a/src/video/glnode.c +++ b/src/video/glnode.c @@ -116,7 +116,10 @@ static void _glnode_initGLUTPre(void) { } static void _glnode_reshapeGLUT(int w, int h) { - video_reshape(w, h, /*landscape:*/true); + prefs_setLongValue(PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, w); + prefs_setLongValue(PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, h); + prefs_setLongValue(PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, true); + prefs_sync(PREF_DOMAIN_INTERFACE); } static void _glnode_initGLUTPost(void) { @@ -205,14 +208,6 @@ static void glnode_renderNodes(void) { #endif } -static void glnode_reshapeNodes(int w, int h, bool landscape) { - glnode_array_node_s *p = head; - while (p) { - p->node.reshape(w, h, landscape); - p = p->next; - } -} - #if INTERFACE_TOUCH static int64_t glnode_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { SCOPE_TRACE_TOUCH("glnode onTouchEvent"); @@ -227,17 +222,6 @@ static int64_t glnode_onTouchEvent(interface_touch_event_t action, int pointer_c } return flags; } - -void (*glnode_getModelDataSetter(interface_device_t type))(const char *jsonData) { - glnode_array_node_s *p = head; - while (p) { - if (p->node.type == type) { - return p->node.setData; - } - p = p->next; - } - return NULL; -} #endif static void glnode_mainLoop(void) { @@ -260,7 +244,6 @@ static void _init_glnode_manager(void) { glnode_backend.init = &glnode_setupNodes; glnode_backend.main_loop = &glnode_mainLoop; - glnode_backend.reshape = &glnode_reshapeNodes; glnode_backend.render = &glnode_renderNodes; glnode_backend.shutdown = &glnode_shutdownNodes; @@ -269,7 +252,6 @@ static void _init_glnode_manager(void) { #if INTERFACE_TOUCH interface_onTouchEvent = &glnode_onTouchEvent; - interface_getModelDataSetter = &glnode_getModelDataSetter; #endif } diff --git a/src/video/glnode.h b/src/video/glnode.h index b0ba1e36..3d1da832 100644 --- a/src/video/glnode.h +++ b/src/video/glnode.h @@ -100,11 +100,9 @@ typedef struct GLNode { void (*setup)(void); void (*shutdown)(void); void (*render)(void); - void (*reshape)(int w, int h, bool landscape); #if INTERFACE_TOUCH interface_device_t type; int64_t (*onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords); - void (*setData)(const char *jsonData); #endif } GLNode; diff --git a/src/video/gltouchjoy.c b/src/video/gltouchjoy.c index 564b016b..0e6123a2 100644 --- a/src/video/gltouchjoy.c +++ b/src/video/gltouchjoy.c @@ -68,9 +68,6 @@ static struct { int buttonY; int buttonYMax; - // Are we in landscape mode - bool landscape; - // TODO FIXME : support 2-players! } touchport = { 0 }; @@ -83,6 +80,9 @@ static struct { AZIMUTH_CLASS(GLModelJoystickAzimuth); +static void gltouchjoy_applyPrefs(void); +static void gltouchjoy_shutdown(void); + // ---------------------------------------------------------------------------- // joystick azimuth model @@ -266,9 +266,10 @@ static void _setup_axis_hud(GLModel *parent) { const unsigned int row = (AXIS_TEMPLATE_COLS+1); + uint8_t *rosetteChars = variant.curr->rosetteChars(); for (unsigned int i=0; itpl)+(row*i))[j] = axes.rosetteChars[(i*ROSETTE_ROWS)+j]; + ((hudElement->tpl)+(row*i))[j] = rosetteChars[(i*ROSETTE_ROWS)+j]; } } @@ -350,13 +351,11 @@ static inline void resetState() { static void gltouchjoy_setup(void) { LOG("..."); - resetState(); + gltouchjoy_shutdown(); - mdlDestroyModel(&axes.model); - mdlDestroyModel(&axes.azimuthModel); - mdlDestroyModel(&buttons.model); - - joyglobals.isShuttingDown = false; + if (joyglobals.prefsChanged) { + gltouchjoy_applyPrefs(); + } // axis origin object @@ -418,6 +417,12 @@ static void gltouchjoy_setup(void) { } // button object + long lVal = 0; + if (variant.curr->variant() == TOUCH_DEVICE_JOYSTICK_KEYPAD) { + buttons.activeChar = prefs_parseLongValue(PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_SPACE_VISUAL; + } else { + buttons.activeChar = prefs_parseLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOY_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : MOUSETEXT_OPENAPPLE; + } buttons.model = mdlCreateQuad((GLModelParams_s){ .skew_x = 1.05-BUTTON_OBJ_W, @@ -451,6 +456,10 @@ static void gltouchjoy_setup(void) { buttons.timingBegin = now; joyglobals.isAvailable = true; + + if (joyglobals.ownsScreen) { + video_animations->animation_showTouchJoystick(); + } } static void gltouchjoy_shutdown(void) { @@ -462,7 +471,6 @@ static void gltouchjoy_shutdown(void) { resetState(); joyglobals.isAvailable = false; - joyglobals.isShuttingDown = true; variant.joys->shutdown(); variant.kpad->shutdown(); @@ -476,8 +484,8 @@ static void gltouchjoy_render(void) { if (!joyglobals.isAvailable) { return; } - if (!joyglobals.isEnabled) { - return; + if (UNLIKELY(joyglobals.prefsChanged)) { + gltouchjoy_setup(); // fully set up again on prefs change } if (!joyglobals.ownsScreen) { return; @@ -550,8 +558,8 @@ static void gltouchjoy_render(void) { static void gltouchjoy_reshape(int w, int h, bool landscape) { LOG("w:%d h:%d landscape:%d", w, h, landscape); + assert(video_isRenderThread()); - touchport.landscape = landscape; swizzleDimensions(&w, &h, landscape); touchport.width = w; touchport.height = h; @@ -703,7 +711,7 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point if (!joyglobals.isAvailable) { return 0x0LL; } - if (!joyglobals.isEnabled) { + if (UNLIKELY(joyglobals.prefsChanged)) { return 0x0LL; } if (!joyglobals.ownsScreen) { @@ -804,36 +812,7 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point return TOUCH_FLAGS_HANDLED | TOUCH_FLAGS_JOY; } -static bool gltouchjoy_isTouchJoystickAvailable(void) { - return joyglobals.isAvailable; -} - -static void gltouchjoy_setTouchJoystickEnabled(bool enabled) { - joyglobals.isEnabled = enabled; -} - -static void gltouchjoy_setTouchJoystickOwnsScreen(bool pwnd) { - joyglobals.ownsScreen = pwnd; - resetState(); - if (joyglobals.ownsScreen) { - caps_lock = true; // HACK FOR NOW : force uppercase scancodes for touchjoy_kpad variant - joyglobals.minAlpha = joyglobals.minAlphaWhenOwnsScreen; - } else { - joyglobals.minAlpha = 0.0; - } -} - -static bool gltouchjoy_ownsScreen(void) { - return joyglobals.ownsScreen; -} - -static void gltouchjoy_setShowControls(bool showControls) { - joyglobals.showControls = showControls; -} - -static void gltouchjoy_setShowAzimuth(bool showAzimuth) { - joyglobals.showAzimuth = showAzimuth; -} +// ---------------------------------------------------------------------------- static void _animation_showTouchJoystick(void) { if (!joyglobals.isAvailable) { @@ -864,95 +843,48 @@ static void _animation_hideTouchJoystick(void) { buttons.timingBegin = (struct timespec){ 0 }; } -static void gltouchjoy_setTouchButtonTypes( - touchjoy_button_type_t touchDownChar, int touchDownScancode, - touchjoy_button_type_t northChar, int northScancode, - touchjoy_button_type_t southChar, int southScancode) -{ - buttons.touchDownChar = touchDownChar; - buttons.touchDownScancode = touchDownScancode; +static void gltouchjoy_applyPrefs(void) { + assert(video_isRenderThread()); - buttons.southChar = southChar; - buttons.southScancode = southScancode; + joyglobals.prefsChanged = false; - buttons.northChar = northChar; - buttons.northScancode = northScancode; + bool bVal = false; + float fVal = 0.f; + long lVal = 0; - buttons.activeChar = ICONTEXT_NONACTIONABLE; - - char currButtonDisplayChar = touchDownChar; - if (touchDownChar == TOUCH_BUTTON0) { - currButtonDisplayChar = MOUSETEXT_OPENAPPLE; - } else if (touchDownChar == TOUCH_BUTTON1) { - currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE; - } else if (touchDownChar == TOUCH_BOTH) { - currButtonDisplayChar = ICONTEXT_MENU_TOUCHJOY; - } else if (touchDownScancode < 0) { - currButtonDisplayChar = ' '; - } - _setup_button_object_with_char(currButtonDisplayChar); -} - -static void gltouchjoy_setTouchAxisSensitivity(float multiplier) { - axes.multiplier = multiplier; -} - -static void gltouchjoy_setButtonSwitchThreshold(int delta) { - joyglobals.switchThreshold = delta; -} - -static void gltouchjoy_setTouchVariant(touchjoy_variant_t variantType) { - resetState(); - - switch (variantType) { - case EMULATED_JOYSTICK: - variant.curr = variant.joys; - break; - - case EMULATED_KEYPAD: - variant.curr = variant.kpad; - break; - - default: - assert(false && "touch variant set to invalid"); - break; - } + const interface_device_t screenOwner = prefs_parseLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, &lVal, /*base:*/10) ? lVal : TOUCH_DEVICE_NONE; + joyglobals.ownsScreen = (screenOwner == TOUCH_DEVICE_JOYSTICK || screenOwner == TOUCH_DEVICE_JOYSTICK_KEYPAD); resetState(); + if (joyglobals.ownsScreen) { + caps_lock = true; // HACK FOR NOW : force uppercase scancodes for touchjoy_kpad variant + joyglobals.minAlpha = joyglobals.minAlphaWhenOwnsScreen; + variant.curr = (screenOwner == TOUCH_DEVICE_JOYSTICK) ? variant.joys : variant.kpad; + } else { + joyglobals.minAlpha = 0.0; + } + + joyglobals.showControls = prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_SHOW_CONTROLS, &bVal) ? bVal : true; + joyglobals.showAzimuth = prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_SHOW_AZIMUTH, &bVal) ? bVal : true; + joyglobals.switchThreshold = prefs_parseLongValue (PREF_DOMAIN_JOYSTICK, PREF_SWITCH_THRESHOLD, &lVal, 10) ? lVal : BUTTON_SWITCH_THRESHOLD_DEFAULT; + joyglobals.screenDivider = prefs_parseFloatValue(PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, &fVal) ? fVal : 0.5f; + joyglobals.axisIsOnLeft = prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_AXIS_ON_LEFT, &bVal) ? bVal : true; + axes.multiplier = prefs_parseFloatValue(PREF_DOMAIN_JOYSTICK, PREF_AXIS_SENSITIVITY, &fVal) ? fVal : 1.f; + + joyglobals.isCalibrating = prefs_parseBoolValue (PREF_DOMAIN_TOUCHSCREEN, PREF_CALIBRATING, &bVal) ? bVal : false; + + variant.joys->prefsChanged(PREF_DOMAIN_JOYSTICK); + variant.kpad->prefsChanged(PREF_DOMAIN_JOYSTICK); + + long width = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, &lVal, 10) ? lVal : (long)(SCANWIDTH*1.5); + long height = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, &lVal, 10) ? lVal : (long)(SCANHEIGHT*1.5); + bool isLandscape = prefs_parseBoolValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, &bVal) ? bVal : true; + + gltouchjoy_reshape(width, height, isLandscape); } -static touchjoy_variant_t gltouchjoy_getTouchVariant(void) { - return variant.curr->variant(); -} - -static void gltouchjoy_setTouchAxisTypes(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) { - memcpy(axes.rosetteChars, rosetteChars, sizeof(uint8_t)*(ROSETTE_ROWS * ROSETTE_COLS)); - memcpy(axes.rosetteScancodes, rosetteScancodes, sizeof(int) *(ROSETTE_ROWS * ROSETTE_COLS)); - _setup_axis_hud(axes.model); -} - -static void gltouchjoy_setScreenDivision(float screenDivider) { - joyglobals.screenDivider = screenDivider; - // force reshape here to apply changes ... - gltouchjoy_reshape(touchport.width, touchport.height, touchport.landscape); -} - -static void gltouchjoy_setAxisOnLeft(bool axisIsOnLeft) { - joyglobals.axisIsOnLeft = axisIsOnLeft; - // force reshape here to apply changes ... - gltouchjoy_reshape(touchport.width, touchport.height, touchport.landscape); -} - -static void gltouchjoy_beginCalibration(void) { - joyglobals.isCalibrating = true; -} - -static void gltouchjoy_endCalibration(void) { - joyglobals.isCalibrating = false; -} - -static bool gltouchjoy_isCalibrating(void) { - return joyglobals.isCalibrating; +static void gltouchjoy_prefsChanged(const char *domain) { + joyglobals.prefsChanged = true; } static void _init_gltouchjoy(void) { @@ -960,97 +892,43 @@ static void _init_gltouchjoy(void) { axes.centerX = 240; axes.centerY = 160; - axes.multiplier = 1.f; axes.trackingIndex = TRACKING_NONE; - axes.rosetteChars[0] = ' '; - axes.rosetteScancodes[0] = -1; - axes.rosetteChars[1] = MOUSETEXT_UP; - axes.rosetteScancodes[1] = -1; - axes.rosetteChars[2] = ' '; - axes.rosetteScancodes[2] = -1; - - axes.rosetteChars[3] = MOUSETEXT_LEFT; - axes.rosetteScancodes[3] = -1; - axes.rosetteChars[4] = ICONTEXT_MENU_TOUCHJOY; - axes.rosetteScancodes[4] = -1; - axes.rosetteChars[5] = MOUSETEXT_RIGHT; - axes.rosetteScancodes[5] = -1; - - axes.rosetteChars[6] = ' '; - axes.rosetteScancodes[6] = -1; - axes.rosetteChars[7] = MOUSETEXT_DOWN; - axes.rosetteScancodes[7] = -1; - axes.rosetteChars[8] = ' '; - axes.rosetteScancodes[8] = -1; - buttons.centerX = 240; buttons.centerY = 160; buttons.trackingIndex = TRACKING_NONE; - - buttons.touchDownChar = TOUCH_BUTTON0; - buttons.touchDownScancode = -1; - - buttons.southChar = TOUCH_BUTTON1; - buttons.southScancode = -1; - - buttons.northChar = TOUCH_BOTH; - buttons.northScancode = -1; - buttons.activeChar = MOUSETEXT_OPENAPPLE; - joyglobals.isEnabled = true; - joyglobals.ownsScreen = true; - joyglobals.showControls = true; - joyglobals.showAzimuth = true; - joyglobals.screenDivider = 0.5f; - joyglobals.axisIsOnLeft = true; - joyglobals.switchThreshold = BUTTON_SWITCH_THRESHOLD_DEFAULT; + joyglobals.prefsChanged = true; // force reload preferences/defaults video_animations->animation_showTouchJoystick = &_animation_showTouchJoystick; video_animations->animation_hideTouchJoystick = &_animation_hideTouchJoystick; - joydriver_isTouchJoystickAvailable = &gltouchjoy_isTouchJoystickAvailable; - joydriver_setTouchJoystickEnabled = &gltouchjoy_setTouchJoystickEnabled; - joydriver_setTouchJoystickOwnsScreen = &gltouchjoy_setTouchJoystickOwnsScreen; - joydriver_ownsScreen = &gltouchjoy_ownsScreen; - joydriver_setShowControls = &gltouchjoy_setShowControls; - joydriver_setShowAzimuth = &gltouchjoy_setShowAzimuth; - joydriver_setTouchButtonTypes = &gltouchjoy_setTouchButtonTypes; - joydriver_setTouchAxisSensitivity = &gltouchjoy_setTouchAxisSensitivity; - joydriver_setButtonSwitchThreshold = &gltouchjoy_setButtonSwitchThreshold; - joydriver_setTouchVariant = &gltouchjoy_setTouchVariant; - joydriver_getTouchVariant = &gltouchjoy_getTouchVariant; - joydriver_setTouchAxisTypes = &gltouchjoy_setTouchAxisTypes; - joydriver_setScreenDivision = &gltouchjoy_setScreenDivision; - joydriver_setAxisOnLeft = &gltouchjoy_setAxisOnLeft; - joydriver_beginCalibration = &gltouchjoy_beginCalibration; - joydriver_endCalibration = &gltouchjoy_endCalibration; - joydriver_isCalibrating = &gltouchjoy_isCalibrating; - glnode_registerNode(RENDER_LOW, (GLNode){ .type = TOUCH_DEVICE_JOYSTICK, .setup = &gltouchjoy_setup, .shutdown = &gltouchjoy_shutdown, .render = &gltouchjoy_render, - .reshape = &gltouchjoy_reshape, .onTouchEvent = &gltouchjoy_onTouchEvent, - .setData = NULL, }); + + prefs_registerListener(PREF_DOMAIN_JOYSTICK, &gltouchjoy_prefsChanged); + prefs_registerListener(PREF_DOMAIN_TOUCHSCREEN, &gltouchjoy_prefsChanged); + prefs_registerListener(PREF_DOMAIN_INTERFACE, &gltouchjoy_prefsChanged); } static __attribute__((constructor)) void __init_gltouchjoy(void) { emulator_registerStartupCallback(CTOR_PRIORITY_LATE, &_init_gltouchjoy); } -void gltouchjoy_registerVariant(touchjoy_variant_t variantType, GLTouchJoyVariant *touchJoyVariant) { +void gltouchjoy_registerVariant(interface_device_t variantType, GLTouchJoyVariant *touchJoyVariant) { switch (variantType) { - case EMULATED_JOYSTICK: + case TOUCH_DEVICE_JOYSTICK: variant.joys = touchJoyVariant; - variant.curr = variant.kpad; + variant.curr = variant.joys; break; - case EMULATED_KEYPAD: + case TOUCH_DEVICE_JOYSTICK_KEYPAD: variant.kpad = touchJoyVariant; variant.curr = variant.kpad; break; diff --git a/src/video/gltouchjoy.h b/src/video/gltouchjoy.h index 9e1a79bc..3f255c26 100644 --- a/src/video/gltouchjoy.h +++ b/src/video/gltouchjoy.h @@ -22,14 +22,28 @@ # define TOUCH_JOY_LOG(...) #endif +#define ROSETTE_ROWS 3 +#define ROSETTE_COLS 3 + +enum { + ROSETTE_NORTHWEST=0, + ROSETTE_NORTH, + ROSETTE_NORTHEAST, + ROSETTE_WEST, + ROSETTE_CENTER, + ROSETTE_EAST, + ROSETTE_SOUTHWEST, + ROSETTE_SOUTH, + ROSETTE_SOUTHEAST, +}; + // globals typedef struct GLTouchJoyGlobals { + bool prefsChanged; bool isAvailable; // Were there any OpenGL/memory errors on gltouchjoy initialization? - bool isShuttingDown; bool isCalibrating; // Are we running in calibration mode? - bool isEnabled; // Does player want touchjoy enabled? bool ownsScreen; // Does the touchjoy currently own the screen? bool showControls; // Are controls visible? bool showAzimuth; // Is joystick azimuth shown? @@ -37,7 +51,6 @@ typedef struct GLTouchJoyGlobals { float minAlpha; float screenDivider; bool axisIsOnLeft; - int switchThreshold; } GLTouchJoyGlobals; @@ -54,9 +67,6 @@ typedef struct GLTouchJoyAxes { // azimuth model GLModel *azimuthModel; - uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS]; - int rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS]; - int centerX; int centerY; float multiplier; @@ -73,13 +83,6 @@ typedef struct GLTouchJoyButtons { uint8_t activeChar; bool modelDirty; - touchjoy_button_type_t touchDownChar; - int touchDownScancode; - touchjoy_button_type_t northChar; - int northScancode; - touchjoy_button_type_t southChar; - int southScancode; - int centerX; int centerY; int trackingIndex; @@ -89,11 +92,13 @@ typedef struct GLTouchJoyButtons { extern GLTouchJoyButtons buttons; typedef struct GLTouchJoyVariant { - touchjoy_variant_t (*variant)(void); + interface_device_t (*variant)(void); void (*resetState)(void); void (*setup)(void (*buttonDrawCallback)(char newChar)); void (*shutdown)(void); + void (*prefsChanged)(const char *domain); + void (*buttonDown)(void); void (*buttonMove)(int dx, int dy); void (*buttonUp)(int dx, int dy); @@ -102,9 +107,11 @@ typedef struct GLTouchJoyVariant { void (*axisMove)(int dx, int dy); void (*axisUp)(int dx, int dy); + uint8_t *(*rosetteChars)(void); + } GLTouchJoyVariant; // registers a touch joystick variant with manager -void gltouchjoy_registerVariant(touchjoy_variant_t variant, GLTouchJoyVariant *touchJoyVariant); +void gltouchjoy_registerVariant(interface_device_t variant, GLTouchJoyVariant *touchJoyVariant); #endif // whole file diff --git a/src/video/gltouchjoy_joy.c b/src/video/gltouchjoy_joy.c index 42965086..0235294a 100644 --- a/src/video/gltouchjoy_joy.c +++ b/src/video/gltouchjoy_joy.c @@ -31,12 +31,16 @@ static struct { pthread_mutex_t tapDelayMutex; pthread_cond_t tapDelayCond; unsigned int tapDelayNanos; + + touchjoy_button_type_t touchDownChar; + touchjoy_button_type_t northChar; + touchjoy_button_type_t southChar; } joys; // ---------------------------------------------------------------------------- -static touchjoy_variant_t touchjoy_variant(void) { - return EMULATED_JOYSTICK; +static interface_device_t touchjoy_variant(void) { + return TOUCH_DEVICE_JOYSTICK; } static inline void _reset_axis_state(void) { @@ -77,7 +81,7 @@ static void *_button_tap_delayed_thread(void *dummyptr) { uint8_t currJoyButtonValue1 = 0x0; uint8_t currButtonDisplayChar = ' '; for (;;) { - if (UNLIKELY(joyglobals.isShuttingDown)) { + if (UNLIKELY(!joyglobals.isAvailable)) { break; } @@ -130,6 +134,10 @@ static void *_button_tap_delayed_thread(void *dummyptr) { pthread_mutex_unlock(&joys.tapDelayMutex); + joys.tapDelayThreadId = 0; + joys.tapDelayMutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + LOG(">>> [DELAYEDTAP] thread exit ..."); return NULL; @@ -146,9 +154,6 @@ static void touchjoy_shutdown(void) { if (pthread_join(joys.tapDelayThreadId, NULL)) { ERRLOG("OOPS: pthread_join tap delay thread ..."); } - joys.tapDelayThreadId = 0; - joys.tapDelayMutex = (pthread_mutex_t){ 0 }; - joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; } // ---------------------------------------------------------------------------- @@ -192,11 +197,11 @@ static void touchjoy_axisUp(int x, int y) { // button state static void _set_current_button_state(touchjoy_button_type_t theButtonChar) { - if (theButtonChar == TOUCH_BUTTON0) { + if (theButtonChar == TOUCH_BUTTON1) { joys.currJoyButtonValue0 = 0x80; joys.currJoyButtonValue1 = 0; joys.currButtonDisplayChar = MOUSETEXT_OPENAPPLE; - } else if (theButtonChar == TOUCH_BUTTON1) { + } else if (theButtonChar == TOUCH_BUTTON2) { joys.currJoyButtonValue0 = 0; joys.currJoyButtonValue1 = 0x80; joys.currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE; @@ -212,7 +217,7 @@ static void _set_current_button_state(touchjoy_button_type_t theButtonChar) { } static void touchjoy_buttonDown(void) { - _set_current_button_state(buttons.touchDownChar); + _set_current_button_state(joys.touchDownChar); joys.trackingButton = true; _signal_tap_delay(); } @@ -222,9 +227,9 @@ static void touchjoy_buttonMove(int dx, int dy) { touchjoy_button_type_t theButtonChar = -1; if (dy < 0) { - theButtonChar = buttons.northChar; + theButtonChar = joys.northChar; } else { - theButtonChar = buttons.southChar; + theButtonChar = joys.southChar; } _set_current_button_state(theButtonChar); @@ -239,19 +244,35 @@ static void touchjoy_buttonUp(int dx, int dy) { _signal_tap_delay(); } -static void gltouchjoy_setTapDelay(float secs) { - if (UNLIKELY(secs < 0.f)) { - ERRLOG("Clamping tap delay to 0.0 secs"); +static void touchjoy_prefsChanged(const char *domain) { + assert(video_isRenderThread()); + + long lVal = 0; + + joys.touchDownChar = prefs_parseLongValue(domain, PREF_JOY_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : TOUCH_BUTTON1; + joys.northChar = prefs_parseLongValue(domain, PREF_JOY_SWIPE_NORTH_CHAR, &lVal, /*base:*/10) ? lVal : TOUCH_BOTH; + joys.southChar = prefs_parseLongValue(domain, PREF_JOY_SWIPE_SOUTH_CHAR, &lVal, /*base:*/10) ? lVal : TOUCH_BUTTON2; + + float fVal = 0.f; + joys.tapDelayNanos = prefs_parseFloatValue(domain, PREF_JOY_TAP_DELAY, &fVal) ? (fVal * NANOSECONDS_PER_SECOND) : BUTTON_TAP_DELAY_NANOS_DEFAULT; +} + +static uint8_t *touchjoy_rosetteChars(void) { + static uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS] = { 0 }; + if (rosetteChars[0] == 0x0) { + rosetteChars[0] = ' '; + rosetteChars[1] = MOUSETEXT_UP; + rosetteChars[2] = ' '; + + rosetteChars[3] = MOUSETEXT_LEFT; + rosetteChars[4] = ICONTEXT_MENU_TOUCHJOY; + rosetteChars[5] = MOUSETEXT_RIGHT; + + rosetteChars[6] = ' '; + rosetteChars[7] = MOUSETEXT_DOWN; + rosetteChars[8] = ' '; } - if (UNLIKELY(secs > 1.f)) { - ERRLOG("Clamping tap delay to 1.0 secs"); - } - unsigned int tapDelayNanos = (unsigned int)((float)NANOSECONDS_PER_SECOND * secs); -#define MIN_WAIT_NANOS 1000000 - if (tapDelayNanos < MIN_WAIT_NANOS) { - tapDelayNanos = MIN_WAIT_NANOS; - } - joys.tapDelayNanos = tapDelayNanos; + return rosetteChars; } // ---------------------------------------------------------------------------- @@ -264,6 +285,8 @@ static void _init_gltouchjoy_joy(void) { happyHappyJoyJoy.setup = &touchjoy_setup, happyHappyJoyJoy.shutdown = &touchjoy_shutdown, + happyHappyJoyJoy.prefsChanged = &touchjoy_prefsChanged; + happyHappyJoyJoy.buttonDown = &touchjoy_buttonDown, happyHappyJoyJoy.buttonMove = &touchjoy_buttonMove, happyHappyJoyJoy.buttonUp = &touchjoy_buttonUp, @@ -272,13 +295,12 @@ static void _init_gltouchjoy_joy(void) { happyHappyJoyJoy.axisMove = &touchjoy_axisMove, happyHappyJoyJoy.axisUp = &touchjoy_axisUp, - joys.tapDelayMutex = (pthread_mutex_t){ 0 }; + happyHappyJoyJoy.rosetteChars = &touchjoy_rosetteChars; + + joys.tapDelayMutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; - joys.tapDelayNanos = BUTTON_TAP_DELAY_NANOS_DEFAULT; - joydriver_setTapDelay = &gltouchjoy_setTapDelay; - - gltouchjoy_registerVariant(EMULATED_JOYSTICK, &happyHappyJoyJoy); + gltouchjoy_registerVariant(TOUCH_DEVICE_JOYSTICK, &happyHappyJoyJoy); } static __attribute__((constructor)) void __init_gltouchjoy_joy(void) { diff --git a/src/video/gltouchjoy_kpad.c b/src/video/gltouchjoy_kpad.c index c9dfda1f..9de7db21 100644 --- a/src/video/gltouchjoy_kpad.c +++ b/src/video/gltouchjoy_kpad.c @@ -68,6 +68,18 @@ static struct { float repeatThresholdNanos; + // axis rosette + uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS]; + long rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS]; + + // touch/swipe buttons + touchjoy_button_type_t touchDownChar; + int touchDownScancode; + touchjoy_button_type_t northChar; + int northScancode; + touchjoy_button_type_t southChar; + int southScancode; + } kpad = { 0 }; static GLTouchJoyVariant kpadJoy = { 0 }; @@ -188,8 +200,8 @@ static void touchkpad_keyboardReadCallback(void) { // ---------------------------------------------------------------------------- -static touchjoy_variant_t touchkpad_variant(void) { - return EMULATED_KEYPAD; +static interface_device_t touchkpad_variant(void) { + return TOUCH_DEVICE_JOYSTICK_KEYPAD; } static void touchkpad_resetState(void) { @@ -212,13 +224,13 @@ static void touchkpad_resetState(void) { for (unsigned int i=0; i= 0) { - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER]; + if (kpad.rosetteScancodes[ROSETTE_CENTER] >= 0) { + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER]; kpad.scancodes[REPEAT_AXIS_ALT] = -1; } } @@ -257,7 +269,7 @@ static void touchkpad_axisMove(int dx, int dy) { TOUCH_JOY_LOG("..."); if ((dx > -joyglobals.switchThreshold) && (dx < joyglobals.switchThreshold) && (dy > -joyglobals.switchThreshold) && (dy < joyglobals.switchThreshold)) { - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER]; + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER]; kpad.scancodes[REPEAT_AXIS_ALT] = -1; return; } @@ -296,98 +308,98 @@ static void touchkpad_axisMove(int dx, int dy) { kpad.scancodes[REPEAT_AXIS_ALT] = -1; switch (kpad.axisCurrentOctant) { case OCTANT_NORTHWEST: - if (axes.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) { - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHWEST]; - TOUCH_JOY_LOG("XY : NORTHWEST, (%d)", axes.rosetteScancodes[ROSETTE_WEST]); + if (kpad.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) { + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; + TOUCH_JOY_LOG("XY : NORTHWEST, (%d)", kpad.rosetteScancodes[ROSETTE_WEST]); } else { - TOUCH_JOY_LOG("XY : WEST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; - kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH]; + TOUCH_JOY_LOG("XY : WEST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; + kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH]; } break; case OCTANT_NORTH: - if (axes.rosetteScancodes[ROSETTE_NORTH] >= 0) { - TOUCH_JOY_LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + if (kpad.rosetteScancodes[ROSETTE_NORTH] >= 0) { + TOUCH_JOY_LOG("Y : NORTH (%d)", kpad.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTH]; } else if (radians < RADIANS_NORTH) { - TOUCH_JOY_LOG("XY : NORTHWEST (%d)", axes.rosetteScancodes[ROSETTE_NORTHWEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHWEST]; + TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; } else { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", axes.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHEAST]; + TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; } break; case OCTANT_NORTHEAST: - if (axes.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", axes.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHEAST]; + if (kpad.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) { + TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; } else { - TOUCH_JOY_LOG("XY : EAST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; - kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH]; + TOUCH_JOY_LOG("XY : EAST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; + kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH]; } break; case OCTANT_WEST: - if (axes.rosetteScancodes[ROSETTE_WEST] >= 0) { - TOUCH_JOY_LOG("Y : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + if (kpad.rosetteScancodes[ROSETTE_WEST] >= 0) { + TOUCH_JOY_LOG("Y : WEST (%d)", kpad.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; } else if (radians > RADIANS_WEST_NEG && radians < 0) { - TOUCH_JOY_LOG("XY : NORTHWEST (%d)", axes.rosetteScancodes[ROSETTE_NORTHWEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHWEST]; + TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; } else { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHWEST]; + TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; } break; case OCTANT_EAST: - if (axes.rosetteScancodes[ROSETTE_EAST] >= 0) { - TOUCH_JOY_LOG("Y : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + if (kpad.rosetteScancodes[ROSETTE_EAST] >= 0) { + TOUCH_JOY_LOG("Y : EAST (%d)", kpad.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; } else if (radians < RADIANS_EAST) { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", axes.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHEAST]; + TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; } else { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHEAST]; + TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; } break; case OCTANT_SOUTHWEST: - if (axes.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHWEST]; + if (kpad.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) { + TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; } else { - TOUCH_JOY_LOG("XY : WEST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; - kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH]; + TOUCH_JOY_LOG("XY : WEST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; + kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH]; } break; case OCTANT_SOUTH: - if (axes.rosetteScancodes[ROSETTE_SOUTH] >= 0) { - TOUCH_JOY_LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + if (kpad.rosetteScancodes[ROSETTE_SOUTH] >= 0) { + TOUCH_JOY_LOG("Y : SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTH]; } else if (radians > RADIANS_SOUTH) { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHWEST]; + TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; } else { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHEAST]; + TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; } break; case OCTANT_SOUTHEAST: - if (axes.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHEAST]; + if (kpad.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) { + TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; } else { - TOUCH_JOY_LOG("XY : EAST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; - kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH]; + TOUCH_JOY_LOG("XY : EAST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; + kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH]; } break; @@ -431,7 +443,7 @@ static void touchkpad_buttonDown(void) { kpad.buttonBegan = true; _touch_sourceBegin(&kpad.buttonLock); } - _set_current_button_state(buttons.touchDownChar, buttons.touchDownScancode); + _set_current_button_state(kpad.touchDownChar, kpad.touchDownScancode); if (kpad.scancodes[REPEAT_BUTTON] >= 0) { TOUCH_JOY_LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar); clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]); @@ -447,11 +459,11 @@ static void touchkpad_buttonMove(int dx, int dy) { touchjoy_button_type_t theButtonChar = -1; int theButtonScancode = -1; if (dy < 0) { - theButtonChar = buttons.northChar; - theButtonScancode = buttons.northScancode; + theButtonChar = kpad.northChar; + theButtonScancode = kpad.northScancode; } else { - theButtonChar = buttons.southChar; - theButtonScancode = buttons.southScancode; + theButtonChar = kpad.southChar; + theButtonScancode = kpad.southScancode; } _set_current_button_state(theButtonChar, theButtonScancode); } @@ -467,8 +479,91 @@ static void touchkpad_buttonUp(int dx, int dy) { } } -static void touchkpad_setKeyRepeatThreshold(float repeatThresholdSecs) { - kpad.repeatThresholdNanos = repeatThresholdSecs * NANOSECONDS_PER_SECOND; +static void touchkpad_prefsChanged(const char *domain) { + assert(video_isRenderThread()); + + bool bVal = false; + float fVal = 0.f; + long lVal = 0; + + kpad.repeatThresholdNanos = prefs_parseFloatValue(domain, PREF_KPAD_REPEAT_THRESH, &fVal) ? fVal*NANOSECONDS_PER_SECOND : NANOSECONDS_PER_SECOND; + + kpad.touchDownChar = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_SPACE_VISUAL; + kpad.touchDownScancode = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_SCAN, &lVal, /*base:*/10) ? lVal : c_keys_ascii_to_scancode(' '); + + kpad.southChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE; + kpad.southScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_SCAN, &lVal, /*base:*/10) ? lVal : -1; + + kpad.northChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE; + kpad.northScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_SCAN, &lVal, /*base:*/10) ? lVal : -1; + + const unsigned long rosetteCount = ROSETTE_ROWS*ROSETTE_COLS; + + do { + const int rosetteChars[ROSETTE_ROWS*ROSETTE_COLS] = { + ICONTEXT_NONACTIONABLE, 'I', ICONTEXT_NONACTIONABLE, + 'J', ICONTEXT_NONACTIONABLE, 'K', + ICONTEXT_NONACTIONABLE, 'M', ICONTEXT_NONACTIONABLE, + }; + const int rosetteScans[ROSETTE_ROWS*ROSETTE_COLS] = { + -1, c_keys_ascii_to_scancode('I'), -1, + c_keys_ascii_to_scancode('J'), -1, c_keys_ascii_to_scancode('K'), + -1, c_keys_ascii_to_scancode('M'), -1, + }; + for (unsigned long i=0; ianimation_showTouchKeyboard(); + } } static void gltouchkbd_render(void) { if (!isAvailable) { return; } - if (!isEnabled) { - return; + if (UNLIKELY(kbd.prefsChanged)) { + gltouchkbd_setup(); } if (!ownsScreen) { return; } - if (kbd.nextGlyphMultiplier) { - kbd.glyphMultiplier = kbd.nextGlyphMultiplier; - kbd.nextGlyphMultiplier = 0; - gltouchkbd_setup(); - } - float alpha = glhud_getTimedVisibility(kbd.timingBegin, minAlpha, maxAlpha); if (alpha < minAlpha) { alpha = minAlpha; @@ -593,10 +588,7 @@ static void gltouchkbd_render(void) { static void gltouchkbd_reshape(int w, int h, bool landscape) { LOG("w:%d h:%d landscape:%d", w, h, landscape); - - touchport.rawWidth = w; - touchport.rawHeight = h; - touchport.isLandscape = landscape; + assert(video_isRenderThread()); touchport.kbdX = 0; @@ -623,31 +615,13 @@ static void gltouchkbd_reshape(int w, int h, bool landscape) { } } -static void gltouchkbd_setData(const char *jsonData) { - JSON_ref parsedData = NULL; - int tokCount = json_createFromString(jsonData, &parsedData); - - do { - if (tokCount < 0) { - break; - } - - json_mapParseFloatValue(parsedData, PREF_PORTRAIT_HEIGHT_SCALE, &kbd.portraitHeightScale); - json_mapParseFloatValue(parsedData, PREF_PORTRAIT_POSITION_SCALE, &kbd.portraitPositionScale); - - gltouchkbd_reshape(touchport.rawWidth, touchport.rawHeight, touchport.isLandscape); - } while (0); - - json_destroy(&parsedData); -} - static int64_t gltouchkbd_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { if (!isAvailable) { return 0x0LL; } - if (!isEnabled) { - return 0x0LL; + if (UNLIKELY(kbd.prefsChanged)) { + return 0x0; } if (!ownsScreen) { return 0x0LL; @@ -702,90 +676,21 @@ static int64_t gltouchkbd_onTouchEvent(interface_touch_event_t action, int point } // ---------------------------------------------------------------------------- -// Animation and settings handling -static bool gltouchkbd_isTouchKeyboardAvailable(void) { - return isAvailable; -} - -static void gltouchkbd_setTouchKeyboardEnabled(bool enabled) { - isEnabled = enabled; -} - -static void gltouchkbd_setTouchKeyboardOwnsScreen(bool pwnd) { - ownsScreen = pwnd; - if (ownsScreen) { - minAlpha = minAlphaWhenOwnsScreen; - if (allowLowercase) { - caps_lock = false; - } else { - caps_lock = true; - } - } else { - // reset visuals - minAlpha = 0.0; - - kbd.selectedCol = -1; - kbd.selectedRow = -1; - - if (kbd.model) { - GLModelHUDElement *hudKeyboard = (GLModelHUDElement *)kbd.model->custom; - hudKeyboard->colorScheme = RED_ON_BLACK; - glhud_setupDefault(kbd.model); - } - - // reset CTRL state upon leaving this touch device - kbd.ctrlPressed = false; - c_keys_handle_input(SCODE_L_CTRL, /*pressed:*/false, /*ASCII:*/false); - } -} - -static bool gltouchkbd_ownsScreen(void) { - return ownsScreen; -} - -static void gltouchkbd_setGlyphScale(int glyphScale) { - if (glyphScale == 0) { - glyphScale = 1; - } - kbd.nextGlyphMultiplier = glyphScale; -} - -static void gltouchkbd_setVisibilityWhenOwnsScreen(float inactiveAlpha, float activeAlpha) { - minAlphaWhenOwnsScreen = inactiveAlpha; - maxAlpha = activeAlpha; - if (ownsScreen) { - minAlpha = minAlphaWhenOwnsScreen; - } -} - -static void gltouchkbd_setLowercaseEnabled(bool enabled) { - allowLowercase = enabled; - if (allowLowercase && ownsScreen) { - caps_lock = false; - } else { - caps_lock = true; - } -} - -static void gltouchkbd_beginCalibration(void) { - isCalibrating = true; -} - -static void gltouchkbd_endCalibration(void) { - isCalibrating = false; -} - -static void gltouchkbd_loadAltKbd(const char *kbdPath) { +static void _loadAltKbd(const char *kbdPath) { JSON_ref jsonRef = NULL; + + json_unescapeSlashes(&kbdPath); int tokCount = json_createFromFile(kbdPath, &jsonRef); - JSON_s parsedData = (JSON_s)parsedData; + JSON_s parsedData = { 0 }; do { if (tokCount < 0) { break; } + parsedData = (JSON_s)(*jsonRef); + // we are expecting a very specific layout ... abort if anything is not correct int idx=0; @@ -922,7 +827,79 @@ static void gltouchkbd_loadAltKbd(const char *kbdPath) { } while (0); - json_destroy(&parsedData); + json_destroy(&jsonRef); +} + +static void gltouchkbd_applyPrefs(void) { + assert(video_isRenderThread()); + + kbd.prefsChanged = false; + + bool bVal = false; + float fVal = 0.f; + long lVal = 0; + + allowLowercase = prefs_parseBoolValue (PREF_DOMAIN_KEYBOARD, PREF_LOWERCASE_ENABLED, &bVal) ? bVal : false; + + minAlphaWhenOwnsScreen = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_MIN_ALPHA, &fVal) ? fVal : 1/4.f; + maxAlpha = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_MAX_ALPHA, &fVal) ? fVal : 1.f; + + kbd.glyphMultiplier = prefs_parseLongValue (PREF_DOMAIN_KEYBOARD, PREF_GLYPH_MULTIPLIER, &lVal, /*base:*/10) ? lVal : 2; + if (kbd.glyphMultiplier == 0) { + kbd.glyphMultiplier = 1; + } + if (kbd.glyphMultiplier > 4) { + kbd.glyphMultiplier = 4; + } + + kbd.portraitHeightScale = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_PORTRAIT_HEIGHT_SCALE, &fVal) ? fVal : 0.5f; + kbd.portraitPositionScale = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_PORTRAIT_POSITION_SCALE, &fVal) ? fVal : 0.f; + + isCalibrating = prefs_parseBoolValue (PREF_DOMAIN_TOUCHSCREEN, PREF_CALIBRATING, &bVal) ? bVal : false; + const interface_device_t screenOwner + = prefs_parseLongValue (PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, &lVal, /*base:*/10) ? (interface_device_t)lVal : TOUCH_DEVICE_KEYBOARD; + ownsScreen = (screenOwner == TOUCH_DEVICE_KEYBOARD || screenOwner == TOUCH_DEVICE_NONE); + + if (ownsScreen) { + minAlpha = minAlphaWhenOwnsScreen; + if (allowLowercase) { + caps_lock = false; + } else { + caps_lock = true; + } + } else { + // reset visuals + minAlpha = 0.0; + + kbd.selectedCol = -1; + kbd.selectedRow = -1; + + if (kbd.model) { + GLModelHUDElement *hudKeyboard = (GLModelHUDElement *)kbd.model->custom; + hudKeyboard->colorScheme = RED_ON_BLACK; + glhud_setupDefault(kbd.model); + } + + // reset CTRL state upon leaving this touch device + kbd.ctrlPressed = false; + c_keys_handle_input(SCODE_L_CTRL, /*pressed:*/false, /*ASCII:*/false); + } + + char *kbdPath = NULL; + if (prefs_copyStringValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_ALT_PATH, &kbdPath)) { + _loadAltKbd(kbdPath); + FREE(kbdPath); + } + + long width = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, &lVal, 10) ? lVal : (long)(SCANWIDTH*1.5); + long height = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, &lVal, 10) ? lVal : (long)(SCANHEIGHT*1.5); + bool isLandscape = prefs_parseBoolValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, &bVal) ? bVal : true; + + gltouchkbd_reshape(width, height, isLandscape); +} + +static void gltouchkbd_prefsChanged(const char *domain) { + kbd.prefsChanged = true; } static void _animation_showTouchKeyboard(void) { @@ -1049,37 +1026,24 @@ static void _init_gltouchkbd(void) { video_animations->animation_showTouchKeyboard = &_animation_showTouchKeyboard; video_animations->animation_hideTouchKeyboard = &_animation_hideTouchKeyboard; - keydriver_isTouchKeyboardAvailable = &gltouchkbd_isTouchKeyboardAvailable; - keydriver_setTouchKeyboardEnabled = &gltouchkbd_setTouchKeyboardEnabled; - keydriver_setTouchKeyboardOwnsScreen = &gltouchkbd_setTouchKeyboardOwnsScreen; - keydriver_ownsScreen = &gltouchkbd_ownsScreen; - keydriver_setVisibilityWhenOwnsScreen = &gltouchkbd_setVisibilityWhenOwnsScreen; - keydriver_setLowercaseEnabled = &gltouchkbd_setLowercaseEnabled; - keydriver_beginCalibration = &gltouchkbd_beginCalibration; - keydriver_endCalibration = &gltouchkbd_endCalibration; - keydriver_loadAltKbd = &gltouchkbd_loadAltKbd; - keydriver_setGlyphScale = &gltouchkbd_setGlyphScale; - - kbd.portraitHeightScale = 0.5f; - kbd.portraitPositionScale = 0.f; - + kbd.prefsChanged = true; kbd.selectedCol = -1; kbd.selectedRow = -1; kbd.ctrlCol = DEFAULT_CTRL_COL; kbd.ctrlRow = CTRLROW; - kbd.glyphMultiplier = 1; - glnode_registerNode(RENDER_LOW, (GLNode){ .type = TOUCH_DEVICE_KEYBOARD, .setup = &gltouchkbd_setup, .shutdown = &gltouchkbd_shutdown, .render = &gltouchkbd_render, - .reshape = &gltouchkbd_reshape, .onTouchEvent = &gltouchkbd_onTouchEvent, - .setData = &gltouchkbd_setData, }); + + prefs_registerListener(PREF_DOMAIN_KEYBOARD, &gltouchkbd_prefsChanged); + prefs_registerListener(PREF_DOMAIN_TOUCHSCREEN, &gltouchkbd_prefsChanged); + prefs_registerListener(PREF_DOMAIN_INTERFACE, &gltouchkbd_prefsChanged); } static __attribute__((constructor)) void __init_gltouchkbd(void) { diff --git a/src/video/gltouchmenu.c b/src/video/gltouchmenu.c index 66df1418..3fa00072 100644 --- a/src/video/gltouchmenu.c +++ b/src/video/gltouchmenu.c @@ -75,9 +75,11 @@ static struct { float maxAlpha; // pending changes requiring reinitialization - unsigned int nextGlyphMultiplier; + bool prefsChanged; } menu = { 0 }; +static void gltouchmenu_applyPrefs(void); + // ---------------------------------------------------------------------------- static inline void _present_menu(GLModel *parent) { @@ -92,9 +94,12 @@ static inline void _show_top_left(void) { topMenuTemplate[0][0] = ICONTEXT_MENU_SPROUT; topMenuTemplate[0][1] = MOUSETEXT_RIGHT; - if (joydriver_ownsScreen()) { + long lVal = 0; + interface_device_t screenOwner = prefs_parseLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, &lVal, /*base:*/10) ? (interface_device_t)lVal : TOUCH_DEVICE_NONE; + + if (screenOwner == TOUCH_DEVICE_JOYSTICK || screenOwner == TOUCH_DEVICE_JOYSTICK_KEYPAD) { topMenuTemplate[1][0] = ICONTEXT_UPPERCASE; - if (joydriver_getTouchVariant() == EMULATED_JOYSTICK) { + if (screenOwner == TOUCH_DEVICE_JOYSTICK) { topMenuTemplate[1][1] = ICONTEXT_MENU_TOUCHJOY_KPAD; } else { topMenuTemplate[1][1] = ICONTEXT_MENU_TOUCHJOY; @@ -174,7 +179,7 @@ static inline void _screen_to_menu(float x, float y, OUTPARM int *col, OUTPARM i //LOG("SCREEN TO MENU : menuX:%d menuXMax:%d menuW:%d keyW:%d ... scrn:(%f,%f)->kybd:(%d,%d)", touchport.topLeftX, touchport.topLeftXMax, touchport.width, keyW, x, y, *col, *row); } -static inline bool _sprout_menu(float x, float y) { +static bool _sprout_menu(float x, float y) { if (! (_is_point_on_left_menu(x, y) || _is_point_on_right_menu(x, y)) ) { return false; @@ -226,7 +231,46 @@ static inline bool _sprout_menu(float x, float y) { } } -static inline int64_t _tap_menu_item(float x, float y) { +static inline void _step_cpu_speed(int delta) { + bool wasPaused = cpu_isPaused(); + + if (!wasPaused) { + cpu_pause(); + } + + // TODO FIXME : consolidate with other CPU stepping code in interface.c/timing.c/glalert.c animation =D + float scale = roundf(cpu_scale_factor * 100.f); + if (delta < 0) { + if (scale > 400.f) { + scale = 375.f; + } else if (scale > 100.f) { + scale -= 25.f; + } else { + scale -= 5.f; + } + } else { + if (scale >= 100.f) { + scale += 25.f; + } else { + scale += 5.f; + } + } + + prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, scale); + prefs_sync(PREF_DOMAIN_VM); + + if (video_animations->animation_showCPUSpeed) { + video_animations->animation_showCPUSpeed(); + } + + timing_initialize(); + + if (!wasPaused) { + cpu_resume(); + } +} + +static int64_t _tap_menu_item(float x, float y) { if (! (_is_point_on_left_menu(x, y) || _is_point_on_right_menu(x, y)) ) { return 0x0LL; } @@ -243,18 +287,19 @@ static inline int64_t _tap_menu_item(float x, float y) { case MOUSETEXT_LEFT: LOG("decreasing cpu speed..."); - flags |= TOUCH_FLAGS_CPU_SPEED_DEC; + _step_cpu_speed(-1); break; case MOUSETEXT_RIGHT: LOG("increasing cpu speed..."); - flags |= TOUCH_FLAGS_CPU_SPEED_INC; + _step_cpu_speed(1); break; case MOUSETEXT_CHECKMARK: LOG("showing main menu..."); flags |= TOUCH_FLAGS_REQUEST_HOST_MENU; _hide_top_right(); + prefs_save(); break; case ICONTEXT_MENU_TOUCHJOY: @@ -262,6 +307,8 @@ static inline int64_t _tap_menu_item(float x, float y) { flags |= TOUCH_FLAGS_INPUT_DEVICE_CHANGE; flags |= TOUCH_FLAGS_JOY; _hide_top_left(); + prefs_setLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, TOUCH_DEVICE_JOYSTICK); + prefs_sync(PREF_DOMAIN_TOUCHSCREEN); break; case ICONTEXT_MENU_TOUCHJOY_KPAD: @@ -269,6 +316,8 @@ static inline int64_t _tap_menu_item(float x, float y) { flags |= TOUCH_FLAGS_INPUT_DEVICE_CHANGE; flags |= TOUCH_FLAGS_JOY_KPAD; _hide_top_left(); + prefs_setLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, TOUCH_DEVICE_JOYSTICK_KEYPAD); + prefs_sync(PREF_DOMAIN_TOUCHSCREEN); break; case ICONTEXT_UPPERCASE: @@ -276,6 +325,8 @@ static inline int64_t _tap_menu_item(float x, float y) { flags |= TOUCH_FLAGS_INPUT_DEVICE_CHANGE; flags |= TOUCH_FLAGS_KBD; _hide_top_left(); + prefs_setLongValue(PREF_DOMAIN_TOUCHSCREEN, PREF_SCREEN_OWNER, TOUCH_DEVICE_KEYBOARD); + prefs_sync(PREF_DOMAIN_TOUCHSCREEN); break; case ICONTEXT_MENU_SPROUT: @@ -352,7 +403,6 @@ static void gltouchmenu_shutdown(void) { menu.topLeftShowing = false; menu.topRightShowing = false; - menu.nextGlyphMultiplier = 0; mdlDestroyModel(&menu.model); } @@ -362,6 +412,10 @@ static void gltouchmenu_setup(void) { gltouchmenu_shutdown(); + if (menu.prefsChanged) { + gltouchmenu_applyPrefs(); + } + menu.model = mdlCreateQuad((GLModelParams_s){ .skew_x = -1.0, .skew_y = 1.0-touchport.modelHeight, @@ -399,11 +453,8 @@ static void gltouchmenu_render(void) { if (!isEnabled) { return; } - - if (menu.nextGlyphMultiplier) { - menu.glyphMultiplier = menu.nextGlyphMultiplier; - menu.nextGlyphMultiplier = 0; - gltouchmenu_setup(); + if (UNLIKELY(menu.prefsChanged)) { + gltouchmenu_setup(); // fully set up again on prefs change } float alpha = glhud_getTimedVisibility(menu.timingBegin, menu.minAlpha, menu.maxAlpha); @@ -434,6 +485,7 @@ static void gltouchmenu_render(void) { static void gltouchmenu_reshape(int w, int h, bool landscape) { LOG("w:%d h:%d landscape:%d", w, h, landscape); + assert(video_isRenderThread()); touchport.topLeftX = 0; touchport.topLeftY = 0; @@ -467,6 +519,9 @@ static int64_t gltouchmenu_onTouchEvent(interface_touch_event_t action, int poin if (!isEnabled) { return 0x0; } + if (UNLIKELY(menu.prefsChanged)) { + return 0x0; + } //LOG("gltouchmenu_onTouchEvent ..."); @@ -520,14 +575,6 @@ static int64_t gltouchmenu_onTouchEvent(interface_touch_event_t action, int poin // ---------------------------------------------------------------------------- // Animation and settings handling -static bool gltouchmenu_isTouchMenuAvailable(void) { - return isAvailable; -} - -static void gltouchmenu_setTouchMenuEnabled(bool enabled) { - isEnabled = enabled; -} - static void _animation_showTouchMenu(void) { clock_gettime(CLOCK_MONOTONIC, &menu.timingBegin); } @@ -538,16 +585,36 @@ static void _animation_hideTouchMenu(void) { menu.timingBegin = (struct timespec){ 0 }; } -static void gltouchmenu_setTouchMenuVisibility(float inactiveAlpha, float activeAlpha) { - menu.minAlpha = inactiveAlpha; - menu.maxAlpha = activeAlpha; +static void gltouchmenu_applyPrefs(void) { + assert(video_isRenderThread()); + + menu.prefsChanged = false; + + bool bVal = false; + float fVal = 0.f; + long lVal = 0; + + isEnabled = prefs_parseBoolValue (PREF_DOMAIN_KEYBOARD, PREF_TOUCHMENU_ENABLED, &bVal) ? bVal : true; + + menu.minAlpha = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_MIN_ALPHA, &fVal) ? fVal : 1/4.f; + menu.maxAlpha = prefs_parseFloatValue(PREF_DOMAIN_KEYBOARD, PREF_MAX_ALPHA, &fVal) ? fVal : 1.f; + menu.glyphMultiplier = prefs_parseLongValue (PREF_DOMAIN_KEYBOARD, PREF_GLYPH_MULTIPLIER, &lVal, /*base:*/10) ? lVal : 2; + if (menu.glyphMultiplier == 0) { + menu.glyphMultiplier = 1; + } + if (menu.glyphMultiplier > 4) { + menu.glyphMultiplier = 4; + } + + long width = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, &lVal, 10) ? lVal : (long)(SCANWIDTH*1.5); + long height = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, &lVal, 10) ? lVal : (long)(SCANHEIGHT*1.5); + bool isLandscape = prefs_parseBoolValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, &bVal) ? bVal : true; + + gltouchmenu_reshape(width, height, isLandscape); } -static void gltouchmenu_setGlyphScale(int glyphScale) { - if (glyphScale == 0) { - glyphScale = 1; - } - menu.nextGlyphMultiplier = glyphScale; +static void gltouchmenu_prefsChanged(const char *domain) { + menu.prefsChanged = true; } // ---------------------------------------------------------------------------- @@ -559,24 +626,18 @@ static void _init_gltouchmenu(void) { video_animations->animation_showTouchMenu = &_animation_showTouchMenu; video_animations->animation_hideTouchMenu = &_animation_hideTouchMenu; - interface_isTouchMenuAvailable = &gltouchmenu_isTouchMenuAvailable; - interface_setTouchMenuEnabled = &gltouchmenu_setTouchMenuEnabled; - interface_setTouchMenuVisibility = &gltouchmenu_setTouchMenuVisibility; - interface_setGlyphScale = &gltouchmenu_setGlyphScale; - - menu.glyphMultiplier = 1; - menu.minAlpha = 1/4.f; // Minimum alpha value of components (at zero, will not render) - menu.maxAlpha = 1.f; + menu.prefsChanged = true; glnode_registerNode(RENDER_TOP, (GLNode){ .type = TOUCH_DEVICE_TOPMENU, .setup = &gltouchmenu_setup, .shutdown = &gltouchmenu_shutdown, .render = &gltouchmenu_render, - .reshape = &gltouchmenu_reshape, .onTouchEvent = &gltouchmenu_onTouchEvent, - .setData = NULL, }); + + prefs_registerListener(PREF_DOMAIN_KEYBOARD, &gltouchmenu_prefsChanged); + prefs_registerListener(PREF_DOMAIN_INTERFACE, &gltouchmenu_prefsChanged); } static __attribute__((constructor)) void __init_gltouchmenu(void) { diff --git a/src/video/glvideo.c b/src/video/glvideo.c index 6619f68d..520535b2 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -18,9 +18,6 @@ static int viewportY = 0; static int viewportWidth = SCANWIDTH*1.5; static int viewportHeight = SCANHEIGHT*1.5; -static int rawWidth = SCANWIDTH*1.5; -static int rawHeight = SCANHEIGHT*1.5; -static bool isLandscape = true; static float portraitPositionScale = 3/4.f; GLint texSamplerLoc = UNINITIALIZED_GL; @@ -37,6 +34,9 @@ static GLModel *crtModel = NULL; static GLuint vertexShader = UNINITIALIZED_GL; static GLuint fragShader = UNINITIALIZED_GL; +static bool prefsChanged = true; +static void glvideo_applyPrefs(void); + //---------------------------------------------------------------------------- static void _glvideo_setup_hackarounds(void) { @@ -131,6 +131,10 @@ static void _glvideo_setup_hackarounds(void) { static void glvideo_init(void) { + if (prefsChanged) { + glvideo_applyPrefs(); + } + _glvideo_setup_hackarounds(); #if !PERSPECTIVE @@ -279,6 +283,10 @@ static void glvideo_render(void) { return; } + if (UNLIKELY(prefsChanged)) { + glvideo_applyPrefs(); + } + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); #if PERSPECTIVE @@ -388,10 +396,7 @@ static void glvideo_render(void) { static void glvideo_reshape(int w, int h, bool landscape) { LOG("w:%d h:%d landscape:%d", w, h, landscape); - - rawWidth = w; - rawHeight = h; - isLandscape = landscape; + assert(video_isRenderThread()); swizzleDimensions(&w, &h, landscape); @@ -410,7 +415,7 @@ static void glvideo_reshape(int w, int h, bool landscape) { viewportY = (h-h2)/2; viewportWidth = w; viewportHeight = h2; - if (!isLandscape) { + if (!landscape) { viewportY = (h-h2) * portraitPositionScale; } //LOG("OK2 : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight); @@ -423,24 +428,29 @@ static void glvideo_reshape(int w, int h, bool landscape) { } } -#if INTERFACE_TOUCH -static void glvideo_setData(const char *jsonData) { - JSON_ref parsedData = NULL; - int tokCount = json_createFromString(jsonData, &parsedData); +static void glvideo_applyPrefs(void) { + assert(video_isRenderThread()); - do { - if (tokCount < 0) { - break; - } + prefsChanged = false; - json_mapParseFloatValue(parsedData, PREF_PORTRAIT_POSITION_SCALE, &portraitPositionScale); + float fVal = 0.f; + long lVal = 0; + bool bVal = false; - glvideo_reshape(rawWidth, rawHeight, isLandscape); - } while (0); + portraitPositionScale = prefs_parseFloatValue(PREF_DOMAIN_VIDEO, PREF_PORTRAIT_POSITION_SCALE, &fVal) ? fVal : 3/4.f; - json_destroy(&parsedData); + long width = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_WIDTH, &lVal, 10) ? lVal : (long)(SCANWIDTH*1.5); + long height = prefs_parseLongValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_HEIGHT, &lVal, 10) ? lVal : (long)(SCANHEIGHT*1.5); + bool isLandscape = prefs_parseBoolValue (PREF_DOMAIN_INTERFACE, PREF_DEVICE_LANDSCAPE, &bVal) ? bVal : true; + + glvideo_reshape(width, height, isLandscape); } +static void glvideo_prefsChanged(const char *domain) { + prefsChanged = true; +} + +#if INTERFACE_TOUCH static int64_t glvideo_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { // no-op return 0x0; @@ -456,13 +466,14 @@ static void _init_glvideo(void) { .setup = &glvideo_init, .shutdown = &glvideo_shutdown, .render = &glvideo_render, - .reshape = &glvideo_reshape, #if INTERFACE_TOUCH .type = TOUCH_DEVICE_FRAMEBUFFER, .onTouchEvent = &glvideo_onTouchEvent, - .setData = &glvideo_setData, #endif }); + + prefs_registerListener(PREF_DOMAIN_VIDEO, &glvideo_prefsChanged); + prefs_registerListener(PREF_DOMAIN_INTERFACE, &glvideo_prefsChanged); } static __attribute__((constructor)) void __init_glvideo(void) {