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
This commit is contained in:
Aaron Culliney 2016-04-05 22:04:57 -07:00
parent b59e1ca7ab
commit db816ad502
47 changed files with 2805 additions and 2999 deletions

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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 ...");

View File

@ -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<String> mPathStack = new ArrayList<String>();
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();

View File

@ -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<Apple2MenuView> 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<Apple2MenuView> viewStack, Apple2Preferences.TouchDeviceVariant variant) {
public Apple2JoystickCalibration(Apple2Activity activity, ArrayList<Apple2MenuView> 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);
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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<Apple2MenuView> 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<Apple2MenuView> 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<String> chars = new ArrayList<String>();
private static ArrayList<String> scans = new ArrayList<String>();
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) {

View File

@ -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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars = new ArrayList<String>();
ArrayList<String> scans = new ArrayList<String>();
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<String> chars, ArrayList<String> scans, int aChar, int aScan) {
chars.add("" + aChar);
scans.add("" + aScan);
}
public static void saveRosettes(ArrayList<String> chars, ArrayList<String> 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

View File

@ -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);
}

View File

@ -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<Apple2MenuView> 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<Apple2MenuView> 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);
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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 <jni.h>
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);
}

View File

@ -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)

View File

@ -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);
}

View File

@ -648,8 +648,6 @@ void cpu65_uninterrupt(int reason)
}
void cpu65_reboot(void) {
timing_initialize();
video_reset();
joy_button0 = 0xff; // OpenApple
cpu65_interrupt(ResetSig);
}

View File

@ -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();

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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 (<sigh> a big fhank you to Java org.json.JSONXXX !)
bool json_unescapeSlashes(char **kbdPath);
// ----------------------------------------------------------------------------
// destructor
void json_destroy(JSON_ref *jsonRef);

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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;

View File

@ -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; i<ROSETTE_ROWS; i++) {
for (unsigned int j=0; j<ROSETTE_COLS; j++) {
((hudElement->tpl)+(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;

View File

@ -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

View File

@ -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) {

View File

@ -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<ROSETTE_COLS; i++) {
for (unsigned int j=0; j<ROSETTE_ROWS; j++) {
c_keys_handle_input(axes.rosetteScancodes[i], /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(kpad.rosetteScancodes[i], /*pressed:*/false, /*ASCII:*/false);
}
}
c_keys_handle_input(buttons.touchDownScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(buttons.northScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(buttons.southScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(kpad.touchDownScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(kpad.northScancode, /*pressed:*/false, /*ASCII:*/false);
c_keys_handle_input(kpad.southScancode, /*pressed:*/false, /*ASCII:*/false);
}
static void touchkpad_setup(void (*buttonDrawCallback)(char newChar)) {
@ -247,8 +259,8 @@ static void touchkpad_axisDown(void) {
kpad.timingBegins[REPEAT_AXIS_ALT] = now;
kpad.axisCurrentOctant = ORIGIN;
if (axes.rosetteScancodes[ROSETTE_CENTER] >= 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; i<rosetteCount; i++) {
kpad.rosetteChars[i] = rosetteChars[i];
kpad.rosetteScancodes[i] = rosetteScans[i];
}
} while (0);
// ASCII : "rosetteChars" : [ 121, 127, 130, 100, 101, 121, 132, 120, 99 ]
JSON_ref array = NULL;
do {
if (!prefs_copyJSONValue(domain, PREF_KPAD_ROSETTE_CHAR_ARRAY, &array)) {
LOG("could not parse touch keypad rosette");
break;
}
long count = 0;
if (!json_arrayCount(array, &count)) {
LOG("rosette is not an array!");
break;
}
if (count != rosetteCount) {
LOG("rosette count unexpected : %lu!", rosetteCount);
break;
}
for (unsigned long i=0; i<rosetteCount; i++) {
kpad.rosetteChars[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? (uint8_t)lVal : ' ';
}
} while (0);
json_destroy(&array);
// long : "rosetteScancodes" : [ -1, 100, -1, 99, -1, 96, -1, 101, -1 ]
do {
if (!prefs_copyJSONValue(domain, PREF_KPAD_ROSETTE_SCAN_ARRAY, &array)) {
LOG("could not parse touch keypad rosette");
break;
}
long count = 0;
if (!json_arrayCount(array, &count)) {
LOG("rosette is not an array!");
break;
}
if (count != rosetteCount) {
LOG("rosette count unexpected : %lu!", rosetteCount);
break;
}
for (unsigned long i=0; i<rosetteCount; i++) {
kpad.rosetteScancodes[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? lVal : -1;
}
} while (0);
json_destroy(&array);
}
static uint8_t *touchkpad_rosetteChars(void) {
return kpad.rosetteChars;
}
// ----------------------------------------------------------------------------
@ -482,13 +577,13 @@ static void _init_gltouchjoy_kpad(void) {
kpad.currButtonDisplayChar = ' ';
kpad.repeatThresholdNanos = KEY_REPEAT_THRESHOLD_NANOS;
kpadJoy.variant = &touchkpad_variant,
kpadJoy.resetState = &touchkpad_resetState,
kpadJoy.setup = &touchkpad_setup,
kpadJoy.shutdown = &touchkpad_shutdown,
kpadJoy.prefsChanged = &touchkpad_prefsChanged;
kpadJoy.buttonDown = &touchkpad_buttonDown,
kpadJoy.buttonMove = &touchkpad_buttonMove,
kpadJoy.buttonUp = &touchkpad_buttonUp,
@ -497,9 +592,9 @@ static void _init_gltouchjoy_kpad(void) {
kpadJoy.axisMove = &touchkpad_axisMove,
kpadJoy.axisUp = &touchkpad_axisUp,
joydriver_setKeyRepeatThreshold = &touchkpad_setKeyRepeatThreshold;
kpadJoy.rosetteChars = &touchkpad_rosetteChars;
gltouchjoy_registerVariant(EMULATED_KEYPAD, &kpadJoy);
gltouchjoy_registerVariant(TOUCH_DEVICE_JOYSTICK_KEYPAD, &kpadJoy);
}
static __attribute__((constructor)) void __init_gltouchjoy_kpad(void) {

View File

@ -42,7 +42,6 @@
#define KBD_OBJ_H_LANDSCAPE GL_MODEL_MAX
static bool isAvailable = false; // Were there any OpenGL/memory errors on gltouchkbd initialization?
static bool isEnabled = true; // Does player want touchkbd enabled?
static bool ownsScreen = false; // Does the touchkbd currently own the screen to the exclusion?
static bool isCalibrating = false; // Are we in calibration mode?
static bool allowLowercase = false; // show lowercase keyboard
@ -108,11 +107,6 @@ static struct {
int kbdW;
int kbdH;
// raw device dimensions
int rawWidth;
int rawHeight;
int isLandscape;
} touchport = { 0 };
// keyboard variables
@ -138,10 +132,11 @@ static struct {
struct timespec timingBegin;
// pending changes requiring reinitialization
unsigned int nextGlyphMultiplier;
bool prefsChanged;
} kbd = { 0 };
static void gltouchkbd_applyPrefs(void);
// ----------------------------------------------------------------------------
// Misc internal methods
@ -435,7 +430,7 @@ static inline int64_t _tap_key_at_point(float x, float y) {
}
} else if (isCTRL) {
c_keys_handle_input(scancode, /*pressed:*/kbd.ctrlPressed, /*ASCII:*/false);
} else if (scancode != -1) {
} else if (scancode) {
// perform a press of other keys (ESC, Arrows, etc)
c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false);
c_keys_handle_input(scancode, /*pressed:*/false, /*ASCII:*/false);
@ -506,8 +501,6 @@ static void gltouchkbd_shutdown(void) {
kbd.selectedCol = -1;
kbd.selectedRow = -1;
kbd.ctrlPressed = false;
kbd.nextGlyphMultiplier = 0;
}
static void gltouchkbd_setup(void) {
@ -515,6 +508,10 @@ static void gltouchkbd_setup(void) {
gltouchkbd_shutdown();
if (kbd.prefsChanged) {
gltouchkbd_applyPrefs();
}
kbd.model = mdlCreateQuad((GLModelParams_s){
.skew_x = -GL_MODEL_HALF,
.skew_y = kbd.modelSkewY,
@ -524,7 +521,7 @@ static void gltouchkbd_setup(void) {
.positionUsageHint = GL_DYNAMIC_DRAW, // positions might change
.tex_w = KBD_FB_WIDTH * kbd.glyphMultiplier,
.tex_h = KBD_FB_HEIGHT * kbd.glyphMultiplier,
.texcoordUsageHint = GL_DYNAMIC_DRAW, // but key texture does
.texcoordUsageHint = GL_DYNAMIC_DRAW, // and key texture does
}, (GLCustom){
.create = &_create_touchkbd_hud,
.destroy = &glhud_destroyDefault,
@ -541,25 +538,23 @@ static void gltouchkbd_setup(void) {
clock_gettime(CLOCK_MONOTONIC, &kbd.timingBegin);
isAvailable = true;
if (ownsScreen) {
video_animations->animation_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) {

View File

@ -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) {

View File

@ -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) {