This commit is contained in:
downthecrop 2021-12-11 16:13:43 -08:00
parent f4ac5b8e50
commit bdb7c4fa54
90 changed files with 4 additions and 9876 deletions

7
.gitignore vendored
View file

@ -1,8 +1,9 @@
/.gradle
/app_2009scape/.cxx/
app_2009scape/.cxx/
/build
/*/build
app_pojavlauncher/src/main/assets/components/jre
app_2009scape/src/main/assets/components/jre
local.properties
.idea/
app_pojavlauncher/.cxx/
.vs/
.vs/

View file

@ -1,240 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.*;
import android.os.*;
import androidx.appcompat.app.*;
import androidx.preference.*;
import android.view.*;
import android.widget.*;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
import com.kdt.pickafile.*;
import java.io.*;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.customcontrols.*;
public class CustomControlsActivity extends BaseActivity
{
private DrawerLayout drawerLayout;
private NavigationView navDrawer;
private ControlLayout ctrlLayout;
private SharedPreferences mPref;
public boolean isModified = false;
public boolean isFromMainActivity = false;
private static String selectedName = "new_control";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean("fromMainActivity", false)) {
// TODO translucent!
// setTheme(androidx.appcompat.R.style.Theme_AppCompat_Translucent);
}
setContentView(R.layout.control_mapping);
mPref = PreferenceManager.getDefaultSharedPreferences(this);
ctrlLayout = (ControlLayout) findViewById(R.id.customctrl_controllayout);
// Menu
drawerLayout = (DrawerLayout) findViewById(R.id.customctrl_drawerlayout);
navDrawer = (NavigationView) findViewById(R.id.customctrl_navigation_view);
navDrawer.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_ctrl_load:
load(ctrlLayout);
break;
case R.id.menu_ctrl_add:
ctrlLayout.addControlButton(new ControlData("New"));
break;
case R.id.menu_ctrl_add_drawer:
ctrlLayout.addDrawer(new ControlDrawerData());
break;
case R.id.menu_ctrl_selectdefault:
dialogSelectDefaultCtrl(ctrlLayout);
break;
case R.id.menu_ctrl_save:
save(false,ctrlLayout);
break;
}
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
drawerLayout.closeDrawers();
return true;
}
});
ctrlLayout.setActivity(this);
ctrlLayout.setModifiable(true);
loadControl(LauncherPreferences.PREF_DEFAULTCTRL_PATH,ctrlLayout);
}
@Override
public void onBackPressed() {
if (!isModified) {
setResult(Activity.RESULT_OK, new Intent());
super.onBackPressed();
return;
}
save(true,ctrlLayout);
}
private static void setDefaultControlJson(String path,ControlLayout ctrlLayout) {
try {
// Load before save to make sure control is not error
ctrlLayout.loadLayout(Tools.GLOBAL_GSON.fromJson(Tools.read(path), CustomControls.class));
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultCtrl", path).commit();
LauncherPreferences.PREF_DEFAULTCTRL_PATH = path;
} catch (Throwable th) {
Tools.showError(ctrlLayout.getContext(), th);
}
}
public static void dialogSelectDefaultCtrl(final ControlLayout layout) {
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
builder.setTitle(R.string.customctrl_selectdefault);
builder.setPositiveButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.create();
FileListView flv = new FileListView(dialog, "json");
flv.lockPathAt(Tools.CTRLMAP_PATH);
flv.setFileSelectedListener(new FileSelectedListener(){
@Override
public void onFileSelected(File file, String path) {
setDefaultControlJson(path,layout);
dialog.dismiss();
}
});
dialog.setView(flv);
dialog.show();
}
private static String doSaveCtrl(String name, final ControlLayout layout) throws Exception {
String jsonPath = Tools.CTRLMAP_PATH + "/" + name + ".json";
layout.saveLayout(jsonPath);
return jsonPath;
}
public static void save(final boolean exit, final ControlLayout layout) {
final Context ctx = layout.getContext();
final EditText edit = new EditText(ctx);
edit.setSingleLine();
edit.setText(selectedName);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.global_save);
builder.setView(edit);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
if (exit) {
builder.setNeutralButton(R.string.mcn_exit_call, new AlertDialog.OnClickListener(){
@Override
public void onClick(DialogInterface p1, int p2) {
layout.setModifiable(false);
if(ctx instanceof MainActivity) {
((MainActivity) ctx).leaveCustomControls();
}else{
((CustomControlsActivity) ctx).isModified = false;
((Activity)ctx).onBackPressed();
}
// setResult(Activity.RESULT_OK, new Intent());
// CustomControlsActivity.super.onBackPressed();
}
});
}
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (edit.getText().toString().isEmpty()) {
edit.setError(ctx.getResources().getString(R.string.global_error_field_empty));
} else {
try {
String jsonPath = doSaveCtrl(edit.getText().toString(),layout);
Toast.makeText(ctx, ctx.getString(R.string.global_save) + ": " + jsonPath, Toast.LENGTH_SHORT).show();
dialog.dismiss();
if (exit) {
if(ctx instanceof MainActivity) {
((MainActivity) ctx).leaveCustomControls();
}else{
((Activity)ctx).onBackPressed();
}
//CustomControlsActivity.super.onBackPressed();
}
} catch (Throwable th) {
Tools.showError(ctx, th, exit);
}
}
}
});
}
});
dialog.show();
}
public static void load(final ControlLayout layout) {
/*ControlJsonSelector sel = new ControlJsonSelector(layout.getContext(), R.string.global_load);
sel.setFinishCallback((f)->{
loadControl(f.getAbsolutePath(),layout);
});
sel.show();*/
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
builder.setTitle(R.string.global_load);
builder.setPositiveButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.create();
FileListView flv = new FileListView(dialog, "json");
if(Build.VERSION.SDK_INT < 29)flv.listFileAt(Tools.CTRLMAP_PATH);
else flv.lockPathAt(Tools.CTRLMAP_PATH);
flv.setFileSelectedListener(new FileSelectedListener(){
@Override
public void onFileSelected(File file, String path) {
loadControl(path,layout);
dialog.dismiss();
}
});
dialog.setView(flv);
dialog.show();
}
private static void loadControl(String path,ControlLayout layout) {
try {
layout.loadLayout(path);
selectedName = new File(path).getName();
// Remove `.json`
selectedName = selectedName.substring(0, selectedName.length() - 5);
} catch (Exception e) {
Tools.showError(layout.getContext(), e);
}
}
}

View file

@ -1,16 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.*;
import android.net.*;
import androidx.browser.customtabs.*;
public class CustomTabs {
public static void openTab(Context context, String url) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setShowTitle(true);
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(context, Uri.parse(url));
}
}

View file

@ -1,38 +0,0 @@
package net.kdt.pojavlaunch;
import java.util.*;
public class DisplayableLocale {
public final Locale mLocale;
public final CharSequence mName;
private static Locale processStringLocale(String locale) {
if (locale.contains("-")) {
String[] split = locale.split("-");
return new Locale(split[0], split[1]);
} else {
return new Locale(locale);
}
}
public DisplayableLocale(String locale) {
this(processStringLocale(locale));
}
public DisplayableLocale(Locale locale) {
this(locale, locale.getDisplayName(locale));
}
public DisplayableLocale(Locale locale, CharSequence name) {
mLocale = locale;
mName = name;
}
public Locale toLocale() {
return mLocale;
}
@Override
public String toString() {
return mName.toString();
}
}

View file

@ -1,238 +0,0 @@
package net.kdt.pojavlaunch;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import android.view.KeyEvent;
import org.lwjgl.glfw.CallbackBridge;
import java.util.Arrays;
public class EfficientAndroidLWJGLKeycode {
//This old version of this class was using an ArrayMap, a generic Key -> Value data structure.
//The key being the android keycode from a KeyEvent
//The value its LWJGL equivalent.
private static final int KEYCODE_COUNT = 103;
private static final int[] androidKeycodes = new int[KEYCODE_COUNT];
private static final short[] LWJGLKeycodes = new short[KEYCODE_COUNT];
private static String[] androidKeyNameArray; /* = new String[androidKeycodes.length]; */
static {
/* BINARY SEARCH IS PERFORMED ON THE androidKeycodes ARRAY !
WHEN ADDING A MAPPING, ADD IT SO THE androidKeycodes ARRAY STAYS SORTED ! */
// Mapping Android Keycodes to LWJGL Keycodes
add(KeyEvent.KEYCODE_UNKNOWN,LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN);
add(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
// Escape key
add(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
// 0-9 keys
add(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0); //7
add(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9); //16
add(KeyEvent.KEYCODE_POUND,LWJGLGLFWKeycode.GLFW_KEY_3);
// Arrow keys
add(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP); //19
add(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
add(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
add(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT); //22
// A-Z keys
add(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A); //29
add(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
add(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
add(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
add(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
add(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
add(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
add(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
add(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
add(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
add(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
add(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
add(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
add(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_N);
add(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
add(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
add(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
add(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
add(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
add(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
add(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
add(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
add(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
add(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
add(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
add(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z); //54
add(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
// Alt keys
add(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
add(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
// Shift keys
add(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
add(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
add(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
add(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
add(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER); //66
add(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
add(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
add(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
add(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
add(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
add(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
add(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
add(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON); //74
add(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH); //76
add(KeyEvent.KEYCODE_AT,LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
// Page keys
add(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP); //92
add(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
add(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
// Control keys
add(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
add(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
add(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
add(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
add(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
// Fn keys
add(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1); //131
add(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
add(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
add(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
add(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
add(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
add(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
add(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
add(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
add(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
add(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
add(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12); //142
// Num keys
add(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK); //143
add(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
add(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
add(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
add(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
add(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL); //161
}
private static short index = 0;
private static void add(int androidKeycode, short LWJGLKeycode){
androidKeycodes[index] = androidKeycode;
LWJGLKeycodes[index] = LWJGLKeycode;
++index;
}
public static boolean containsKey(int keycode){
return getIndexByKey(keycode) >= 0;
}
public static String[] generateKeyName() {
if (androidKeyNameArray == null) {
androidKeyNameArray = new String[androidKeycodes.length];
for(int i=0; i < androidKeyNameArray.length; ++i){
androidKeyNameArray[i] = KeyEvent.keyCodeToString(androidKeycodes[i]).replace("KEYCODE_", "");
}
}
return androidKeyNameArray;
}
public static void execKey(KeyEvent keyEvent) {
execKey(keyEvent, getIndexByKey(keyEvent.getKeyCode()));
}
public static void execKey(KeyEvent keyEvent, int valueIndex) {
//valueIndex points to where the value is stored in the array.
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn();
CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed();
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
try {
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000');
sendKeyPress(
getValueByIndex(valueIndex),
key,
0,
CallbackBridge.getCurrentMods(),
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
} catch (Throwable th) {
th.printStackTrace();
}
}
public static void execKeyIndex(int index){
//Send a quick key press.
sendKeyPress(getValueByIndex(index));
}
public static int getValueByIndex(int index) {
return LWJGLKeycodes[index];
}
public static int getIndexByKey(int key){
return Arrays.binarySearch(androidKeycodes, key);
}
public static short getValue(int key){
return LWJGLKeycodes[Arrays.binarySearch(androidKeycodes, key)];
}
public static int getIndexByValue(int lwjglKey) {
//Since the LWJGL keycodes aren't sorted, linear search is used.
//You should avoid using this function on performance critical areas
for (int i = 0; i < LWJGLKeycodes.length; i++) {
if(LWJGLKeycodes[i] == lwjglKey) return i;
}
return 0;
}
}

View file

@ -1,39 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@Keep
public class ExitActivity extends AppCompatActivity {
public static void showExitMessage(Context ctx, int code) {
Intent i = new Intent(ctx,ExitActivity.class);
i.putExtra("code",code);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int code = -1;
Bundle extras = getIntent().getExtras();
if(extras != null) {
code = extras.getInt("code",-1);
}
new AlertDialog.Builder(this)
.setMessage(getString(R.string.mcn_exit_title,code))
.setPositiveButton(android.R.string.ok,(dialog,which)->{
dialog.dismiss();
ExitActivity.this.finish();
}).setOnCancelListener((z)->{
ExitActivity.this.finish();
})
.show();
}
}

View file

@ -1,204 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.OpenableColumns;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.utils.FileUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* An activity dedicated to importing control files.
*/
public class ImportControlActivity extends Activity {
private Uri mUriData;
private boolean mHasIntentChanged = true;
private volatile boolean mIsFileVerified = false;
private EditText mEditText;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Tools.initContextConstants(getApplicationContext());
setContentView(R.layout.import_control_layout);
mEditText = findViewById(R.id.editText_import_control_file_name);
}
/**
* Override the previous loaded intent
* @param intent the intent used to replace the old one.
*/
@Override
protected void onNewIntent(Intent intent) {
if(intent != null) setIntent(intent);
mHasIntentChanged = true;
}
/**
* Update all over again if the intent changed.
*/
@Override
protected void onPostResume() {
super.onPostResume();
if(!mHasIntentChanged) return;
mIsFileVerified = false;
getUriData();
mEditText.setText(getNameFromURI(mUriData));
mHasIntentChanged = false;
//Import and verify thread
//Kill the app if the file isn't valid.
new Thread(() -> {
importControlFile("TMP_IMPORT_FILE");
if(verify())mIsFileVerified = true;
else runOnUiThread(() -> {
Toast.makeText(
ImportControlActivity.this,
getText(R.string.import_control_invalid_file),
Toast.LENGTH_SHORT).show();
finishAndRemoveTask();
});
}).start();
//Auto show the keyboard
new Handler(Looper.getMainLooper()).postDelayed(() -> {
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
mEditText.setSelection(mEditText.getText().length());
}, 100);
}
/**
* Start the import.
* @param view the view which called the function
*/
public void startImport(View view) {
String fileName = trimFileName(mEditText.getText().toString());
//Step 1 check for suffixes.
if(!isFileNameValid(fileName)){
Toast.makeText(this, getText(R.string.import_control_invalid_name), Toast.LENGTH_SHORT).show();
return;
}
if(!mIsFileVerified){
Toast.makeText(this, getText(R.string.import_control_verifying_file), Toast.LENGTH_LONG).show();
return;
}
new File(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json").renameTo(new File(Tools.CTRLMAP_PATH + "/" + fileName + ".json"));
Toast.makeText(getApplicationContext(), getText(R.string.import_control_done), Toast.LENGTH_SHORT).show();
finishAndRemoveTask();
}
/**
* Copy a the file from the Intent data with a provided name into the controlmap folder.
* @param fileName The file name to use.
* @return whether the file was successfully imported
*/
private boolean importControlFile(String fileName){
InputStream is;
try {
is = getContentResolver().openInputStream(mUriData);
OutputStream os = new FileOutputStream(Tools.CTRLMAP_PATH + "/" + fileName + ".json");
byte[] buffer = new byte[1024];
while(is.read(buffer) != -1)
os.write(buffer);
os.close();
is.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* Tell if the clean version of the filename is valid.
* @param fileName the string to test
* @return whether the filename is valid
*/
private static boolean isFileNameValid(String fileName){
fileName = trimFileName(fileName);
if(fileName.isEmpty()) return false;
if (FileUtils.exists(Tools.CTRLMAP_PATH + "/" + fileName + ".json")) return false;
return true;
}
/**
* Remove or undesirable chars from the string
* @param fileName The string to trim
* @return The trimmed string
*/
private static String trimFileName(String fileName){
return fileName
.replace(".json", "")
.replaceAll("%..", "/")
.replace("/", "")
.replace("\\", "")
.trim();
}
/**
* Tries to get an Uri from the various sources
*/
private void getUriData(){
mUriData = getIntent().getData();
if(mUriData != null) return;
try {
mUriData = getIntent().getClipData().getItemAt(0).getUri();
}catch (Exception ignored){}
}
/**
* Verify if the control file is valid
* @return Whether the control file is valid
*/
private static boolean verify(){
try{
String jsonLayoutData = Tools.read(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json");
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
return layoutJobj.has("version") && layoutJobj.has("mControlDataList");
}catch (JSONException | IOException e) {
e.printStackTrace();
return false;
}
}
public String getNameFromURI(Uri uri) {
Cursor c = getContentResolver().query(uri, null, null, null, null);
c.moveToFirst();
String fileName = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
c.close();
return trimFileName(fileName);
}
}

View file

@ -1,52 +0,0 @@
package net.kdt.pojavlaunch;
import android.os.*;
public class InstallerTask extends AsyncTask<String, Void, String>
{
@Override
protected String doInBackground(String[] p1)
{
try
{
downloadLibraries(p1[0]);
dexMinecraftLibs();
downloadMinecraft(p1[0]);
dexMinecraftClient(p1[0]);
downloadAssets(p1[0]);
}
catch (Exception e)
{
return e.getMessage();
}
return null;
}
@Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
if(result == null){
//No errors
}
}
private void downloadLibraries(String versionName) throws Exception
{
}
private void dexMinecraftLibs() throws Exception
{
}
private void downloadMinecraft(String versionName) throws Exception
{
}
private void dexMinecraftClient(String version) throws Exception
{
}
private void downloadAssets(String versionName) throws Exception
{
}
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch;
public class JAssetInfo
{
public String hash;
public int size;
}

View file

@ -1,9 +0,0 @@
package net.kdt.pojavlaunch;
import java.util.Map;
public class JAssets {
public boolean map_to_resources;
public Map<String, JAssetInfo> objects;
}

View file

@ -1,73 +0,0 @@
package net.kdt.pojavlaunch;
import androidx.annotation.Keep;
import java.util.Map;
import net.kdt.pojavlaunch.value.*;
@Keep
public class JMinecraftVersionList {
public static final String TYPE_OLD_ALPHA = "old_alpha";
public static final String TYPE_OLD_BETA = "old_beta";
public static final String TYPE_RELEASE = "release";
public static final String TYPE_SNAPSHOT = "snapshot";
public Map<String, String> latest;
public Version[] versions;
@Keep
public static class Version {
// Since 1.13, so it's one of ways to check
public Arguments arguments;
public AssetIndex assetIndex;
public String assets;
public Map<String, MinecraftClientInfo> downloads;
public String id;
public String inheritsFrom;
public JavaVersionInfo javaVersion;
public DependentLibrary[] libraries;
public String mainClass;
public String minecraftArguments;
public int minimumLauncherVersion;
public DependentLibrary optifineLib;
public String releaseTime;
public String time;
public String type;
public String url;
public String sha1;
}
@Keep
public static class JavaVersionInfo {
public String component;
public int majorVersion;
}
// Since 1.13
@Keep
public static class Arguments {
public Object[] game;
public Object[] jvm;
@Keep
public static class ArgValue {
public ArgRules[] rules;
public String value;
// TLauncher styled argument...
public String[] values;
@Keep
public static class ArgRules {
public String action;
public String features;
}
}
}
@Keep
public static class AssetIndex {
public String id, sha1, url;
public long size, totalSize;
}
}

View file

@ -1,143 +0,0 @@
package net.kdt.pojavlaunch;
import java.io.*;;
/**
* This account data format is deprecated.
* The current account data format is JSON on net.kdt.pojavlaunch.value.MinecraftAccount.
* This class remain for account data migrator only.
* Methods for saving/exporting on this format are no longer available.
*/
@Deprecated
public class MCProfile
{
private static String[] emptyBuilder = new String[]{
"1.9", //Version
"ProfileIDEmpty",
"AccessToken",
"AccessTokenEmpty",
"Steve"
};
public static MCProfile.Builder load(String pofFilePath) {
try {
String pofContent = Tools.read(pofFilePath);
return parse(pofContent);
} catch (Exception e) {
throw new RuntimeException("Unable to load Profile " + pofFilePath, e);
}
}
public static MCProfile.Builder parse(String content) {
if (content == null || content.isEmpty()) {
return null;
}
MCProfile.Builder builder = new MCProfile.Builder();
String[] profileInfos = content.split(":");
String cltk = profileInfos[0];
String prtk = profileInfos[1];
String acct = profileInfos[2];
String name = profileInfos[3];
String vers = profileInfos[4];
String isAc = profileInfos[5];
//System.out.println("parse THE VER = " + vers);
builder.setClientID(cltk);
builder.setProfileID(prtk);
builder.setAccessToken(acct);
builder.setUsername(name);
builder.setVersion(vers);
builder.setIsMojangAccount(Boolean.parseBoolean(isAc));
return builder;
}
public static MCProfile.Builder loadSafety(String pofFilePath) {
try {
return load(pofFilePath);
} catch (Exception e) {
e.printStackTrace();
// return new MCProfile.Builder();
return null;
}
}
public static class Builder implements Serializable
{
private String[] fullArgs = new String[6];
private boolean isMojangAccount = true;
public Builder()
{
fullArgs = emptyBuilder;
setClientID("0");
setProfileID("00000000-0000-0000-0000-000000000000");
setAccessToken("0");
}
public boolean isMojangAccount()
{
return isMojangAccount;
}
public String getVersion()
{
return fullArgs[0];
}
public String getClientID()
{
return fullArgs[1];
}
public String getProfileID()
{
return fullArgs[2];
}
public String getAccessToken()
{
return fullArgs[3];
}
public String getUsername()
{
return fullArgs[4];
}
public void setIsMojangAccount(boolean value)
{
isMojangAccount = value;
}
public void setVersion(String value)
{
fullArgs[0] = value;
}
public void setClientID(String value)
{
fullArgs[1] = value;
}
public void setProfileID(String value)
{
fullArgs[2] = value;
}
public void setAccessToken(String value)
{
fullArgs[3] = value;
}
public void setUsername(String value)
{
fullArgs[4] = value;
}
}
}

View file

@ -1,95 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.Intent;
import android.os.*;
import net.kdt.pojavlaunch.customcontrols.*;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import java.io.*;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SUSTAINED_PERFORMANCE;
public class MainActivity extends BaseMainActivity {
public static ControlLayout mControlLayout;
private MCOptionUtils.MCOptionListener optionListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initLayout(R.layout.main_with_customctrl);
// Set the sustained performance mode for available APIs
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
getWindow().setSustainedPerformanceMode(PREF_SUSTAINED_PERFORMANCE);
super.ingameControlsEditorListener = menuItem -> {
switch (menuItem.getItemId()) {
case R.id.menu_ctrl_load:
CustomControlsActivity.load(mControlLayout);
break;
case R.id.menu_ctrl_add:
mControlLayout.addControlButton(new ControlData("New"));
break;
case R.id.menu_ctrl_add_drawer:
mControlLayout.addDrawer(new ControlDrawerData());
break;
case R.id.menu_ctrl_selectdefault:
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
break;
case R.id.menu_ctrl_save:
CustomControlsActivity.save(true,mControlLayout);
break;
}
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
return true;
};
// Recompute the gui scale when options are changed
optionListener = MCOptionUtils::getMcScale;
MCOptionUtils.addMCOptionListener(optionListener);
mControlLayout = findViewById(R.id.main_control_layout);
mControlLayout.setModifiable(false);
try {
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
} catch(IOException e) {
try {
mControlLayout.loadLayout(Tools.CTRLDEF_FILE);
DEFAULT_PREF.edit().putString("defaultCtrl",Tools.CTRLDEF_FILE).commit();
} catch (IOException ioException) {
Tools.showError(this, ioException);
}
} catch (Throwable th) {
Tools.showError(this, th);
}
// toggleGui(null);
mControlLayout.toggleControlVisible();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
// Reload PREF_DEFAULTCTRL_PATH
LauncherPreferences.loadPreferences(getApplicationContext());
try {
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onBackPressed() {
//if(isInEditor) CustomControlsActivity.save(true,mControlLayout);
}
}

View file

@ -1,600 +0,0 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.BaseMainActivity.touchCharInput;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
import android.app.Activity;
import android.content.*;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.*;
import android.view.*;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
import com.google.android.material.math.MathUtils;
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import org.lwjgl.glfw.CallbackBridge;
/**
* Class dealing with showing minecraft surface and taking inputs to dispatch them to minecraft
*/
public class MinecraftGLView extends TextureView {
/* Gamepad object for gamepad inputs, instantiated on need */
private Gamepad gamepad = null;
/* Pointer Debug textview, used to show info about the pointer state */
private TextView pointerDebugText;
/* Resolution scaler option, allow downsizing a window */
private final float scaleFactor = LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
/* Display properties, such as resolution and DPI */
private final DisplayMetrics displayMetrics = Tools.getDisplayMetrics((Activity) getContext());
/* Sensitivity, adjusted according to screen size */
private final double sensitivityFactor = (1.4 * (1080f/ displayMetrics.heightPixels));
/* Use to detect simple and double taps */
private final TapDetector singleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
private final TapDetector doubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
/* MC GUI scale, listened by MCOptionUtils */
private int GUIScale = getMcScale();
private MCOptionUtils.MCOptionListener GUIScaleListener = () -> GUIScale = getMcScale();
/* Surface ready listener, used by the activity to launch minecraft */
SurfaceReadyListener surfaceReadyListener = null;
/* List of hotbarKeys, used when clicking on the hotbar */
private static final int[] hotbarKeys = {
LWJGLGLFWKeycode.GLFW_KEY_1, LWJGLGLFWKeycode.GLFW_KEY_2, LWJGLGLFWKeycode.GLFW_KEY_3,
LWJGLGLFWKeycode.GLFW_KEY_4, LWJGLGLFWKeycode.GLFW_KEY_5, LWJGLGLFWKeycode.GLFW_KEY_6,
LWJGLGLFWKeycode.GLFW_KEY_7, LWJGLGLFWKeycode.GLFW_KEY_8, LWJGLGLFWKeycode.GLFW_KEY_9};
/* Last hotbar button (0-9) registered */
private int lastHotbarKey = -1;
/* Events can start with only a move instead of an pointerDown due to mouse passthrough */
private boolean shouldBeDown = false;
/* When fingers are really near to each other, it tends to either swap or remove a pointer ! */
private int lastPointerCount = 0;
/* Mouse positions, scaled by the scaleFactor */
private float mouse_x, mouse_y;
/* Previous MotionEvent position, not scale */
private float prevX, prevY;
/* PointerID used for the moving camera */
private int currentPointerID = -1000;
/* Initial first pointer positions non-scaled, used to test touch sloppiness */
private float initialX, initialY;
/* Last first pointer positions non-scaled, used to scroll distance */
private float scrollLastInitialX, scrollLastInitialY;
/* How much distance a finger has to go for touch sloppiness to be disabled */
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
/* How much distance a finger has to go to scroll */
public static final int FINGER_SCROLL_THRESHOLD = (int) Tools.dpToPx(6);
/* Handle hotbar throw button and mouse mining button */
public static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
public static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029;
private final Handler theHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
if(msg.what == MSG_LEFT_MOUSE_BUTTON_CHECK) {
if (LauncherPreferences.PREF_DISABLE_GESTURES) return;
float x = CallbackBridge.mouseX;
float y = CallbackBridge.mouseY;
if (CallbackBridge.isGrabbing() &&
MathUtils.dist(x, y, initialX, initialY) < FINGER_STILL_THRESHOLD) {
triggeredLeftMouseButton = true;
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
}
return;
}
if(msg.what == MSG_DROP_ITEM_BUTTON_CHECK) {
if(CallbackBridge.isGrabbing()){
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q);
theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
}
return;
}
}
};
/* Whether the button was triggered, used by the handler */
private static boolean triggeredLeftMouseButton = false;
public MinecraftGLView(Context context) {
this(context, null);
}
public MinecraftGLView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
//Fixes freeform and dex mode having transparent glass,
//since it forces android to used the background color of the view/layout behind it.
setOpaque(false);
setFocusable(true);
MCOptionUtils.addMCOptionListener(GUIScaleListener);
}
/** Initialize the view and all its settings */
public void start(){
// Add the pointer debug textview
pointerDebugText = new TextView(getContext());
pointerDebugText.setX(0);
pointerDebugText.setY(0);
pointerDebugText.setVisibility(GONE);
((ViewGroup)getParent()).addView(pointerDebugText);
setSurfaceTextureListener(new SurfaceTextureListener() {
private boolean isCalled = false;
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor);
windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor);
texture.setDefaultBufferSize(windowWidth, windowHeight);
//Load Minecraft options:
MCOptionUtils.load();
MCOptionUtils.set("overrideWidth", String.valueOf(windowWidth));
MCOptionUtils.set("overrideHeight", String.valueOf(windowHeight));
MCOptionUtils.save();
getMcScale();
// Should we do that?
if(isCalled) return;
isCalled = true;
JREUtils.setupBridgeWindow(new Surface(texture));
new Thread(() -> {
try {
Thread.sleep(200);
if(surfaceReadyListener != null){
surfaceReadyListener.isReady();
}
} catch (Throwable e) {
Tools.showError(getContext(), e, true);
}
}, "JVM Main thread").start();
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor);
windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor);
CallbackBridge.sendUpdateWindowSize(windowWidth, windowHeight);
getMcScale();
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
texture.setDefaultBufferSize(windowWidth, windowHeight);
}
});
}
/**
* The touch event for both grabbed an non-grabbed mouse state on the touch screen
* Does not cover the virtual mouse touchpad
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
// Looking for a mouse to handle, won't have an effect if no mouse exists.
for (int i = 0; i < e.getPointerCount(); i++) {
if (e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
// Mouse found
if(CallbackBridge.isGrabbing()) return false;
CallbackBridge.sendCursorPos( e.getX(i) * scaleFactor, e.getY(i) * scaleFactor);
return true; //mouse event handled successfully
}
// System.out.println("Pre touch, isTouchInHotbar=" + Boolean.toString(isTouchInHotbar) + ", action=" + MotionEvent.actionToString(e.getActionMasked()));
//Getting scaled position from the event
/* Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though. */
if(!CallbackBridge.isGrabbing()) {
mouse_x = (e.getX() * scaleFactor);
mouse_y = (e.getY() * scaleFactor);
//One android click = one MC click
if(singleTapDetector.onTouchEvent(e)){
CallbackBridge.putMouseEventWithCoords(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, mouse_x, mouse_y);
return true;
}
}
// Check double tap state, used for the hotbar
boolean hasDoubleTapped = doubleTapDetector.onTouchEvent(e);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
int pointerCount = e.getPointerCount();
// In-menu interactions
if(!CallbackBridge.isGrabbing()){
// Touch hover
if(pointerCount == 1){
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
prevX = e.getX();
prevY = e.getY();
break;
}
// Scrolling feature
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
// The pointer count can never be 0, and it is not 1, therefore it is >= 2
int hScroll = ((int) (e.getX() - scrollLastInitialX)) / FINGER_SCROLL_THRESHOLD;
int vScroll = ((int) (e.getY() - scrollLastInitialY)) / FINGER_SCROLL_THRESHOLD;
if(vScroll != 0 || hScroll != 0){
CallbackBridge.sendScroll(hScroll, vScroll);
scrollLastInitialX = e.getX();
scrollLastInitialY = e.getY();
}
break;
}
// Camera movement
int pointerIndex = e.findPointerIndex(currentPointerID);
int hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
// Start movement, due to new pointer or loss of pointer
if (pointerIndex == -1 || lastPointerCount != pointerCount || !shouldBeDown) {
if(hudKeyHandled != -1) break; //No pointer attribution on hotbar
shouldBeDown = true;
currentPointerID = e.getPointerId(0);
prevX = e.getX();
prevY = e.getY();
break;
}
// Continue movement as usual
if(hudKeyHandled == -1){ //No camera on hotbar
mouse_x += (e.getX(pointerIndex) - prevX) * sensitivityFactor;
mouse_y += (e.getY(pointerIndex) - prevY) * sensitivityFactor;
}
prevX = e.getX(pointerIndex);
prevY = e.getY(pointerIndex);
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
break;
case MotionEvent.ACTION_DOWN: // 0
CallbackBridge.sendPrepareGrabInitialPos();
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
boolean isTouchInHotbar = hudKeyHandled != -1;
if (isTouchInHotbar) {
sendKeyPress(hudKeyHandled);
if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){
//Prevent double tapping Event on two different slots
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F);
}
theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 350);
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
lastHotbarKey = hudKeyHandled;
break;
}
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
prevX = e.getX();
prevY = e.getY();
if (CallbackBridge.isGrabbing()) {
currentPointerID = e.getPointerId(0);
// It cause hold left mouse while moving camera
initialX = mouse_x;
initialY = mouse_y;
theHandler.sendEmptyMessageDelayed(MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
}
lastHotbarKey = hudKeyHandled;
break;
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
shouldBeDown = false;
currentPointerID = -1;
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
isTouchInHotbar = hudKeyHandled != -1;
// We only treat in world events
if (!CallbackBridge.isGrabbing()) break;
// Stop the dropping of items
if (isTouchInHotbar) {
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q, 0, false);
theHandler.removeMessages(MSG_DROP_ITEM_BUTTON_CHECK);
break;
}
// Remove the mouse left button
if(triggeredLeftMouseButton){
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
triggeredLeftMouseButton = false;
break;
}
theHandler.removeMessages(MSG_LEFT_MOUSE_BUTTON_CHECK);
// In case of a short click, just send a quick right click
if(!LauncherPreferences.PREF_DISABLE_GESTURES &&
MathUtils.dist(initialX, initialY, mouse_x, mouse_y) < FINGER_STILL_THRESHOLD){
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, true);
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
}
break;
case MotionEvent.ACTION_POINTER_DOWN: // 5
//TODO Hey we could have some sort of middle click detection ?
scrollLastInitialX = e.getX();
scrollLastInitialY = e.getY();
//Checking if we are pressing the hotbar to select the item
hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1));
if(hudKeyHandled != -1){
sendKeyPress(hudKeyHandled);
if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){
//Prevent double tapping Event on two different slots
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F);
}
}
lastHotbarKey = hudKeyHandled;
break;
}
// Actualise the pointer count
lastPointerCount = e.getPointerCount();
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
}
/**
* The event for mouse/joystick movements
* We don't do the gamepad right now.
*/
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
int mouseCursorIndex = -1;
if(Gamepad.isGamepadEvent(event)){
if(gamepad == null){
gamepad = new Gamepad(this, event.getDevice());
}
gamepad.update(event);
return true;
}
for(int i = 0; i < event.getPointerCount(); i++) {
if(event.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
// Mouse found
mouseCursorIndex = i;
break;
}
if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice!
if(CallbackBridge.isGrabbing()) {
if(BaseMainActivity.isAndroid8OrHigher()){
requestFocus();
requestPointerCapture();
}
}
switch(event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_MOVE:
mouse_x = (event.getX(mouseCursorIndex) * scaleFactor);
mouse_y = (event.getY(mouseCursorIndex) * scaleFactor);
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
case MotionEvent.ACTION_SCROLL:
CallbackBridge.sendScroll((double) event.getAxisValue(MotionEvent.AXIS_VSCROLL), (double) event.getAxisValue(MotionEvent.AXIS_HSCROLL));
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
return sendMouseButtonUnconverted(event.getActionButton(),true);
case MotionEvent.ACTION_BUTTON_RELEASE:
return sendMouseButtonUnconverted(event.getActionButton(),false);
default:
return false;
}
}
//TODO MOVE THIS SOMEWHERE ELSE
private boolean debugErrored = false;
/** The input event for mouse with a captured pointer */
@RequiresApi(26)
@Override
public boolean dispatchCapturedPointerEvent(MotionEvent e) {
mouse_x += (e.getX()*scaleFactor);
mouse_y += (e.getY()*scaleFactor);
CallbackBridge.mouseX = mouse_x;
CallbackBridge.mouseY = mouse_y;
if(!CallbackBridge.isGrabbing()){
releasePointerCapture();
clearFocus();
}
if (pointerDebugText.getVisibility() == View.VISIBLE && !debugErrored) {
StringBuilder builder = new StringBuilder();
try {
builder.append("PointerCapture debug\n");
builder.append("MotionEvent=").append(e.getActionMasked()).append("\n");
builder.append("PressingBtn=").append(MotionEvent.class.getDeclaredMethod("buttonStateToString").invoke(null, e.getButtonState())).append("\n\n");
builder.append("PointerX=").append(e.getX()).append("\n");
builder.append("PointerY=").append(e.getY()).append("\n");
builder.append("RawX=").append(e.getRawX()).append("\n");
builder.append("RawY=").append(e.getRawY()).append("\n\n");
builder.append("XPos=").append(mouse_x).append("\n");
builder.append("YPos=").append(mouse_y).append("\n\n");
builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n");
builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n");
} catch (Throwable th) {
debugErrored = true;
builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th));
} finally {
pointerDebugText.setText(builder.toString());
builder.setLength(0);
}
}
pointerDebugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
return sendMouseButtonUnconverted(e.getActionButton(), true);
case MotionEvent.ACTION_BUTTON_RELEASE:
return sendMouseButtonUnconverted(e.getActionButton(), false);
case MotionEvent.ACTION_SCROLL:
CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL));
return true;
default:
return false;
}
}
/** The event for keyboard/ gamepad button inputs */
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
//Toast.makeText(this, event.toString(),Toast.LENGTH_SHORT).show();
//Toast.makeText(this, event.getDevice().toString(), Toast.LENGTH_SHORT).show();
//Filtering useless events by order of probability
if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true;
int eventKeycode = event.getKeyCode();
if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true;
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false;
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false;
if(event.getRepeatCount() != 0) return true;
if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true;
//Toast.makeText(this, "FIRST VERIF PASSED", Toast.LENGTH_SHORT).show();
//Sometimes, key events comes from SOME keys of the software keyboard
//Even weirder, is is unknown why a key or another is selected to trigger a keyEvent
if((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD){
if(eventKeycode == KeyEvent.KEYCODE_ENTER) return true; //We already listen to it.
touchCharInput.dispatchKeyEvent(event);
return true;
}
//Toast.makeText(this, "SECOND VERIF PASSED", Toast.LENGTH_SHORT).show();
//Sometimes, key events may come from the mouse
if(event.getDevice() != null
&& ( (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE
|| (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ){
//Toast.makeText(this, "THE EVENT COMES FROM A MOUSE", Toast.LENGTH_SHORT).show();
if(eventKeycode == KeyEvent.KEYCODE_BACK){
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, event.getAction() == KeyEvent.ACTION_DOWN);
return true;
}
}
System.out.println(event);
if(Gamepad.isGamepadEvent(event)){
if(gamepad == null){
gamepad = new Gamepad(this, event.getDevice());
}
gamepad.update(event);
return true;
}
int index = EfficientAndroidLWJGLKeycode.getIndexByKey(eventKeycode);
if(index >= 0) {
//Toast.makeText(this,"THIS IS A KEYBOARD EVENT !", Toast.LENGTH_SHORT).show();
EfficientAndroidLWJGLKeycode.execKey(event, index);
return true;
}
return false;
}
/** Get the mouse direction as a string */
private String getMoving(float pos, boolean xOrY) {
if (pos == 0) return "STOPPED";
if (pos > 0) return xOrY ? "RIGHT" : "DOWN";
return xOrY ? "LEFT" : "UP";
}
/** Convert the mouse button, then send it
* @return Whether the event was processed
*/
public static boolean sendMouseButtonUnconverted(int button, boolean status) {
int glfwButton = -256;
switch (button) {
case MotionEvent.BUTTON_PRIMARY:
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT;
break;
case MotionEvent.BUTTON_TERTIARY:
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE;
break;
case MotionEvent.BUTTON_SECONDARY:
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT;
break;
}
if(glfwButton == -256) return false;
sendMouseButton(glfwButton, status);
return true;
}
/** @return the hotbar key, given the position. -1 if no key are pressed */
public int handleGuiBar(int x, int y) {
if (!CallbackBridge.isGrabbing()) return -1;
int barHeight = mcscale(20);
int barY = CallbackBridge.physicalHeight - barHeight;
if(y < barY) return -1;
int barWidth = mcscale(180);
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
if(x < barX || x >= barX + barWidth) return -1;
return hotbarKeys[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + barWidth, 0, 9)];
}
/** Return the size, given the UI scale size */
private int mcscale(int input) {
return (int)((GUIScale * input)/scaleFactor);
}
/** Toggle the pointerDebugText visibility state */
public void togglepointerDebugging() {
pointerDebugText.setVisibility(pointerDebugText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
}
/** A small interface called when the listener is ready for the first time */
public interface SurfaceReadyListener {
void isReady();
}
public void setSurfaceReadyListener(SurfaceReadyListener listener){
surfaceReadyListener = listener;
}
}

View file

@ -1,91 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.*;
import android.content.*;
import android.content.pm.*;
import android.content.res.*;
import android.os.*;
import androidx.core.app.*;
import android.util.*;
import java.io.*;
import java.text.*;
import java.util.*;
import net.kdt.pojavlaunch.utils.*;
public class PojavApplication extends Application
{
public static String CRASH_REPORT_TAG = "PojavCrashReport";
@Override
public void onCreate() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable th) {
boolean storagePermAllowed = Build.VERSION.SDK_INT < 23 || ActivityCompat.checkSelfPermission(PojavApplication.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
File crashFile = new File(storagePermAllowed ? Tools.DIR_GAME_HOME : Tools.DIR_DATA, "latestcrash.txt");
try {
// Write to file, since some devices may not able to show error
crashFile.getParentFile().mkdirs();
crashFile.createNewFile();
PrintStream crashStream = new PrintStream(crashFile);
crashStream.append("PojavLauncher crash report\n");
crashStream.append(" - Time: " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
crashStream.append(" - Device: " + Build.PRODUCT + " " + Build.MODEL + "\n");
crashStream.append(" - Android version: " + Build.VERSION.RELEASE + "\n");
crashStream.append(" - Crash stack trace:\n");
crashStream.append(" - Launcher version: " + BuildConfig.VERSION_NAME + "\n");
crashStream.append(Log.getStackTraceString(th));
crashStream.close();
} catch (Throwable th2) {
Log.e(CRASH_REPORT_TAG, " - Exception attempt saving crash stack trace:", th2);
Log.e(CRASH_REPORT_TAG, " - The crash stack trace was:", th);
}
FatalErrorActivity.showError(PojavApplication.this, crashFile.getAbsolutePath(), storagePermAllowed, th);
// android.os.Process.killProcess(android.os.Process.myPid());
BaseMainActivity.fullyExit();
}
});
try {
super.onCreate();
Tools.APP_NAME = getResources().getString(R.string.app_short_name);
Tools.DIR_DATA = getDir("files", MODE_PRIVATE).getParent();
//Tools.DIR_HOME_JRE = Tools.DIR_DATA + "/jre_runtime".replace("/data/user/0", "/data/data");
Tools.DIR_ACCOUNT_OLD = Tools.DIR_DATA + "/Users";
Tools.DIR_ACCOUNT_NEW = Tools.DIR_DATA + "/accounts";
// Tools.FILE_ACCOUNT_JSON = getFilesDir().getAbsolutePath() + "/account_profiles.json";
Tools.DEVICE_ARCHITECTURE = Architecture.getDeviceArchitecture();
//Force x86 lib directory for Asus x86 based zenfones
if(Architecture.isx86Device() && Architecture.is32BitsDevice()){
String originalJNIDirectory = getApplicationInfo().nativeLibraryDir;
getApplicationInfo().nativeLibraryDir = originalJNIDirectory.substring(0,
originalJNIDirectory.lastIndexOf("/"))
.concat("/x86");
}
} catch (Throwable th) {
Intent ferrorIntent = new Intent(this, FatalErrorActivity.class);
ferrorIntent.putExtra("throwable", th);
startActivity(ferrorIntent);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleUtils.setLocale(base));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LocaleUtils.setLocale(this);
}
}

View file

@ -1,392 +0,0 @@
package net.kdt.pojavlaunch;
import static android.os.Build.VERSION_CODES.P;
import static net.kdt.pojavlaunch.Tools.getFileName;
import static net.kdt.pojavlaunch.Tools.ignoreNotch;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
import android.animation.ValueAnimator;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Guideline;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.extra.ExtraListener;
import net.kdt.pojavlaunch.fragments.ConsoleFragment;
import net.kdt.pojavlaunch.fragments.CrashFragment;
import net.kdt.pojavlaunch.fragments.LauncherFragment;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PojavLauncherActivity extends BaseLauncherActivity
{
// An equivalent ViewPager2 adapter class
private static class ScreenSlidePagerAdapter extends FragmentStateAdapter {
public ScreenSlidePagerAdapter(FragmentActivity fa) {
super(fa);
}
@Override
public Fragment createFragment(int position) {
if (position == 0) return new LauncherFragment();
if (position == 1) return new ConsoleFragment();
if (position == 2) return new CrashFragment();
if (position == 3) return new LauncherPreferenceFragment();
return null;
}
@Override
public int getItemCount() {
return 4;
}
}
private TextView tvConnectStatus;
private Spinner accountSelector;
private ViewPager2 viewPager;
private final Button[] Tabs = new Button[4];
private View selectedTab;
private ImageView accountFaceImageView;
private Button logoutBtn; // MineButtons
private ExtraListener backPreferenceListener;
public PojavLauncherActivity() {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.launcher_main_v4);
if (true) {
final ProgressDialog barrier = new ProgressDialog(this);
barrier.setMessage(getString(R.string.global_waiting));
barrier.setProgressStyle(barrier.STYLE_SPINNER);
barrier.setCancelable(false);
barrier.show();
//final Uri uri = data.getData();
Uri uri = Uri.parse("android.resource://"+getPackageName()+"/"+R.raw.beta);
String path = uri.getPath();
//File modFile = new File(new URI(path));
barrier.setMessage(PojavLauncherActivity.this.getString(R.string.multirt_progress_caching));
Thread t = new Thread(()->{
try {
final String name = getFileName(this, uri);
final File modInstallerFile = new File(getCacheDir(), name);
FileOutputStream fos = new FileOutputStream(modInstallerFile);
IOUtils.copy(getContentResolver().openInputStream(uri), fos);
fos.close();
PojavLauncherActivity.this.runOnUiThread(() -> {
barrier.dismiss();
Intent intent = new Intent(PojavLauncherActivity.this, JavaGUILauncherActivity.class);
intent.putExtra("modFile", modInstallerFile);
startActivity(intent);
});
}catch(IOException e) {
Tools.showError(PojavLauncherActivity.this,e);
}
});
t.start();
}
//Boilerplate linking/initialisation
/*
viewPager = findViewById(R.id.launchermainTabPager);
selectedTab = findViewById(R.id.viewTabSelected);
tvConnectStatus = findViewById(R.id.launchermain_text_accountstatus);
accountFaceImageView = findViewById(R.id.launchermain_account_image);
accountSelector = findViewById(R.id.launchermain_spinner_account);
mVersionSelector = findViewById(R.id.launchermain_spinner_version);
mLaunchProgress = findViewById(R.id.progressDownloadBar);
mLaunchTextStatus = findViewById(R.id.progressDownloadText);
logoutBtn = findViewById(R.id.installJarButton);
mPlayButton = findViewById(R.id.launchermainPlayButton);
Tabs[0] = findViewById(R.id.btnTab1);
Tabs[1] = findViewById(R.id.btnTab2);
Tabs[2] = findViewById(R.id.btnTab3);
Tabs[3] = findViewById(R.id.btnTab4);
*/
if (BuildConfig.DEBUG) {
Toast.makeText(this, "Launcher process id: " + android.os.Process.myPid(), Toast.LENGTH_LONG).show();
}
// Setup the viewPager to slide across fragments
viewPager.setAdapter(new ScreenSlidePagerAdapter(this));
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
setTabActive(position);
}
});
initTabs(0);
//Setup listener to the backPreference system
backPreferenceListener = (key, value) -> {
if(value.equals("true")){
onBackPressed();
ExtraCore.setValue(key, "false");
}
return false;
};
ExtraCore.addExtraListener("back_preference", backPreferenceListener);
// Try to load the temporary account
final List<String> accountList = new ArrayList<>();
final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent();
if (tempProfile != null) {
accountList.add(tempProfile.username);
}
for (String s : new File(Tools.DIR_ACCOUNT_NEW).list()) {
accountList.add(s.substring(0, s.length() - 5));
}
// Setup account spinner
pickAccount();
ArrayAdapter<String> adapterAcc = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, accountList);
adapterAcc.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
accountSelector.setAdapter(adapterAcc);
if (tempProfile != null) {
accountSelector.setSelection(0);
} else {
for (int i = 0; i < accountList.size(); i++) {
String account = accountList.get(i);
if (account.equals(mProfile.username)) {
accountSelector.setSelection(i);
}
}
}
accountSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView<?> p1, View p2, int position, long p4) {
if (tempProfile != null && position == 0) {
PojavProfile.setCurrentProfile(PojavLauncherActivity.this, tempProfile);
} else {
PojavProfile.setCurrentProfile(PojavLauncherActivity.this,
accountList.get(position + (tempProfile != null ? 1 : 0)));
}
pickAccount();
}
@Override
public void onNothingSelected(AdapterView<?> p1) {
// TODO: Implement this method
}
});
// Setup the minecraft version list
List<String> versions = new ArrayList<>();
final File fVers = new File(Tools.DIR_HOME_VERSION);
try {
if (fVers.listFiles().length < 1) {
throw new Exception(getString(R.string.error_no_version));
}
for (File fVer : fVers.listFiles()) {
if (fVer.isDirectory())
versions.add(fVer.getName());
}
} catch (Exception e) {
versions.add(getString(R.string.global_error) + ":");
versions.add(e.getMessage());
} finally {
mAvailableVersions = versions.toArray(new String[0]);
}
//mAvailableVersions;
ArrayAdapter<String> adapterVer = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mAvailableVersions);
adapterVer.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mVersionSelector.setAdapter(adapterVer);
statusIsLaunching(false);
//Add the preference changed listener
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
if(key.equals("hideSidebar")){
changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false));
return;
}
if(key.equals("ignoreNotch")){
ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this);
return;
}
});
changeLookAndFeel(PREF_HIDE_SIDEBAR);
}
private void selectTabPage(int pageIndex){
viewPager.setCurrentItem(pageIndex);
setTabActive(pageIndex);
}
private void pickAccount() {
try {
mProfile = PojavProfile.getCurrentProfileContent(this);
accountFaceImageView.setImageBitmap(mProfile.getSkinFace());
//TODO FULL BACKGROUND LOGIN
tvConnectStatus.setText(mProfile.accessToken.equals("0") ? R.string.mcl_account_offline : R.string.mcl_account_connected);
} catch(Exception e) {
mProfile = new MinecraftAccount();
Tools.showError(this, e, true);
}
}
public void statusIsLaunching(boolean isLaunching) {
int launchVisibility = isLaunching ? View.VISIBLE : View.GONE;
mLaunchProgress.setVisibility(launchVisibility);
mLaunchTextStatus.setVisibility(launchVisibility);
logoutBtn.setEnabled(!isLaunching);
mVersionSelector.setEnabled(!isLaunching);
canBack = !isLaunching;
}
public void onTabClicked(View view) {
for(int i=0; i<Tabs.length;i++){
if(view.getId() == Tabs[i].getId()) {
selectTabPage(i);
return;
}
}
}
private void setTabActive(int index){
for (Button tab : Tabs) {
tab.setTypeface(null, Typeface.NORMAL);
tab.setTextColor(Color.rgb(220,220,220)); //Slightly less bright white.
}
Tabs[index].setTypeface(Tabs[index].getTypeface(), Typeface.BOLD);
Tabs[index].setTextColor(Color.WHITE);
//Animating the white bar on the left
ValueAnimator animation = ValueAnimator.ofFloat(selectedTab.getY(), Tabs[index].getY()+(Tabs[index].getHeight()- selectedTab.getHeight())/2f);
animation.setDuration(250);
animation.addUpdateListener(animation1 -> selectedTab.setY((float) animation1.getAnimatedValue()));
animation.start();
}
protected void initTabs(int activeTab){
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> {
//Do something after 100ms
selectTabPage(activeTab);
});
}
private void changeLookAndFeel(boolean useOldLook){
Guideline guideLine = findViewById(R.id.guidelineLeft);
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) guideLine.getLayoutParams();
if(useOldLook || getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
//UI v1 Style
//Hide the sidebar
params.guidePercent = 0; // 0%, range: 0 <-> 1
guideLine.setLayoutParams(params);
//Remove the selected Tab and the head image
selectedTab.setVisibility(View.GONE);
accountFaceImageView.setVisibility(View.GONE);
//Enlarge the button, but just a bit.
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
params.matchConstraintPercentWidth = 0.35f;
}else{
//UI v2 Style
//Show the sidebar back
params.guidePercent = 0.23f; // 23%, range: 0 <-> 1
guideLine.setLayoutParams(params);
//Show the selected Tab
selectedTab.setVisibility(View.VISIBLE);
accountFaceImageView.setVisibility(View.VISIBLE);
//Set the default button size
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
params.matchConstraintPercentWidth = 0.25f;
}
mPlayButton.setLayoutParams(params);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
//Try to get the notch so it can be taken into account in settings
if (Build.VERSION.SDK_INT >= P){
try {
PREF_NOTCH_SIZE = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout().getBoundingRects().get(0).width();
}catch (Exception e){
Log.i("NOTCH DETECTION", "No notch detected, or the device if in split screen mode");
PREF_NOTCH_SIZE = -1;
}
Tools.updateWindowSize(this);
}
}
/**
* Custom back stack system. Use the classic backstack when the focus is on the setting screen,
* finish the activity and remove the back_preference listener otherwise
*/
@Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if(count > 0 && viewPager.getCurrentItem() == 3){
getSupportFragmentManager().popBackStack();
}else{
super.onBackPressed();
//additional code
ExtraCore.removeExtraListenerFromValue("back_preference", backPreferenceListener);
finish();
}
}
}

View file

@ -1,60 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.*;
import java.io.*;
import net.kdt.pojavlaunch.value.*;
import org.apache.commons.io.FileUtils;
public class PojavMigrator
{
public static void migrateAccountData(Context ctx) {
File oldAccDir = new File(Tools.DIR_ACCOUNT_OLD);
if (oldAccDir.exists() && oldAccDir.isDirectory()) {
for (String account : oldAccDir.list()) {
File oldAccFile = new File(oldAccDir, account);
try {
MCProfile.Builder oldAccStruct = MCProfile.load(oldAccFile.getAbsolutePath());
MinecraftAccount newAccStruct = new MinecraftAccount();
newAccStruct.accessToken = oldAccStruct.getAccessToken();
newAccStruct.clientToken = oldAccStruct.getClientID();
newAccStruct.isMicrosoft = false;
newAccStruct.profileId = oldAccStruct.getProfileID();
newAccStruct.selectedVersion = oldAccStruct.getVersion();
newAccStruct.username = oldAccStruct.getUsername();
newAccStruct.save();
} catch (IOException e) {
Tools.showError(ctx, e);
}
oldAccFile.delete();
}
}
}
public static boolean migrateGameDir() throws IOException {
File oldGameDir = new File(Tools.DIR_GAME_OLD);
boolean moved = oldGameDir.exists() && oldGameDir.isDirectory();
/*
if (!migrateBugFix20201217() && moved) {
command("mv " + Tools.DIR_GAME_OLD + " " + Tools.DIR_GAME_HOME + "/");
}
*/
if(moved) {
oldGameDir.renameTo(new File(Tools.DIR_GAME_NEW + "/"));
FileUtils.deleteDirectory(new File(Tools.DIR_GAME_NEW + "/lwjgl3"));
}
return moved;
}
private static void command(String cmd) throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec(cmd);
int exitCode = p.waitFor();
if (exitCode != 0) {
throw new IOException("Exit code " + exitCode +
", message:\n" + Tools.read(p.getErrorStream()));
}
}
}

View file

@ -1,111 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.IOException;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshTokenTask;
import net.kdt.pojavlaunch.value.MinecraftAccount;
public class PojavProfile
{
private static String PROFILE_PREF = "pojav_profile";
private static String PROFILE_PREF_FILE = "file";
public static String PROFILE_PREF_TEMP_CONTENT = "tempContent";
public static SharedPreferences getPrefs(Context ctx) {
return ctx.getSharedPreferences(PROFILE_PREF, Context.MODE_PRIVATE);
}
public static MinecraftAccount getCurrentProfileContent(Context ctx) throws JsonSyntaxException {
MinecraftAccount build = MinecraftAccount.load(getCurrentProfileName(ctx));
if (build == null) {
System.out.println("isTempProfile null? " + (getTempProfileContent() == null));
return getTempProfileContent();
}
return build;
}
public static MinecraftAccount getTempProfileContent() {
try {
MinecraftAccount acc = MinecraftAccount.parse(Tools.read(Tools.DIR_DATA+"/cache/tempacc.json"));
if (acc.accessToken == null) {
acc.accessToken = "0";
}
if (acc.clientToken == null) {
acc.clientToken = "0";
}
if (acc.profileId == null) {
acc.profileId = "00000000-0000-0000-0000-000000000000";
}
if (acc.username == null) {
acc.username = "0";
}
if (acc.selectedVersion == null) {
acc.selectedVersion = "1.7.10";
}
if (acc.msaRefreshToken == null) {
acc.msaRefreshToken = "0";
}
return acc;
}catch (IOException e) {
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the temporary profile",e);
return null;
}
}
public static String getCurrentProfileName(Context ctx) {
String name = getPrefs(ctx).getString(PROFILE_PREF_FILE, "");
// A dirty fix
if (!name.isEmpty() && name.startsWith(Tools.DIR_ACCOUNT_NEW) && name.endsWith(".json")) {
name = name.substring(0, name.length() - 5).replace(Tools.DIR_ACCOUNT_NEW, "").replace(".json", "");
setCurrentProfile(ctx, name);
}
return name;
}
public static boolean setCurrentProfile(Context ctx, Object obj) {
SharedPreferences.Editor pref = getPrefs(ctx).edit();
try {
if (obj instanceof MinecraftAccount) {
try {
MinecraftAccount.saveTempAccount((MinecraftAccount) obj);
} catch (IOException e) {
Tools.showError(ctx, e);
}
} else if (obj instanceof String) {
String acc = (String) obj;
pref.putString(PROFILE_PREF_FILE, acc);
MinecraftAccount.clearTempAccount();
} else if (obj == null) {
pref.putString(PROFILE_PREF_FILE, "");
} else {
throw new IllegalArgumentException("Profile must be MinecraftAccount.class, String.class or null");
}
} finally {
return pref.commit();
}
}
public static boolean isFileType(Context ctx) {
return new File(Tools.DIR_ACCOUNT_NEW + "/" + PojavProfile.getCurrentProfileName(ctx) + ".json").exists();
}
public static void launch(Activity ctx, Object o) {
PojavProfile.setCurrentProfile(ctx, o);
Intent intent = new Intent(ctx, PojavLauncherActivity.class); //MCLauncherActivity.class);
ctx.startActivity(intent);
}
public static void updateTokens(final Activity ctx, final String name, RefreshListener listen) throws Exception {
new RefreshTokenTask(ctx, listen).execute(name);
}
}

View file

@ -1,104 +0,0 @@
package net.kdt.pojavlaunch.authenticator.microsoft;
import android.app.*;
import android.content.*;
import android.os.*;
import java.lang.ref.WeakReference;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.authenticator.mojang.*;
import net.kdt.pojavlaunch.value.*;
public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
/*
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
*/
//private Gson gson = new Gson();
private final RefreshListener listener;
private final WeakReference<Context> ctx;
private ProgressDialog build;
public MicrosoftAuthTask(Context ctx, RefreshListener listener) {
this.ctx = new WeakReference<>(ctx);
this.listener = listener;
}
@Override
public void onPreExecute() {
build = new ProgressDialog(ctx.get());
build.setMessage(ctx.get().getString(R.string.global_waiting));
build.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
build.setCancelable(false);
build.setMax(6);
build.show();
}
@Override
public Object doInBackground(String... args) {
try {
/*
publishProgress();
String msaAccessToken = acquireAccessToken(args[0]);
publishProgress();
String xblToken = acquireXBLToken(msaAccessToken);
publishProgress();
String[] xstsData = acquireXsts(xblToken);
publishProgress();
String mcAccessToken = acquireMinecraftToken(xstsData[0], xstsData[1]);
publishProgress();
*/
Msa msa = new Msa(this, Boolean.parseBoolean(args[0]), args[1]);
MinecraftAccount acc = MinecraftAccount.load(msa.mcName);
if (msa.doesOwnGame) {
acc.clientToken = "0"; /* FIXME */
acc.accessToken = msa.mcToken;
acc.username = msa.mcName;
acc.profileId = msa.mcUuid;
acc.isMicrosoft = true;
acc.msaRefreshToken = msa.msRefreshToken;
acc.updateSkinFace();
}
acc.save();
return acc;
} catch (Throwable e) {
return e;
}
}
public void publishProgressPublic() {
super.publishProgress();
}
@Override
protected void onProgressUpdate(Void[] p1) {
super.onProgressUpdate(p1);
build.setProgress(build.getProgress() + 1);
}
@Override
public void onPostExecute(Object result) {
build.dismiss();
if (result instanceof MinecraftAccount) {
listener.onSuccess((MinecraftAccount) result);
} else {
listener.onFailed((Throwable) result);
}
}
}

View file

@ -1,296 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package net.kdt.pojavlaunch.authenticator.microsoft;
import android.util.*;
import java.io.*;
import java.net.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
import org.json.*;
public class Msa {
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
private MicrosoftAuthTask task;
public boolean isRefresh;
public String msRefreshToken;
public String mcName;
public String mcToken;
public String mcUuid;
public boolean doesOwnGame;
public Msa(MicrosoftAuthTask task, boolean isRefresh, String authCode) throws IOException, JSONException {
this.task = task;
acquireAccessToken(isRefresh, authCode);
}
public void acquireAccessToken(boolean isRefresh, String authcode) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(authTokenUrl);
Log.i("MicroAuth", "isRefresh=" + isRefresh + ", authCode= "+authcode);
Map<Object, Object> data = new HashMap<>();
/*Map.of(
"client_id", "00000000402b5328",
"code", authcode,
"grant_type", "authorization_code",
"redirect_uri", "https://login.live.com/oauth20_desktop.srf",
"scope", "service::user.auth.xboxlive.com::MBI_SSL"
);*/
data.put("client_id", "00000000402b5328");
data.put(isRefresh ? "refresh_token" : "code", authcode);
data.put("grant_type", isRefresh ? "refresh_token" : "authorization_code");
data.put("redirect_url", "https://login.live.com/oauth20_desktop.srf");
data.put("scope", "service::user.auth.xboxlive.com::MBI_SSL");
//да пошла yf[eq1 она ваша джава 11
String req = ofFormData(data);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes("UTF-8"));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
msRefreshToken = jo.getString("refresh_token");
Log.i("MicroAuth","Acess Token = "+jo.getString("access_token"));
acquireXBLToken(jo.getString("access_token"));
}else{
throwResponseError(conn);
}
}
private void acquireXBLToken(String accessToken) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(xblAuthUrl);
Map<Object, Object> data = new HashMap<>();
Map<Object, Object> properties = new HashMap<>();
properties.put("AuthMethod", "RPS");
properties.put("SiteName", "user.auth.xboxlive.com");
properties.put("RpsTicket", accessToken);
data.put("Properties",properties);
data.put("RelyingParty", "http://auth.xboxlive.com");
data.put("TokenType", "JWT");
/*Map.of(
"Properties", Map.of(
"AuthMethod", "RPS",
"SiteName", "user.auth.xboxlive.com",
"RpsTicket", accessToken
),
"RelyingParty", "http://auth.xboxlive.com",
"TokenType", "JWT"
);*/
String req = ofJSONData(data);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes("UTF-8"));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
Log.i("MicroAuth","Xbl Token = "+jo.getString("Token"));
acquireXsts(jo.getString("Token"));
}else{
throwResponseError(conn);
}
}
private void acquireXsts(String xblToken) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(xstsAuthUrl);
Map<Object, Object> data = new HashMap<>();
Map<Object, Object> properties = new HashMap<>();
properties.put("SandboxId", "RETAIL");
properties.put("UserTokens",Collections.singleton(xblToken));
data.put("Properties",properties);
data.put("RelyingParty", "rp://api.minecraftservices.com/");
data.put("TokenType", "JWT");
/*Map<Object, Object> data = Map.of(
"Properties", Map.of(
"SandboxId", "RETAIL",
"UserTokens", List.of(xblToken)
),
"RelyingParty", "rp://api.minecraftservices.com/",
"TokenType", "JWT"
);
*/
String req = ofJSONData(data);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes("UTF-8"));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
String uhs = jo.getJSONObject("DisplayClaims").getJSONArray("xui").getJSONObject(0).getString("uhs");
Log.i("MicroAuth","Xbl Xsts = "+jo.getString("Token")+"; Uhs = " + uhs);
acquireMinecraftToken(uhs,jo.getString("Token"));
}else{
throwResponseError(conn);
}
}
private void acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(mcLoginUrl);
Map<Object, Object> data = new HashMap<>();
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
String req = ofJSONData(data);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes("UTF-8"));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
Log.i("MicroAuth","MC token: "+jo.getString("access_token"));
mcToken = jo.getString("access_token");
checkMcProfile(jo.getString("access_token"));
checkMcStore(jo.getString("access_token"));
}else{
throwResponseError(conn);
}
}
private void checkMcStore(String mcAccessToken) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(mcStoreUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
conn.setRequestMethod("GET");
conn.setUseCaches(false);
conn.connect();
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
JSONArray ja = jo.getJSONArray("items");
Log.i("MicroAuth","Store Len = " + ja.length());
for(int i = 0; i < ja.length(); i++) {
String prod = ja.getJSONObject(i).getString("name");
Log.i("MicroAuth","Product " + i +": " +prod);
}
}else{
throwResponseError(conn);
}
}
private void checkMcProfile(String mcAccessToken) throws IOException, JSONException {
task.publishProgressPublic();
URL url = new URL(mcProfileUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
conn.setUseCaches(false);
conn.connect();
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
String s= Tools.read(conn.getInputStream());
Log.i("MicroAuth","profile:" + s);
JSONObject jsonObject = new JSONObject(s);
String name = (String) jsonObject.get("name");
String uuid = (String) jsonObject.get("id");
String uuidDashes = uuid .replaceFirst(
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
);
doesOwnGame = true;
Log.i("MicroAuth","UserName = " + name);
Log.i("MicroAuth","Uuid Minecraft = " + uuidDashes);
mcName=name;
mcUuid=uuidDashes;
}else{
Log.i("MicroAuth","It seems that this Microsoft Account does not own the game.");
doesOwnGame = false;
throwResponseError(conn);
}
}
public static String ofJSONData(Map<Object, Object> data) {
return new JSONObject(data).toString();
}
public static String ofFormData(Map<Object, Object> data) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<Object, Object> entry : data.entrySet()) {
if (builder.length() > 0) {
builder.append("&");
}
try {
builder.append(URLEncoder.encode(entry.getKey().toString(), "UTF-8"));
builder.append("=");
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
//Should not happen
}
}
return builder.toString();
}
private static void throwResponseError(HttpURLConnection conn) throws IOException {
String otherErrStr = "";
String errStr = Tools.read(conn.getErrorStream());
Log.i("MicroAuth","Error code: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + "\n" + errStr);
if (errStr.contains("NOT_FOUND") &&
errStr.contains("The server has not found anything matching the request URI"))
{
// TODO localize this
otherErrStr = "It seems that this Microsoft Account does not own the game. Make sure that you have bought/migrated to your Microsoft account.";
}
throw new RuntimeException(otherErrStr + "\n\nMSA Error: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + ", error stream:\n" + errStr);
}
}

View file

@ -1,66 +0,0 @@
package net.kdt.pojavlaunch.authenticator.microsoft.ui;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import net.kdt.pojavlaunch.R;
public class MicrosoftLoginGUIActivity extends AppCompatActivity {
public static final int AUTHENTICATE_MICROSOFT_REQUEST = 60;
WebView webView;
ProgressDialog waitDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
waitDialog = new ProgressDialog(this);
waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
waitDialog.setIndeterminate(true);
waitDialog.setMessage(getString(R.string.global_waiting));
webView = new WebView(this);
webView.setWebViewClient(new WebViewTrackClient());
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.loadUrl("https://login.live.com/oauth20_authorize.srf" +
"?client_id=00000000402b5328" +
"&response_type=code" +
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
"&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf");
setContentView(webView);
}
class WebViewTrackClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.startsWith("ms-xal-00000000402b5328")) {
Intent data = new Intent();
data.setData(Uri.parse(url));
if(waitDialog.isShowing()) waitDialog.dismiss();
setResult(Activity.RESULT_OK,data);
finish();
return true;
}else{
return super.shouldOverrideUrlLoading(view, url);
}
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//super.onPageStarted(view, url, favicon);
if(!waitDialog.isShowing()) waitDialog.show();
}
@Override
public void onPageFinished(WebView view, String url) {
waitDialog.hide();
}
}
}

View file

@ -1,50 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang;
import android.content.*;
import android.os.*;
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
import java.io.*;
import java.lang.ref.WeakReference;
import java.util.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.value.*;
public class InvalidateTokenTask extends AsyncTask<String, Void, Throwable> {
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
//private Gson gson = new Gson();
private MinecraftAccount profilePath;
private final WeakReference<Context> ctx;
private String path;
public InvalidateTokenTask(Context ctx) {
this.ctx = new WeakReference<>(ctx);
}
@Override
public Throwable doInBackground(String... args) {
path = args[0];
try {
this.profilePath = MinecraftAccount.load(args[0]);
if (profilePath.accessToken.equals("0")) {
return null;
}
this.authenticator.invalidate(profilePath.accessToken,
UUID.fromString(profilePath.isMicrosoft ? profilePath.profileId : profilePath.clientToken /* should be? */));
return null;
} catch (Throwable e) {
return e;
}
}
@Override
public void onPostExecute(Throwable result) {
if (result != null) {
Tools.showError(ctx.get(), result);
}
new File(Tools.DIR_ACCOUNT_NEW + "/" + path + ".json").delete();
}
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang;
public interface LoginListener
{
public void onBeforeLogin();
public void onLoginDone(String[] result);
}

View file

@ -1,71 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang;
import android.os.*;
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
public class LoginTask extends AsyncTask<String, Void, Void>
{
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
//private String TAG = "MojangAuth-login";
private LoginListener listener;
public LoginTask setLoginListener(LoginListener listener) {
this.listener = listener;
return this;
}
private UUID getRandomUUID() {
return UUID.randomUUID();
}
@Override
protected void onPreExecute() {
listener.onBeforeLogin();
super.onPreExecute();
}
@Override
protected Void doInBackground(String[] args) {
ArrayList<String> str = new ArrayList<String>();
str.add("ERROR");
try{
try{
AuthenticateResponse response = authenticator.authenticate(args[0], args[1], getRandomUUID());
if (response.selectedProfile == null) {
str.add("Can't login a demo account!\n");
} else {
if (new File(Tools.DIR_ACCOUNT_NEW + "/" + response.selectedProfile.name + ".json").exists()) {
str.add("This account already exist!\n");
} else {
str.add(response.accessToken); // Access token
str.add(response.clientToken.toString()); // Client token
str.add(response.selectedProfile.id); // Profile ID
str.add(response.selectedProfile.name); // Username
str.set(0, "NORMAL");
}
}
}
//MainActivity.updateStatus(804);
catch(Throwable e){
str.add(e.getMessage());
}
}
catch(Exception e){
str.add(e.getMessage());
}
listener.onLoginDone(str.toArray(new String[0]));
return null;
}
@Override
protected void onPostExecute(Void result) {
// listener.onLoginDone(result);
super.onPostExecute(result);
}
}

View file

@ -1,9 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang;
import net.kdt.pojavlaunch.value.*;
public interface RefreshListener
{
public void onFailed(Throwable e);
public void onSuccess(MinecraftAccount profile);
}

View file

@ -1,77 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang;
import android.content.*;
import android.os.*;
import java.lang.ref.WeakReference;
import java.util.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
import android.app.*;
import net.kdt.pojavlaunch.value.*;
public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
//private Gson gson = new Gson();
private RefreshListener listener;
private MinecraftAccount profilePath;
private final WeakReference<Context> ctx;
private ProgressDialog build;
public RefreshTokenTask(Context ctx, RefreshListener listener) {
this.ctx = new WeakReference<>(ctx);
this.listener = listener;
}
@Override
public void onPreExecute() {
build = new ProgressDialog(ctx.get());
build.setMessage(ctx.get().getString(R.string.global_waiting));
build.setProgressStyle(ProgressDialog.STYLE_SPINNER);
build.setCancelable(false);
build.show();
}
@Override
public Throwable doInBackground(String... args) {
try {
this.profilePath = MinecraftAccount.load(args[0]);
int responseCode = 400;
try {
responseCode = this.authenticator.validate(profilePath.accessToken).statusCode;
}catch(RuntimeException e) {}
if (responseCode == 403) {
RefreshResponse response = this.authenticator.refresh(profilePath.accessToken, UUID.fromString(profilePath.clientToken));
if (response == null) {
// Refresh when offline?
return null;
} else if (response.selectedProfile == null) {
throw new IllegalArgumentException("Can't refresh a demo account!");
}
profilePath.clientToken = response.clientToken.toString();
profilePath.accessToken = response.accessToken;
profilePath.username = response.selectedProfile.name;
profilePath.profileId = response.selectedProfile.id;
}
profilePath.updateSkinFace();
profilePath.save();
return null;
} catch (Throwable e) {
return e;
}
}
@Override
public void onPostExecute(Throwable result) {
build.dismiss();
if (result == null) {
listener.onSuccess(profilePath);
} else {
listener.onFailed(result);
}
}
}

View file

@ -1,24 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import java.util.UUID;
public class AuthenticateRequest {
public AgentInfo agent = new AgentInfo();
public UUID clientToken;
public String password;
public String username;
public static class AgentInfo {
public String name;
public int version;
}
public AuthenticateRequest(String username, String password, UUID clientToken, String clientName, int clientVersion) {
this.username = username;
this.password = password;
this.clientToken = clientToken;
this.agent.name = clientName;
this.agent.version = clientVersion;
}
}

View file

@ -1,11 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import java.util.UUID;
public class AuthenticateResponse {
public String accessToken;
public Profile[] availableProfiles;
public UUID clientToken;
public Profile selectedProfile;
}

View file

@ -1,8 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
public class ErrorResponse {
public String cause;
public String error;
public String errorMessage;
}

View file

@ -1,21 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
public class NetworkResponse
{
public final int statusCode;
public final Object response;
public NetworkResponse(int statusCode, Object response) {
this.statusCode = statusCode;
this.response = response;
}
public void throwExceptionIfNeed(String msg) {
if (statusCode < 200 || statusCode >= 300) {
throw new RuntimeException(msg);
}
}
public void throwExceptionIfNeed() {
throwExceptionIfNeed("Respone error, code: " + statusCode + ", message: " + response);
}
}

View file

@ -1,8 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
public class Profile {
public String id;
public boolean legacy;
public String name;
}

View file

@ -1,14 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import java.util.UUID;
public class RefreshRequest {
public String accessToken;
public UUID clientToken;
public RefreshRequest(String accessToken, UUID clientToken) {
this.accessToken = accessToken;
this.clientToken = clientToken;
}
}

View file

@ -1,10 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import java.util.UUID;
public class RefreshResponse {
public String accessToken;
public UUID clientToken;
public Profile selectedProfile;
}

View file

@ -1,122 +0,0 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import android.util.*;
import java.io.*;
import java.net.*;
import java.nio.charset.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
import org.apache.commons.io.*;
public class YggdrasilAuthenticator {
private static final String API_URL = "https://authserver.mojang.com/";
private String clientName = "Minecraft";
private int clientVersion = 1;
private NetworkResponse makeRequest(String endpoint, Object inputObject, Class<?> responseClass) throws IOException, Throwable {
Throwable th;
InputStream is = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
String requestJson = Tools.GLOBAL_GSON.toJson(inputObject);
try {
URL url = new URL(API_URL + endpoint);
OutputStream os;
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "Minecraft");
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.connect();
os = null;
os = conn.getOutputStream();
os.write(requestJson.getBytes(Charset.forName("UTF-8")));
if (os != null) {
os.close();
}
int statusCode = conn.getResponseCode();
if (statusCode != 200) {
is = conn.getErrorStream();
} else {
is = conn.getInputStream();
}
if (is != null) {
IOUtils.copy(is, bos);
try {
is.close();
} catch (Exception e) {
throw e;
}
}
String outString = new String(bos.toByteArray(), Charset.forName("UTF-8"));
if (statusCode == 200 || statusCode == 204){
Log.i("Result", "Task " + endpoint + " successful");
if (responseClass != null) {
return new NetworkResponse(statusCode, Tools.GLOBAL_GSON.fromJson(outString, responseClass));
}
} else {
Log.i("Result", "Task " + endpoint + " failure");
}
return new NetworkResponse(statusCode, outString);
} catch (UnknownHostException e) {
if (endpoint.equals("refresh")) {
return null;
} else {
throw new RuntimeException("Can't connect to the server", e);
}
} catch (Throwable th2) {
th = th2;
if (is != null) {
try {
is.close();
} catch (Exception e2) {
e2.addSuppressed(th2);
throw e2;
}
}
throw th;
}
} catch (Throwable th3) {
th = th3;
if (is != null) {
is.close();
}
throw th;
}
}
public AuthenticateResponse authenticate(String username, String password, UUID clientId) throws IOException, Throwable {
NetworkResponse obj = makeRequest("authenticate", new AuthenticateRequest(username, password, clientId, this.clientName, this.clientVersion), AuthenticateResponse.class);
/*
if (obj.statusCode != 200) {
throw new RuntimeException("Invalid username or password, status code: " + obj.statusCode);
}
*/
obj.throwExceptionIfNeed();
return (AuthenticateResponse) obj.response;
}
public RefreshResponse refresh(String authToken, UUID clientId) throws IOException, Throwable {
NetworkResponse obj = makeRequest("refresh", new RefreshRequest(authToken, clientId), RefreshResponse.class);
if (obj == null) {
return null;
} else {
obj.throwExceptionIfNeed(); // "Invalid username or password, status code: " + obj.statusCode);
return (RefreshResponse) obj.response;
}
}
public NetworkResponse validate(String authToken) throws Throwable {
return makeRequest("validate", new RefreshRequest(authToken, null), null);
}
public NetworkResponse invalidate(String authToken, UUID clientId) throws Throwable {
return makeRequest("invalidate", new RefreshRequest(authToken, clientId), null);
}
}

View file

@ -1,313 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import android.util.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.*;
import net.objecthunter.exp4j.*;
import net.objecthunter.exp4j.function.Function;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import androidx.annotation.Keep;
@Keep
public class ControlData {
public static final int SPECIALBTN_KEYBOARD = -1;
public static final int SPECIALBTN_TOGGLECTRL = -2;
public static final int SPECIALBTN_MOUSEPRI = -3;
public static final int SPECIALBTN_MOUSESEC = -4;
public static final int SPECIALBTN_VIRTUALMOUSE = -5;
public static final int SPECIALBTN_MOUSEMID = -6;
public static final int SPECIALBTN_SCROLLUP = -7;
public static final int SPECIALBTN_SCROLLDOWN = -8;
private static ControlData[] SPECIAL_BUTTONS;
private static String[] SPECIAL_BUTTON_NAME_ARRAY;
// Internal usage only
public boolean isHideable;
private static WeakReference<ExpressionBuilder> builder = new WeakReference<>(null);
private static WeakReference<Field> expression = new WeakReference<>(null);
private static WeakReference<ArrayMap<String , String>> conversionMap = new WeakReference<>(null);
static {
bypassExpressionBuilder();
buildConversionMap();
}
/**
* Both fields below are dynamic position data, auto updates
* X and Y position, unlike the original one which uses fixed
* position, so it does not provide auto-location when a control
* is made on a small device, then import the control to a
* bigger device or vice versa.
*/
public String dynamicX, dynamicY;
public boolean isDynamicBtn, isToggle, passThruEnabled;
public static ControlData[] getSpecialButtons(){
if (SPECIAL_BUTTONS == null) {
SPECIAL_BUTTONS = new ControlData[]{
new ControlData("Keyboard", new int[]{SPECIALBTN_KEYBOARD}, "${margin} * 3 + ${width} * 2", "${margin}", false),
new ControlData("GUI", new int[]{SPECIALBTN_TOGGLECTRL}, "${margin}", "${bottom} - ${margin}"),
new ControlData("PRI", new int[]{SPECIALBTN_MOUSEPRI}, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("SEC", new int[]{SPECIALBTN_MOUSESEC}, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("Mouse", new int[]{SPECIALBTN_VIRTUALMOUSE}, "${right}", "${margin}", false),
new ControlData("MID", new int[]{SPECIALBTN_MOUSEMID}, "${margin}", "${margin}"),
new ControlData("SCROLLUP", new int[]{SPECIALBTN_SCROLLUP}, "${margin}", "${margin}"),
new ControlData("SCROLLDOWN", new int[]{SPECIALBTN_SCROLLDOWN}, "${margin}", "${margin}")
};
}
return SPECIAL_BUTTONS;
}
public static String[] buildSpecialButtonArray() {
if (SPECIAL_BUTTON_NAME_ARRAY == null) {
List<String> nameList = new ArrayList<String>();
for (ControlData btn : getSpecialButtons()) {
nameList.add(btn.name);
}
SPECIAL_BUTTON_NAME_ARRAY = nameList.toArray(new String[0]);
}
return SPECIAL_BUTTON_NAME_ARRAY;
}
public String name;
private float width; //Dp instead of Px now
private float height; //Dp instead of Px now
public int[] keycodes; //Should store up to 4 keys
public float opacity; //Alpha value from 0 to 1;
public int bgColor;
public int strokeColor;
public int strokeWidth; //0-100%
public float cornerRadius; //0-100%
public boolean isSwipeable;
public ControlData() {
this("button");
}
public ControlData(String name){
this(name, new int[] {});
}
public ControlData(String name, int[] keycodes) {
this(name, keycodes, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2);
}
public ControlData(String name, int[] keycodes, float x, float y) {
this(name, keycodes, x, y, 50, 50);
}
public ControlData(android.content.Context ctx, int resId, int[] keycodes, float x, float y, boolean isSquare) {
this(ctx.getResources().getString(resId), keycodes, x, y, isSquare);
}
public ControlData(String name, int[] keycodes, float x, float y, boolean isSquare) {
this(name, keycodes, x, y, isSquare ? 50 : 80, isSquare ? 50 : 30);
}
public ControlData(String name, int[] keycodes, float x, float y, float width, float height) {
this(name, keycodes, Float.toString(x), Float.toString(y), width, height, false);
this.isDynamicBtn = false;
}
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY) {
this(name, keycodes, dynamicX, dynamicY, 50, 50, false);
}
public ControlData(android.content.Context ctx, int resId, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
this(ctx.getResources().getString(resId), keycodes, dynamicX, dynamicY, isSquare);
}
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
this(name, keycodes, dynamicX, dynamicY, isSquare ? 50 : 80, isSquare ? 50 : 30, false);
}
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle){
this(name, keycodes, dynamicX, dynamicY, width, height, isToggle, 1,0x4D000000, 0xFFFFFFFF,0,0);
}
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle, float opacity, int bgColor, int strokeColor, int strokeWidth, float cornerRadius) {
this.name = name;
this.keycodes = inflateKeycodeArray(keycodes);
this.dynamicX = dynamicX;
this.dynamicY = dynamicY;
this.width = width;
this.height = height;
this.isDynamicBtn = false;
this.isToggle = isToggle;
this.opacity = opacity;
this.bgColor = bgColor;
this.strokeColor = strokeColor;
this.strokeWidth = strokeWidth;
this.cornerRadius = cornerRadius;
}
//Deep copy constructor
public ControlData(ControlData controlData){
this(
controlData.name,
controlData.keycodes,
controlData.dynamicX,
controlData.dynamicY,
controlData.width,
controlData.height,
controlData.isToggle,
controlData.opacity,
controlData.bgColor,
controlData.strokeColor,
controlData.strokeWidth,
controlData.cornerRadius
);
}
public void execute(boolean isDown) {
for(int keycode : keycodes){
sendKeyPress(keycode, 0, isDown);
}
}
public float insertDynamicPos(String dynamicPos) {
// Insert value to ${variable}
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, fillConversionMap());
// Calculate, because the dynamic position contains some math equations
return calculate(insertedPos);
}
private static float calculate(String math) {
setExpression(math);
return (float) builder.get().build().evaluate();
}
private static int[] inflateKeycodeArray(int[] keycodes){
int[] inflatedArray = new int[]{GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN};
System.arraycopy(keycodes, 0, inflatedArray, 0, keycodes.length);
return inflatedArray;
}
public boolean containsKeycode(int keycodeToCheck){
for(int keycode : keycodes)
if(keycodeToCheck == keycode)
return true;
return false;
}
//Getters || setters (with conversion for ease of use)
public float getWidth() {
return Tools.dpToPx(width);
}
public float getHeight(){
return Tools.dpToPx(height);
}
public void setWidth(float widthInPx){
width = Tools.pxToDp(widthInPx);
}
public void setHeight(float heightInPx){
height = Tools.pxToDp(heightInPx);
}
/**
* Create a weak reference to a builder and its expression field.
* Although VERY bad practice it isn't slower due to saved GC time.
* The normal way requires us to create ONE builder and TWO functions for EACH button.
*/
private static void bypassExpressionBuilder(){
ExpressionBuilder expressionBuilder = new ExpressionBuilder("1 + 1")
.function(new Function("dp", 1) {
@Override
public double apply(double... args) {
return Tools.pxToDp((float) args[0]);
}
})
.function(new Function("px", 1) {
@Override
public double apply(double... args) {
return Tools.dpToPx((float) args[0]);
}
});
builder = new WeakReference<>(expressionBuilder);
try {
expression = new WeakReference<>(builder.get().getClass().getDeclaredField("expression"));
expression.get().setAccessible(true);
expression.get().set(expression.get(), expression.get().getModifiers() & ~Modifier.FINAL);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* wrapper for the WeakReference to the expressionField.
* @param stringExpression the expression to set.
*/
private static void setExpression(String stringExpression){
if(builder.get() == null) bypassExpressionBuilder();
try {
expression.get().set(builder.get(), stringExpression);
}catch (IllegalAccessException e){}
}
/**
* Build a shared conversion map without the ControlData dependent values
* You need to set the view dependent values before using it.
*/
private static void buildConversionMap() {
// Values in the map below may be always changed
ArrayMap<String, String> keyValueMap = new ArrayMap<>(10);
keyValueMap.put("top", "0");
keyValueMap.put("left", "0");
keyValueMap.put("right", "DUMMY_RIGHT");
keyValueMap.put("bottom", "DUMMY_BOTTOM");
keyValueMap.put("width", "DUMMY_WIDTH");
keyValueMap.put("height", "DUMMY_HEIGHT");
keyValueMap.put("screen_width", Integer.toString(CallbackBridge.physicalWidth));
keyValueMap.put("screen_height", Integer.toString(CallbackBridge.physicalHeight));
keyValueMap.put("margin", Integer.toString((int) Tools.dpToPx(2)));
keyValueMap.put("preferred_scale", Float.toString(LauncherPreferences.PREF_BUTTONSIZE));
conversionMap = new WeakReference<>(keyValueMap);
}
/**
* Fill the conversionMap with controlData dependent values.
* The returned valueMap should NOT be kept in memory.
* @return the valueMap to use.
*/
private Map<String, String> fillConversionMap(){
ArrayMap<String, String> valueMap = conversionMap.get();
if (valueMap == null){
buildConversionMap();
valueMap = conversionMap.get();
}
valueMap.put("right", Float.toString(CallbackBridge.physicalWidth - getWidth()));
valueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - getHeight()));
valueMap.put("width", Float.toString(getWidth()));
valueMap.put("height", Float.toString(getHeight()));
return valueMap;
}
}

View file

@ -1,85 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import net.kdt.pojavlaunch.Tools;
import java.util.ArrayList;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.DOWN;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.LEFT;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.RIGHT;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.UP;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.FREE;
import androidx.annotation.Keep;
@Keep
public class ControlDrawerData {
public ArrayList<ControlData> buttonProperties;
public ControlData properties;
public Orientation orientation;
@androidx.annotation.Keep
public enum Orientation {
DOWN,
LEFT,
UP,
RIGHT,
FREE
}
public static Orientation[] getOrientations(){
return new Orientation[]{DOWN,LEFT,UP,RIGHT,FREE};
}
public static int orientationToInt(Orientation orientation){
switch (orientation){
case DOWN: return 0;
case LEFT: return 1;
case UP: return 2;
case RIGHT: return 3;
case FREE: return 4;
}
return -1;
}
public static Orientation intToOrientation(int by){
switch (by){
case 0: return DOWN;
case 1: return LEFT;
case 2: return UP;
case 3: return RIGHT;
case 4: return FREE;
}
return null;
}
public ControlDrawerData(){
this(new ArrayList<>());
}
public ControlDrawerData(ArrayList<ControlData> buttonProperties){
this(buttonProperties, new ControlData("Drawer", new int[] {}, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2));
}
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties){
this(buttonProperties, properties, Orientation.LEFT);
}
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties, Orientation orientation){
this.buttonProperties = buttonProperties;
this.properties = properties;
this.orientation = orientation;
}
public ControlDrawerData(ControlDrawerData drawerData){
buttonProperties = new ArrayList<>(drawerData.buttonProperties.size());
for(ControlData controlData : drawerData.buttonProperties){
buttonProperties.add(new ControlData(controlData));
}
properties = new ControlData(drawerData.properties);
orientation = drawerData.orientation;
}
}

View file

@ -1,299 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import android.content.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import com.google.gson.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import net.kdt.pojavlaunch.customcontrols.handleview.HandleView;
import net.kdt.pojavlaunch.prefs.*;
public class ControlLayout extends FrameLayout
{
protected CustomControls mLayout;
private boolean mModifiable;
private CustomControlsActivity mActivity;
private boolean mControlVisible = false;
public ControlLayout(Context ctx) {
super(ctx);
}
public ControlLayout(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
public void hideAllHandleViews() {
for(ControlButton button : getButtonChildren()){
HandleView hv = button.getHandleView();
if(hv != null) hv.hide();
}
}
public void loadLayout(String jsonPath) throws IOException, JsonSyntaxException {
CustomControls layout = LayoutConverter.loadAndConvertIfNecessary(jsonPath);
if(layout != null) {
loadLayout(layout);
}else{
throw new IOException("Unsupported control layout version");
}
}
public void loadLayout(CustomControls controlLayout) {
if (mModifiable)
hideAllHandleViews();
removeAllButtons();
if(mLayout != null) {
mLayout.mControlDataList = null;
mLayout = null;
}
System.gc();
mapTable.clear();
// Cleanup buttons only when input layout is null
if (controlLayout == null) return;
mLayout = controlLayout;
//CONTROL BUTTON
for (ControlData button : controlLayout.mControlDataList) {
addControlView(button);
}
//CONTROL DRAWER
for(ControlDrawerData drawerData : controlLayout.mDrawerDataList){
ControlDrawer drawer = addDrawerView(drawerData);
if(mModifiable) drawer.areButtonsVisible = true;
}
mLayout.scaledAt = LauncherPreferences.PREF_BUTTONSIZE;
setModified(false);
} // loadLayout
//CONTROL BUTTON
public void addControlButton(ControlData controlButton) {
mLayout.mControlDataList.add(controlButton);
addControlView(controlButton);
}
private void addControlView(ControlData controlButton) {
final ControlButton view = new ControlButton(this, controlButton);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
view.setFocusableInTouchMode(false);
}
addView(view);
setModified(true);
}
// CONTROL DRAWER
public void addDrawer(ControlDrawerData drawerData){
mLayout.mDrawerDataList.add(drawerData);
addDrawerView();
}
private void addDrawerView(){
addDrawerView(null);
}
private ControlDrawer addDrawerView(ControlDrawerData drawerData){
final ControlDrawer view = new ControlDrawer(this,drawerData == null ? mLayout.mDrawerDataList.get(mLayout.mDrawerDataList.size()-1) : drawerData);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
view.setFocusableInTouchMode(false);
}
addView(view);
//CONTROL SUB BUTTON
for (ControlData subButton : view.getDrawerData().buttonProperties) {
addSubView(view, subButton);
}
setModified(true);
return view;
}
//CONTROL SUB-BUTTON
public void addSubButton(ControlDrawer drawer, ControlData controlButton){
//Yep there isn't much here
drawer.getDrawerData().buttonProperties.add(controlButton);
addSubView(drawer, drawer.getDrawerData().buttonProperties.get(drawer.getDrawerData().buttonProperties.size()-1 ));
}
public void addSubView(ControlDrawer drawer, ControlData controlButton){
final ControlSubButton view = new ControlSubButton(this, controlButton, drawer);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
view.setFocusableInTouchMode(false);
}else{
view.setVisible(drawer.areButtonsVisible);
}
drawer.addButton(view);
addView(view);
setModified(true);
}
private void removeAllButtons() {
for(View v : getButtonChildren()){
removeView(v);
}
System.gc();
//i wanna be sure that all the removed Views will be removed after a reload
//because if frames will slowly go down after many control changes it will be warm and bad
}
public void removeControlButton(ControlButton controlButton) {
mLayout.mControlDataList.remove(controlButton.getProperties());
controlButton.setVisibility(View.GONE);
removeView(controlButton);
setModified(true);
}
public void removeControlDrawer(ControlDrawer controlDrawer){
for(ControlSubButton subButton : controlDrawer.buttons){
subButton.setVisibility(GONE);
removeView(subButton);
}
mLayout.mDrawerDataList.remove(controlDrawer.getDrawerData());
controlDrawer.setVisibility(GONE);
removeView(controlDrawer);
setModified(true);
}
public void removeControlSubButton(ControlSubButton subButton){
subButton.parentDrawer.drawerData.buttonProperties.remove(subButton.getProperties());
subButton.parentDrawer.buttons.remove(subButton);
subButton.parentDrawer.syncButtons();
subButton.setVisibility(GONE);
removeView(subButton);
}
public void saveLayout(String path) throws Exception {
mLayout.save(path);
setModified(false);
}
public void setActivity(CustomControlsActivity activity) {
mActivity = activity;
}
public void toggleControlVisible(){
mControlVisible = !mControlVisible;
setControlVisible(mControlVisible);
}
public float getLayoutScale(){
return mLayout.scaledAt;
}
public void setControlVisible(boolean isVisible) {
if (mModifiable) return; // Not using on custom controls activity
mControlVisible = isVisible;
for(ControlButton button : getButtonChildren()){
button.setVisible(isVisible);
}
}
public void setModifiable(boolean isModifiable) {
mModifiable = isModifiable;
for(ControlButton button : getButtonChildren()){
button.setModifiable(isModifiable);
if (!isModifiable)
button.setAlpha(button.getProperties().opacity);
}
}
public boolean getModifiable(){
return mModifiable;
}
public void setModified(boolean isModified) {
if (mActivity != null) mActivity.isModified = isModified;
}
public ArrayList<ControlButton> getButtonChildren(){
ArrayList<ControlButton> children = new ArrayList<>();
for(int i=0; i<getChildCount(); ++i){
View v = getChildAt(i);
if(v instanceof ControlButton)
children.add(((ControlButton) v));
}
return children;
}
HashMap<View, ControlButton> mapTable = new HashMap<>();
//While this is called onTouch, this should only be called from a ControlButton.
public boolean onTouch(View v, MotionEvent ev) {
ControlButton lastControlButton = mapTable.get(v);
//Check if the action is cancelling, reset the lastControl button associated to the view
if(ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL){
if(lastControlButton != null) lastControlButton.sendKeyPresses(false);
mapTable.put(v, null);
return true;
}
if(ev.getActionMasked() != MotionEvent.ACTION_MOVE) return false;
//Optimization pass to avoid looking at all children again
if(lastControlButton != null){
if( ev.getRawX() > lastControlButton.getX() && ev.getRawX() < lastControlButton.getX() + lastControlButton.getWidth() &&
ev.getRawY() > lastControlButton.getY() && ev.getRawY() < lastControlButton.getY() + lastControlButton.getHeight()){
return true;
}
}
//Release last keys
if (lastControlButton != null) lastControlButton.sendKeyPresses(false);
mapTable.put(v, null);
//Look for another SWIPEABLE button
for(ControlButton button : getButtonChildren()){
if(!button.getProperties().isSwipeable) continue;
if( ev.getRawX() > button.getX() && ev.getRawX() < button.getX() + button.getWidth() &&
ev.getRawY() > button.getY() && ev.getRawY() < button.getY() + button.getHeight()){
//Press the new key
if(!button.equals(lastControlButton)){
button.sendKeyPresses(true);
mapTable.put(v, button);
}
return true;
}
}
return false;
}
}

View file

@ -1,65 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import android.content.*;
import androidx.annotation.Keep;
import java.io.IOException;
import java.util.*;
import net.kdt.pojavlaunch.*;
@Keep
public class CustomControls {
public int version = -1;
public float scaledAt;
public List<ControlData> mControlDataList;
public List<ControlDrawerData> mDrawerDataList;
public CustomControls() {
this(new ArrayList<>(), new ArrayList<>());
}
public CustomControls(List<ControlData> mControlDataList, List<ControlDrawerData> mDrawerDataList) {
this.mControlDataList = mControlDataList;
this.mDrawerDataList = mDrawerDataList;
this.scaledAt = 100f;
}
// Generate default control
public CustomControls(Context ctx) {
this();
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[0])); // Keyboard
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[1])); // GUI
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[2])); // Primary Mouse mControlDataList
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[3])); // Secondary Mouse mControlDataList
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[4])); // Virtual mouse toggle
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F3}, "${margin}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, new int[]{LWJGLGLFWKeycode.GLFW_KEY_T}, "${margin} * 2 + ${width}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB}, "${margin} * 4 + ${width} * 3", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F5}, "${margin}", "${height} + ${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, new int[]{LWJGLGLFWKeycode.GLFW_KEY_W}, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, new int[]{LWJGLGLFWKeycode.GLFW_KEY_A}, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, new int[]{LWJGLGLFWKeycode.GLFW_KEY_S}, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, new int[]{LWJGLGLFWKeycode.GLFW_KEY_D}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, new int[]{LWJGLGLFWKeycode.GLFW_KEY_E}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
ControlData shiftData = new ControlData(ctx, R.string.control_shift, new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT}, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
shiftData.isToggle = true;
this.mControlDataList.add(shiftData);
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE}, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
//The default controls are conform to the V2
version = 4;
}
public void save(String path) throws IOException {
//Current version is the V2.4 so the version as to be marked as 4 !
version = 4;
Tools.write(path, Tools.GLOBAL_GSON.toJson(this));
}
}

View file

@ -1,130 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import com.google.gson.JsonSyntaxException;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.Tools;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.lwjgl.glfw.CallbackBridge;
import java.io.IOException;
import java.util.ArrayList;
public class LayoutConverter {
public static boolean convertLookType = false; //false = flat; true = classic
public static CustomControls loadAndConvertIfNecessary(String jsonPath) throws IOException, JsonSyntaxException {
String jsonLayoutData = Tools.read(jsonPath);
try {
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
if(!layoutJobj.has("version")) { //v1 layout
CustomControls layout = LayoutConverter.convertV1Layout(layoutJobj);
layout.save(jsonPath);
return layout;
}else if (layoutJobj.getInt("version") == 2) {
CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj);
layout.save(jsonPath);
return layout;
}else if (layoutJobj.getInt("version") == 3 || layoutJobj.getInt("version") == 4) {
return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
}else{
return null;
}
}catch (JSONException e) {
throw new JsonSyntaxException("Failed to load",e);
}
}
public static CustomControls convertV2Layout(JSONObject oldLayoutJson) throws JSONException {
CustomControls layout = Tools.GLOBAL_GSON.fromJson(oldLayoutJson.toString(), CustomControls.class);
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
layout.mControlDataList = new ArrayList<>(layoutMainArray.length());
for(int i = 0; i < layoutMainArray.length(); i++) {
JSONObject button = layoutMainArray.getJSONObject(i);
ControlData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlData.class);
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
double buttonC = button.getDouble("x");
double ratio = buttonC/CallbackBridge.physicalWidth;
n_button.dynamicX = ratio + " * ${screen_width}";
}
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
double buttonC = button.getDouble("y");
double ratio = buttonC/CallbackBridge.physicalHeight;
n_button.dynamicY = ratio + " * ${screen_height}";
}
layout.mControlDataList.add(n_button);
}
JSONArray layoutDrawerArray = oldLayoutJson.getJSONArray("mDrawerDataList");
layout.mDrawerDataList = new ArrayList<>();
for(int i = 0; i < layoutDrawerArray.length(); i++) {
JSONObject button = layoutDrawerArray.getJSONObject(i);
JSONObject buttonProperties = button.getJSONObject("properties");
ControlDrawerData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlDrawerData.class);
if((n_button.properties.dynamicX == null || n_button.properties.dynamicX.isEmpty())&&buttonProperties.has("x")) {
double buttonC = buttonProperties.getDouble("x");
double ratio = buttonC/CallbackBridge.physicalWidth;
n_button.properties.dynamicX = ratio + " * ${screen_width}";
}
if((n_button.properties.dynamicY == null || n_button.properties.dynamicY.isEmpty())&&buttonProperties.has("y")) {
double buttonC = buttonProperties.getDouble("y");
double ratio = buttonC/CallbackBridge.physicalHeight;
n_button.properties.dynamicY = ratio + " * ${screen_height}";
}
layout.mDrawerDataList.add(n_button);
}
layout.version = 3;
return layout;
}
public static CustomControls convertV1Layout(JSONObject oldLayoutJson) throws JSONException {
CustomControls empty = new CustomControls();
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
for(int i = 0; i < layoutMainArray.length(); i++) {
JSONObject button = layoutMainArray.getJSONObject(i);
ControlData n_button = new ControlData();
int[] keycodes = new int[] {LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN};
n_button.isDynamicBtn = button.getBoolean("isDynamicBtn");
n_button.dynamicX = button.getString("dynamicX");
n_button.dynamicY = button.getString("dynamicY");
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
double buttonC = button.getDouble("x");
double ratio = buttonC/CallbackBridge.physicalWidth;
n_button.dynamicX = ratio + " * ${screen_width}";
}
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
double buttonC = button.getDouble("y");
double ratio = buttonC/CallbackBridge.physicalHeight;
n_button.dynamicY = ratio + " * ${screen_height}";
}
n_button.name = button.getString("name");
n_button.opacity = ((float)((button.getInt("transparency")-100)*-1))/100f;
n_button.passThruEnabled = button.getBoolean("passThruEnabled");
n_button.isToggle = button.getBoolean("isToggle");
n_button.setHeight(button.getInt("height"));
n_button.setWidth(button.getInt("width"));
if(convertLookType) {
n_button.strokeColor = 0xdd7f7f7f;
n_button.bgColor = 0x807f7f7f;
n_button.strokeWidth = 10;
}else{
n_button.bgColor = 0x4d000000;
n_button.strokeWidth = 0;
}
if(button.getBoolean("isRound")) { n_button.cornerRadius = 35f; }
int next_idx = 0;
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
keycodes[next_idx] = button.getInt("keycode");
n_button.keycodes = keycodes;
empty.mControlDataList.add(n_button);
}
empty.scaledAt = (float)oldLayoutJson.getDouble("scaledAt");
empty.version = 3;
return empty;
}
}

View file

@ -1,168 +0,0 @@
package net.kdt.pojavlaunch.customcontrols;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.R;
import org.lwjgl.glfw.CallbackBridge;
/**
* This class is intended for sending characters used in chat via the virtual keyboard
*/
public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText {
public TouchCharInput(@NonNull Context context) {
this(context, null);
}
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
private boolean isDoingInternalChanges = false;
/**
* We take the new chars, and send them to the game.
* If less chars are present, remove some.
* The text is always cleaned up.
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
if(isDoingInternalChanges)return;
for(int i=0; i< lengthBefore; ++i){
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
}
for(int i=start, count = 0; count < lengthAfter; ++i){
CallbackBridge.sendChar(text.charAt(i), 0);
++count;
}
//Reset the keyboard state
if(text.length() < 1) clear();
}
/**
* When we change from app to app, the keyboard gets disabled.
* So, we disable the object
*/
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
disable();
}
/**
* Intercepts the back key to disable focus
* Does not affect the rest of the activity.
*/
@Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
disable();
}
return super.onKeyPreIme(keyCode, event);
}
/**
* Toggle on and off the soft keyboard, depending of the state
*
* @return if the keyboard is set to be shown.
*/
public boolean switchKeyboardState(){
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
//If an hard keyboard is present, never trigger the soft one
if(hasFocus()
|| (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY
&& getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES)){
imm.hideSoftInputFromWindow(getWindowToken(), 0);
clear();
disable();
return false;
}else{
enable();
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
return true;
}
}
/**
* Clear the EditText from any leftover inputs
* It does not affect the in-game input
*/
@SuppressLint("SetTextI18n")
public void clear(){
isDoingInternalChanges = true;
//Braille space, doesn't trigger keyboard auto-complete
//replacing directly the text without though setText avoids notifying changes
setText(" ");
setSelection(getText().length());
isDoingInternalChanges = false;
}
/**
* Send the enter key.
*/
private void sendEnter(){
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ENTER);
clear();
}
/**
* Regain ability to exist, take focus and have some text being input
*/
public void enable(){
setEnabled(true);
setFocusable(true);
setVisibility(VISIBLE);
requestFocus();
}
/**
* Lose ability to exist, take focus and have some text being input
*/
public void disable(){
clear();
setVisibility(GONE);
clearFocus();
setEnabled(false);
}
/**
* This function deals with anything that has to be executed when the constructor is called
*/
private void setup(){
setOnEditorActionListener((textView, i, keyEvent) -> {
sendEnter();
clear();
disable();
return false;
});
clear();
disable();
}
}

View file

@ -1,559 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.buttons;
import android.annotation.SuppressLint;
import android.graphics.*;
import android.graphics.drawable.GradientDrawable;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.handleview.*;
import net.kdt.pojavlaunch.*;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
@SuppressLint("ViewConstructor")
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener
{
private final Paint mRectPaint = new Paint();;
protected GestureDetector mGestureDetector;
protected ControlData mProperties;
protected SelectionEndHandleView mHandleView;
protected boolean mModifiable = false;
protected boolean mCanTriggerLongClick = true;
protected boolean isToggled = false;
protected boolean isPointerOutOfBounds = false;
public ControlButton(ControlLayout layout, ControlData properties) {
super(layout.getContext());
setPadding(4, 4, 4, 4);
setOnLongClickListener(this);
//When a button is created, the width/height has yet to be processed to fit the scaling.
setProperties(preProcessProperties(properties, layout));
setModified(false);
}
public HandleView getHandleView() {
return mHandleView;
}
public ControlData getProperties() {
return mProperties;
}
public void setProperties(ControlData properties) {
setProperties(properties, true);
}
public ControlData preProcessProperties(ControlData properties, ControlLayout layout){
//When a button is created, properties have to be modified to fit the screen.
//Size
properties.setWidth(properties.getWidth() / layout.getLayoutScale() * PREF_BUTTONSIZE);
properties.setHeight(properties.getHeight() / layout.getLayoutScale() * PREF_BUTTONSIZE);
//Visibility
properties.isHideable = !properties.containsKeycode(ControlData.SPECIALBTN_TOGGLECTRL) && !properties.containsKeycode(ControlData.SPECIALBTN_VIRTUALMOUSE);
return properties;
}
public void setProperties(ControlData properties, boolean changePos) {
mProperties = properties;
if(mProperties.isToggle){
//For the toggle layer
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(R.attr.colorAccent, value, true);
mRectPaint.setColor(value.data);
mRectPaint.setAlpha(128);
}else{
mRectPaint.setColor(Color.WHITE);
mRectPaint.setAlpha(60);
}
setText(properties.name);
if (changePos) {
setX(properties.insertDynamicPos(mProperties.dynamicX));
setY(properties.insertDynamicPos(mProperties.dynamicY));
}
setLayoutParams(new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight() ));
}
public void setBackground(){
GradientDrawable gd = new GradientDrawable();
gd.setColor(mProperties.bgColor);
gd.setStroke(computeStrokeWidth(mProperties.strokeWidth), mProperties.strokeColor);
gd.setCornerRadius(computeCornerRadius(mProperties.cornerRadius));
setBackground(gd);
}
public void setModifiable(boolean isModifiable) {
mModifiable = isModifiable;
}
private void setModified(boolean modified) {
if (getParent() != null)
((ControlLayout) getParent()).setModified(modified);
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
mProperties.setWidth(params.width);
mProperties.setHeight(params.height);
setBackground();
// Re-calculate position
if(!mProperties.isDynamicBtn){
setX(getX());
setY(getY());
}else {
setX(mProperties.insertDynamicPos(mProperties.dynamicX));
setY(mProperties.insertDynamicPos(mProperties.dynamicY));
}
setModified(true);
}
public void setVisible(boolean isVisible){
if(mProperties.isHideable)
setVisibility(isVisible ? VISIBLE : GONE);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
setWillNotDraw(visibility == GONE);
}
@Override
public void setX(float x) {
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
x -= PREF_CONTROL_RIGHT_OFFSET;
}else{
x += PREF_CONTROL_LEFT_OFFSET;
}
super.setX(x);
setModified(true);
}
@Override
public void setY(float y) {
// We have to account for control offset preference
if(y - PREF_CONTROL_TOP_OFFSET + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y -= PREF_CONTROL_BOTTOM_OFFSET;
}else{
y += PREF_CONTROL_TOP_OFFSET;
}
super.setY(y);
setModified(true);
}
@Override
public float getX() {
float x = super.getX();
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > (CallbackBridge.physicalWidth)/2f){
x += PREF_CONTROL_RIGHT_OFFSET;
}else{
x -= PREF_CONTROL_LEFT_OFFSET;
}
return x;
}
@Override
public float getY(){
// We have to account for control offset preference
float y = super.getY();
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y += PREF_CONTROL_BOTTOM_OFFSET;
}else{
y -= PREF_CONTROL_TOP_OFFSET;
}
return y;
}
/**
* Apply the dynamic equation on the x axis.
* @param dynamicX The equation to compute the position from
*/
public void setDynamicX(String dynamicX){
mProperties.dynamicX = dynamicX;
setX(mProperties.insertDynamicPos(dynamicX));
}
/**
* Apply the dynamic equation on the y axis.
* @param dynamicY The equation to compute the position from
*/
public void setDynamicY(String dynamicY){
mProperties.dynamicY = dynamicY;
setY(mProperties.insertDynamicPos(dynamicY));
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param x The absolute position on the horizontal axis
* @return The equation as a String
*/
public String generateDynamicX(float x){
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
return (x + mProperties.getWidth()) / CallbackBridge.physicalWidth + " * ${screen_width} - ${width}";
}else{
return x / CallbackBridge.physicalWidth + " * ${screen_width}";
}
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param y The absolute position on the vertical axis
* @return The equation as a String
*/
public String generateDynamicY(float y){
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
return (y + mProperties.getHeight()) / CallbackBridge.physicalHeight + " * ${screen_height} - ${height}";
}else{
return y / CallbackBridge.physicalHeight + " * ${screen_height}";
}
}
public void updateProperties() {
setProperties(mProperties);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isToggled || (!mProperties.isToggle && isActivated()))
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), mProperties.cornerRadius, mProperties.cornerRadius, mRectPaint);
}
@Override
public boolean onLongClick(View v) {
if (mCanTriggerLongClick && mModifiable) {
//Instantiate on need only
if(mHandleView == null) mHandleView = new SelectionEndHandleView(this);
if (mHandleView.isShowing()) {
mHandleView.hide();
} else {
if (getParent() != null) {
((ControlLayout) getParent()).hideAllHandleViews();
}
try {
mHandleView.show(this);
} catch (Throwable th) {
th.printStackTrace();
}
}
}
return mCanTriggerLongClick;
}
protected float downX, downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable){
mCanTriggerLongClick = false;
switch (event.getActionMasked()){
case MotionEvent.ACTION_MOVE:
//Send the event to be taken as a mouse action
if(mProperties.passThruEnabled && CallbackBridge.isGrabbing()){
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
//If out of bounds
if(event.getX() < getLeft() || event.getX() > getRight() ||
event.getY() < getTop() || event.getY() > getBottom()){
if(mProperties.isSwipeable && !isPointerOutOfBounds){
//Remove keys
if(!triggerToggle()) {
sendKeyPresses(false);
}
}
isPointerOutOfBounds = true;
((ControlLayout) getParent()).onTouch(this, event);
break;
}
//Else if we now are in bounds
if(isPointerOutOfBounds) {
((ControlLayout) getParent()).onTouch(this, event);
//RE-press the button
if(mProperties.isSwipeable && !mProperties.isToggle){
sendKeyPresses(true);
}
}
isPointerOutOfBounds = false;
break;
case MotionEvent.ACTION_DOWN: // 0
case MotionEvent.ACTION_POINTER_DOWN: // 5
if(!mProperties.isToggle){
sendKeyPresses(true);
}
break;
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
case MotionEvent.ACTION_POINTER_UP: // 6
if(mProperties.passThruEnabled){
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
if(isPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
isPointerOutOfBounds = false;
if(!triggerToggle()) {
sendKeyPresses(false);
}
break;
default:
return false;
}
return true;
}
/* If the button can be modified/moved */
//Instantiate the gesture detector only when needed
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
if (mGestureDetector.onTouchEvent(event)) {
mCanTriggerLongClick = true;
onLongClick(this);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
mCanTriggerLongClick = true;
downX = event.getRawX() - getX();
downY = event.getRawY() - getY();
break;
case MotionEvent.ACTION_MOVE:
mCanTriggerLongClick = false;
if (!mProperties.isDynamicBtn) {
snapAndAlign(
MathUtils.clamp(event.getRawX() - downX, 0, CallbackBridge.physicalWidth - getWidth()),
MathUtils.clamp(event.getRawY() - downY, 0, CallbackBridge.physicalHeight - getHeight())
);
}
break;
}
return super.onTouchEvent(event);
}
/**
* Passe a series of checks to determine if the ControlButton is available to be snapped on.
*
* @param button The button to check
* @return whether or not the button
*/
protected boolean canSnap(ControlButton button){
float MIN_DISTANCE = Tools.dpToPx(8);
if(button == this) return false;
if(com.google.android.material.math.MathUtils.dist(
button.getX() + button.getWidth()/2f,
button.getY() + button.getHeight()/2f,
getX() + getWidth()/2f,
getY() + getHeight()/2f) > Math.max(button.getWidth()/2f + getWidth()/2f, button.getHeight()/2f + getHeight()/2f) + MIN_DISTANCE) return false;
return true;
}
/**
* Try to snap, then align to neighboring buttons, given the provided coordinates.
* The new position is automatically applied to the View,
* regardless of if the View snapped or not.
*
* The new position is always dynamic, thus replacing previous dynamic positions
*
* @param x Coordinate on the x axis
* @param y Coordinate on the y axis
*/
protected void snapAndAlign(float x, float y){
float MIN_DISTANCE = Tools.dpToPx(8);
String dynamicX = generateDynamicX(x);
String dynamicY = generateDynamicY(y);
setX(x);
setY(y);
for(ControlButton button : ((ControlLayout) getParent()).getButtonChildren()){
//Step 1: Filter unwanted buttons
if(!canSnap(button)) continue;
//Step 2: Get Coordinates
float button_top = button.getY();
float button_bottom = button_top + button.getHeight();
float button_left = button.getX();
float button_right = button_left + button.getWidth();
float top = getY();
float bottom = getY() + getHeight();
float left = getX();
float right = getX() + getWidth();
//Step 3: For each axis, we try to snap to the nearest
if(Math.abs(top - button_bottom) < MIN_DISTANCE){ // Bottom snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " + ${margin}" ;
}else if(Math.abs(button_top - bottom) < MIN_DISTANCE){ //Top snap
dynamicY = applySize(button.getProperties().dynamicY, button) + " - ${height} - ${margin}";
}
if(!dynamicY.equals(generateDynamicY(getY()))){ //If we snapped
if(Math.abs(button_left - left) < MIN_DISTANCE){ //Left align snap
dynamicX = applySize(button.getProperties().dynamicX, button);
}else if(Math.abs(button_right - right) < MIN_DISTANCE){ //Right align snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " - ${width}";
}
}
if(Math.abs(button_left - right) < MIN_DISTANCE){ //Left snap
dynamicX = applySize(button.getProperties().dynamicX, button) + " - ${width} - ${margin}";
}else if(Math.abs(left - button_right) < MIN_DISTANCE){ //Right snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " + ${margin}";
}
if(!dynamicX.equals(generateDynamicX(getX()))){ //If we snapped
if(Math.abs(button_top - top) < MIN_DISTANCE){ //Top align snap
dynamicY = applySize(button.getProperties().dynamicY, button);
}else if(Math.abs(button_bottom - bottom) < MIN_DISTANCE){ //Bottom align snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " - ${height}";
}
}
}
setDynamicX(dynamicX);
setDynamicY(dynamicY);
}
/**
* Do a pre-conversion of an equation using values from a button,
* so the variables can be used for another button
*
* Internal use only.
* @param equation The dynamic position as a String
* @param button The button to get the values from.
* @return The pre-processed equation as a String.
*/
private static String applySize(String equation, ControlButton button){
return equation
.replace("${right}", "(${screen_width} - ${width})")
.replace("${bottom}","(${screen_height} - ${height})")
.replace("${height}", "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})")
.replace("${width}", "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})");
}
public int computeStrokeWidth(float widthInPercent){
float maxSize = Math.max(mProperties.getWidth(), mProperties.getHeight());
return (int)((maxSize/2) * (widthInPercent/100));
}
public float computeCornerRadius(float radiusInPercent){
float minSize = Math.min(mProperties.getWidth(), mProperties.getHeight());
return (minSize/2) * (radiusInPercent/100);
}
public boolean triggerToggle(){
//returns true a the toggle system is triggered
if(mProperties.isToggle){
isToggled = !isToggled;
invalidate();
sendKeyPresses(isToggled);
return true;
}
return false;
}
public void sendKeyPresses(boolean isDown){
setActivated(isDown);
for(int keycode : mProperties.keycodes){
if(keycode >= GLFW_KEY_UNKNOWN){
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
CallbackBridge.setModifiers(keycode, isDown);
}else{
sendSpecialKey(keycode, isDown);
}
}
}
private void sendSpecialKey(int keycode, boolean isDown){
switch (keycode) {
case ControlData.SPECIALBTN_KEYBOARD:
if(isDown)BaseMainActivity.switchKeyboardState();
break;
case ControlData.SPECIALBTN_TOGGLECTRL:
if(isDown)MainActivity.mControlLayout.toggleControlVisible();
break;
case ControlData.SPECIALBTN_VIRTUALMOUSE:
if(isDown)BaseMainActivity.toggleMouse(getContext());
break;
case ControlData.SPECIALBTN_MOUSEPRI:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
break;
case ControlData.SPECIALBTN_MOUSEMID:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
break;
case ControlData.SPECIALBTN_MOUSESEC:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
break;
case ControlData.SPECIALBTN_SCROLLDOWN:
if (!isDown) CallbackBridge.sendScroll(0, 1d);
break;
case ControlData.SPECIALBTN_SCROLLUP:
if (!isDown) CallbackBridge.sendScroll(0, -1d);
break;
}
}
}

View file

@ -1,172 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.buttons;
import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.ViewGroup;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import java.util.ArrayList;
@SuppressLint("ViewConstructor")
public class ControlDrawer extends ControlButton {
public ArrayList<ControlSubButton> buttons;
public ControlDrawerData drawerData;
public ControlLayout mLayout;
public boolean areButtonsVisible;
public ControlDrawer(ControlLayout layout, ControlDrawerData drawerData) {
super(layout, drawerData.properties);
buttons = new ArrayList<>(drawerData.buttonProperties.size());
mLayout = layout;
this.drawerData = drawerData;
areButtonsVisible = layout.getModifiable();
}
public void addButton(ControlData properties){
addButton(new ControlSubButton(mLayout, properties, this));
}
public void addButton(ControlSubButton button){
buttons.add(button);
setControlButtonVisibility(button, mModifiable || areButtonsVisible);
syncButtons();
}
private void setControlButtonVisibility(ControlButton button, boolean isVisible){
button.setVisible(isVisible);
}
private void switchButtonVisibility(){
areButtonsVisible = !areButtonsVisible;
for(ControlButton button : buttons){
button.setVisible(areButtonsVisible);
}
}
//Syncing stuff
private void alignButtons(){
if(buttons == null) return;
if(drawerData.orientation == ControlDrawerData.Orientation.FREE) return;
for(int i=0; i < buttons.size(); ++i){
switch (drawerData.orientation){
case RIGHT:
buttons.get(i).setDynamicX(generateDynamicX(getX() + (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1) ));
buttons.get(i).setDynamicY(generateDynamicY(getY()));
break;
case LEFT:
buttons.get(i).setDynamicX(generateDynamicX(getX() - (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1)));
buttons.get(i).setDynamicY(generateDynamicY(getY()));
break;
case UP:
buttons.get(i).setDynamicY(generateDynamicY(getY() - (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
buttons.get(i).setDynamicX(generateDynamicX(getX()));
break;
case DOWN:
buttons.get(i).setDynamicY(generateDynamicY(getY() + (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
buttons.get(i).setDynamicX(generateDynamicX(getX()));
break;
}
buttons.get(i).updateProperties();
}
}
private void resizeButtons(){
if (buttons == null) return;
for(ControlSubButton subButton : buttons){
subButton.mProperties.setWidth(mProperties.getWidth());
subButton.mProperties.setHeight(mProperties.getHeight());
subButton.updateProperties();
}
}
public void syncButtons(){
alignButtons();
resizeButtons();
}
/**
* Check whether or not the button passed as a parameter belongs to this drawer.
*
* @param button The button to look for
* @return Whether the button is in the buttons list of the drawer.
*/
public boolean containsChild(ControlButton button){
for(ControlButton childButton : buttons){
if (childButton == button) return true;
}
return false;
}
@Override
public ControlData preProcessProperties(ControlData properties, ControlLayout layout) {
ControlData data = super.preProcessProperties(properties, layout);
data.isHideable = true;
return data;
}
@Override
public void setVisible(boolean isVisible) {
//TODO replicate changes to his children ?
setVisibility(isVisible ? VISIBLE : GONE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable){
switch (event.getActionMasked()){
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_POINTER_UP: // 6
switchButtonVisibility();
break;
}
return true;
}
return super.onTouchEvent(event);
}
@Override
protected boolean canSnap(ControlButton button) {
return super.canSnap(button) && !containsChild(button);
}
@Override
public void setX(float x) {
super.setX(x);
alignButtons();
}
@Override
public void setY(float y) {
super.setY(y);
alignButtons();
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
syncButtons();
}
//Getters
public ControlDrawerData getDrawerData() {
return drawerData;
}
}

View file

@ -1,62 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.buttons;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import net.kdt.pojavlaunch.SingleTapConfirm;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
public class ControlSubButton extends ControlButton {
public ControlDrawer parentDrawer;
public ControlSubButton(ControlLayout layout, ControlData properties, ControlDrawer parentDrawer) {
super(layout, properties);
this.parentDrawer = parentDrawer;
filterProperties();
}
private void filterProperties(){
mProperties.setHeight(parentDrawer.getProperties().getHeight());
mProperties.setWidth(parentDrawer.getProperties().getWidth());
mProperties.isDynamicBtn = false;
setProperties(mProperties, false);
}
@Override
public void setVisible(boolean isVisible) {
setVisibility(isVisible ? (parentDrawer.areButtonsVisible ? VISIBLE : GONE) : (!mProperties.isHideable && parentDrawer.getVisibility() == GONE) ? VISIBLE : View.GONE);
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
if(parentDrawer != null){
params.width = (int)parentDrawer.mProperties.getWidth();
params.height = (int)parentDrawer.mProperties.getHeight();
}
super.setLayoutParams(params);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
return super.onTouchEvent(event);
}
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
if (mGestureDetector.onTouchEvent(event)) {
mCanTriggerLongClick = true;
onLongClick(this);
}
return true;
}
}

View file

@ -1,412 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.content.Context;
import android.view.Choreographer;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_EAST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NONE;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_EAST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_WEST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_EAST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_WEST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_WEST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.isJoystickEvent;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
public class Gamepad {
/* Resolution scaler option, allow downsizing a window */
private final float scaleFactor = LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
/* Mouse positions, scaled by the scaleFactor */
private float mouse_x, mouse_y;
/* Sensitivity, adjusted according to screen size */
private final double sensitivityFactor = (1.4 * (1080f/ currentDisplayMetrics.heightPixels));
private final ImageView pointerView;
private final GamepadDpad gamepadDpad = new GamepadDpad();
private final GamepadJoystick leftJoystick;
private int currentJoystickDirection = DIRECTION_NONE;
private final GamepadJoystick rightJoystick;
private float lastHorizontalValue = 0.0f;
private float lastVerticalValue = 0.0f;
private final double mouseMaxAcceleration = 2f;
private double mouseMagnitude;
private double mouseAngle;
private double mouseSensitivity = 19;
private final GamepadMap gameMap = GamepadMap.getDefaultGameMap();
private final GamepadMap menuMap = GamepadMap.getDefaultMenuMap();
private GamepadMap currentMap = gameMap;
private boolean lastGrabbingState = true;
private final boolean mModifierDigitalTriggers;
private boolean mModifierSwappedAxis = true; //Triggers and right stick axis are swapped.
private final Choreographer screenChoreographer;
private long lastFrameTime;
public Gamepad(View contextView, InputDevice inputDevice){
screenChoreographer = Choreographer.getInstance();
Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
updateGrabbingState();
tick(frameTimeNanos);
screenChoreographer.postFrameCallback(this);
}
};
screenChoreographer.postFrameCallback(frameCallback);
lastFrameTime = System.nanoTime();
Toast.makeText(contextView.getContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
for(InputDevice.MotionRange range : inputDevice.getMotionRanges()){
if(range.getAxis() == MotionEvent.AXIS_RTRIGGER
|| range.getAxis() == MotionEvent.AXIS_LTRIGGER
|| range.getAxis() == MotionEvent.AXIS_GAS
|| range.getAxis() == MotionEvent.AXIS_BRAKE){
mModifierSwappedAxis = false;
break;
}
}
leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
if(!mModifierSwappedAxis)
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
else
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, inputDevice);
mModifierDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
Context ctx = contextView.getContext();
pointerView = new ImageView(contextView.getContext());
pointerView.setImageDrawable(ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.pointer, ctx.getTheme()));
pointerView.getDrawable().setFilterBitmap(false);
int size = (int) ((22 * getMcScale()) / scaleFactor);
pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size));
mouse_x = CallbackBridge.windowWidth/2;
mouse_y = CallbackBridge.windowHeight/2;
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
((ViewGroup)contextView.getParent()).addView(pointerView);
}
public void tick(long frameTimeNanos){
//update mouse position
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
double acceleration = (mouseMagnitude - currentJoystick.getDeadzone()) / (1 - currentJoystick.getDeadzone());
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
if(acceleration > 1) acceleration = 1;
// Compute delta since last tick time
float deltaX = (float) (Math.cos(mouseAngle) * acceleration * mouseSensitivity);
float deltaY = (float) (Math.sin(mouseAngle) * acceleration * mouseSensitivity);
float deltaTimeScale = ((frameTimeNanos - lastFrameTime) / 16666666f); // Scale of 1 = 60Hz
deltaX *= deltaTimeScale;
deltaY *= deltaTimeScale;
CallbackBridge.mouseX += deltaX;
CallbackBridge.mouseY -= deltaY;
if(!lastGrabbingState){
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
placePointerView((int) (CallbackBridge.mouseX / scaleFactor), (int) (CallbackBridge.mouseY/ scaleFactor));
}
mouse_x = CallbackBridge.mouseX;
mouse_y = CallbackBridge.mouseY;
//Send the mouse to the game
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
// Update last nano time
lastFrameTime = frameTimeNanos;
}
/** Update the grabbing state, and change the currentMap, mouse position and sensibility */
private void updateGrabbingState() {
boolean lastGrabbingValue = lastGrabbingState;
lastGrabbingState = CallbackBridge.isGrabbing();
if(lastGrabbingValue == lastGrabbingState) return;
// Switch grabbing state then
currentMap.resetPressedState();
if(lastGrabbingState){
currentMap = gameMap;
pointerView.setVisibility(View.INVISIBLE);
mouseSensitivity = 18;
return;
}
currentMap = menuMap;
sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing
mouse_x = CallbackBridge.windowWidth/2;
mouse_y = CallbackBridge.windowHeight/2;
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
pointerView.setVisibility(View.VISIBLE);
// Sensitivity in menu is MC and HARDWARE resolution dependent
mouseSensitivity = 19 * scaleFactor / sensitivityFactor;
}
public void update(KeyEvent event){
sendButton(event);
}
public void update(MotionEvent event){
updateDirectionalJoystick(event);
updateMouseJoystick(event);
updateAnalogTriggers(event);
int[] dpadEvent = gamepadDpad.convertEvent(event);
sendButton(dpadEvent[0], dpadEvent[1]);
}
private void updateMouseJoystick(MotionEvent event){
GamepadJoystick currentJoystick = lastGrabbingState ? rightJoystick : leftJoystick;
float horizontalValue = currentJoystick.getHorizontalAxis(event);
float verticalValue = currentJoystick.getVerticalAxis(event);
if(horizontalValue != lastHorizontalValue || verticalValue != lastVerticalValue){
lastHorizontalValue = horizontalValue;
lastVerticalValue = verticalValue;
mouseMagnitude = currentJoystick.getMagnitude(event);
mouseAngle = currentJoystick.getAngleRadian(event);
tick(System.nanoTime());
return;
}
lastHorizontalValue = horizontalValue;
lastVerticalValue = verticalValue;
mouseMagnitude = currentJoystick.getMagnitude(event);
mouseAngle = currentJoystick.getAngleRadian(event);
}
private void updateDirectionalJoystick(MotionEvent event){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
int lastJoystickDirection = currentJoystickDirection;
currentJoystickDirection = currentJoystick.getHeightDirection(event);
if(currentJoystickDirection == lastJoystickDirection) return;
sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap());
sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap());
}
private void updateAnalogTriggers(MotionEvent event){
if(!mModifierDigitalTriggers){
getCurrentMap().TRIGGER_LEFT.update(
(event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5)
|| (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5)
|| (mModifierSwappedAxis &&(event.getAxisValue(MotionEvent.AXIS_Z) > 0.5)) );
getCurrentMap().TRIGGER_RIGHT.update(
(event.getAxisValue( MotionEvent.AXIS_RTRIGGER) > 0.5)
|| (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5)
|| (mModifierSwappedAxis && event.getAxisValue(MotionEvent.AXIS_RZ) > 0.5) );
}
}
public void notifyGUISizeChange(int newSize){
//Change the pointer size to match UI
int size = (int) ((22 * newSize) / scaleFactor);
pointerView.post(() -> pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size)));
}
private GamepadMap getCurrentMap(){
return currentMap;
}
private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){
switch (direction){
case DIRECTION_NORTH:
sendInput(map.DIRECTION_FORWARD, isDown);
break;
case DIRECTION_NORTH_EAST:
sendInput(map.DIRECTION_FORWARD, isDown);
sendInput(map.DIRECTION_RIGHT, isDown);
break;
case DIRECTION_EAST:
sendInput(map.DIRECTION_RIGHT, isDown);
break;
case DIRECTION_SOUTH_EAST:
sendInput(map.DIRECTION_RIGHT, isDown);
sendInput(map.DIRECTION_BACKWARD, isDown);
break;
case DIRECTION_SOUTH:
sendInput(map.DIRECTION_BACKWARD, isDown);
break;
case DIRECTION_SOUTH_WEST:
sendInput(map.DIRECTION_BACKWARD, isDown);
sendInput(map.DIRECTION_LEFT, isDown);
break;
case DIRECTION_WEST:
sendInput(map.DIRECTION_LEFT, isDown);
break;
case DIRECTION_NORTH_WEST:
sendInput(map.DIRECTION_FORWARD, isDown);
sendInput(map.DIRECTION_LEFT, isDown);
break;
}
}
private void placePointerView(int x, int y){
pointerView.setX(x - pointerView.getWidth()/2);
pointerView.setY(y - pointerView.getHeight()/2);
}
public void sendButton(KeyEvent event){
sendButton(event.getKeyCode(), event.getAction());
}
public void sendButton(int keycode, int action){
boolean isDown = action == KeyEvent.ACTION_DOWN;
switch (keycode){
case KeyEvent.KEYCODE_BUTTON_A:
getCurrentMap().BUTTON_A.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_B:
getCurrentMap().BUTTON_B.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_X:
getCurrentMap().BUTTON_X.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_Y:
getCurrentMap().BUTTON_Y.update(isDown);
break;
//Shoulders
case KeyEvent.KEYCODE_BUTTON_L1:
getCurrentMap().SHOULDER_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_R1:
getCurrentMap().SHOULDER_RIGHT.update(isDown);
break;
//Triggers
case KeyEvent.KEYCODE_BUTTON_L2:
getCurrentMap().TRIGGER_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_R2:
getCurrentMap().TRIGGER_RIGHT.update(isDown);
break;
//L3 || R3
case KeyEvent.KEYCODE_BUTTON_THUMBL:
getCurrentMap().THUMBSTICK_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
getCurrentMap().THUMBSTICK_RIGHT.update(isDown);
break;
//DPAD
case KeyEvent.KEYCODE_DPAD_UP:
getCurrentMap().DPAD_UP.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
getCurrentMap().DPAD_DOWN.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
getCurrentMap().DPAD_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
getCurrentMap().DPAD_RIGHT.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
getCurrentMap().DPAD_RIGHT.update(false);
getCurrentMap().DPAD_LEFT.update(false);
getCurrentMap().DPAD_UP.update(false);
getCurrentMap().DPAD_DOWN.update(false);
break;
//Start/select
case KeyEvent.KEYCODE_BUTTON_START:
getCurrentMap().BUTTON_START.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_SELECT:
getCurrentMap().BUTTON_SELECT.update(isDown);
break;
default:
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), isDown);
break;
}
}
public static void sendInput(int[] keycodes, boolean isDown){
for(int keycode : keycodes){
switch (keycode){
case GamepadMap.MOUSE_SCROLL_DOWN:
if(isDown) CallbackBridge.sendScroll(0, -1);
break;
case GamepadMap.MOUSE_SCROLL_UP:
if(isDown) CallbackBridge.sendScroll(0, 1);
break;
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
break;
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
break;
default:
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
break;
}
CallbackBridge.setModifiers(keycode, isDown);
}
}
public static boolean isGamepadEvent(MotionEvent event){
return isJoystickEvent(event);
}
public static boolean isGamepadEvent(KeyEvent event){
return ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|| GamepadDpad.isDpadEvent(event) );
}
}

View file

@ -1,46 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.view.KeyEvent;
public class GamepadButton {
/*
Just a simple button, that auto deal with the great habit from android to just SPAAAMS input events
*/
public int[] keycodes;
public boolean isToggleable = false;
private boolean isDown = false;
private boolean isToggled = false;
public void update(KeyEvent event){
boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN);
update(isKeyDown);
}
public void update(boolean isKeyDown){
if(isKeyDown != isDown){
isDown = isKeyDown;
if(isToggleable){
if(isKeyDown){
isToggled = !isToggled;
Gamepad.sendInput(keycodes, isToggled);
}
return;
}
Gamepad.sendInput(keycodes, isDown);
}
}
public void resetButtonState(){
if(isDown || isToggled){
Gamepad.sendInput(keycodes, false);
}
isDown = false;
isToggled = false;
}
public boolean isDown(){
return isToggleable ? isToggled : isDown;
}
}

View file

@ -1,60 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
public class GamepadDpad {
private int lastKeycode = KEYCODE_DPAD_CENTER;
/**
* Convert the event to a 2 int array: keycode and keyAction, similar to a keyEvent
* @param event The motion to convert
* @return int[0] keycode, int[1] keyAction
*/
public int[] convertEvent(MotionEvent event){
// Use the hat axis value to find the D-pad direction
float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
int action = KeyEvent.ACTION_DOWN;
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
// LEFT and RIGHT direction accordingly.
if (Float.compare(xaxis, -1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_LEFT;
} else if (Float.compare(xaxis, 1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_RIGHT;
}
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
// UP and DOWN direction accordingly.
else if (Float.compare(yaxis, -1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_UP;
} else if (Float.compare(yaxis, 1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_DOWN;
}else {
//No keycode change
action = KeyEvent.ACTION_UP;
}
return new int[]{lastKeycode, action};
}
public static boolean isDpadEvent(MotionEvent event) {
// Check that input comes from a device with directional pads.
// And... also the joystick since it declares sometimes as a joystick.
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
}
public static boolean isDpadEvent(KeyEvent event){
return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC);
}
}

View file

@ -1,99 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.view.InputDevice;
import android.view.MotionEvent;
import com.google.android.material.math.MathUtils;
public class GamepadJoystick {
//Directions
public static final int DIRECTION_NONE = -1; //GamepadJoystick at the center
public static final int DIRECTION_EAST = 0;
public static final int DIRECTION_NORTH_EAST = 1;
public static final int DIRECTION_NORTH = 2;
public static final int DIRECTION_NORTH_WEST = 3;
public static final int DIRECTION_WEST = 4;
public static final int DIRECTION_SOUTH_WEST = 5;
public static final int DIRECTION_SOUTH = 6;
public static final int DIRECTION_SOUTH_EAST = 7;
private final InputDevice device;
private final int verticalAxis;
private final int horizontalAxis;
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
this.verticalAxis = verticalAxis;
this.horizontalAxis = horizontalAxis;
this.device = device;
}
public double getAngleRadian(MotionEvent event){
//From -PI to PI
return -Math.atan2(getVerticalAxis(event), getHorizontalAxis(event));
}
public double getAngleDegree(MotionEvent event){
//From 0 to 360 degrees
double result = Math.toDegrees(getAngleRadian(event));
if(result < 0) result += 360;
return result;
}
public double getMagnitude(MotionEvent event){
float x = Math.abs(event.getAxisValue(horizontalAxis));
float y = Math.abs(event.getAxisValue(verticalAxis));
return MathUtils.dist(0,0, x, y);
}
public float getVerticalAxis(MotionEvent event){
return applyDeadzone(event, verticalAxis);
}
public float getHorizontalAxis(MotionEvent event){
return applyDeadzone(event, horizontalAxis);
}
private float applyDeadzone(MotionEvent event, int axis){
//This piece of code also modifies the value
//to make it seem like there was no deadzone in the first place
double magnitude = getMagnitude(event);
float deadzone = getDeadzone();
if (magnitude < deadzone) return 0;
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
}
public static boolean isJoystickEvent(MotionEvent event){
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
&& event.getAction() == MotionEvent.ACTION_MOVE;
}
public int getHeightDirection(MotionEvent event){
if(getMagnitude(event) <= getDeadzone()) return DIRECTION_NONE;
return ((int) ((getAngleDegree(event)+22.5)/45)) % 8;
}
/**
* Get the deadzone from the Input device linked to this joystick
* Some controller aren't supported, fallback to 0.2 if that the case.
* @return the deadzone of the joystick
*/
public float getDeadzone() {
try{
return Math.max(device.getMotionRange(horizontalAxis).getFlat() * 1.9f, 0.2f);
}catch (Exception e){
return 0.2f;
}
}
}

View file

@ -1,174 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
public class GamepadMap {
public static final int MOUSE_SCROLL_DOWN = -1;
public static final int MOUSE_SCROLL_UP = -2;
/*
This class is just here to store the mapping
can be modified to create re-mappable controls I guess
Be warned, you should define ALL keys if you want to avoid a non defined exception
*/
public GamepadButton BUTTON_A = new GamepadButton();
public GamepadButton BUTTON_B = new GamepadButton();
public GamepadButton BUTTON_X = new GamepadButton();
public GamepadButton BUTTON_Y = new GamepadButton();
public GamepadButton BUTTON_START = new GamepadButton();
public GamepadButton BUTTON_SELECT = new GamepadButton();
public GamepadButton TRIGGER_RIGHT = new GamepadButton(); //R2
public GamepadButton TRIGGER_LEFT = new GamepadButton(); //L2
public GamepadButton SHOULDER_RIGHT = new GamepadButton(); //R1
public GamepadButton SHOULDER_LEFT = new GamepadButton(); //L1
public int[] DIRECTION_FORWARD;
public int[] DIRECTION_BACKWARD;
public int[] DIRECTION_RIGHT;
public int[] DIRECTION_LEFT;
public GamepadButton THUMBSTICK_RIGHT = new GamepadButton(); //R3
public GamepadButton THUMBSTICK_LEFT = new GamepadButton(); //L3
public GamepadButton DPAD_UP = new GamepadButton();
public GamepadButton DPAD_RIGHT = new GamepadButton();
public GamepadButton DPAD_DOWN = new GamepadButton();
public GamepadButton DPAD_LEFT = new GamepadButton();
/*
* Sets all buttons to a not pressed state, sending an input if needed
*/
public void resetPressedState(){
BUTTON_A.resetButtonState();
BUTTON_B.resetButtonState();
BUTTON_X.resetButtonState();
BUTTON_Y.resetButtonState();
BUTTON_START.resetButtonState();
BUTTON_SELECT.resetButtonState();
TRIGGER_LEFT.resetButtonState();
TRIGGER_RIGHT.resetButtonState();
SHOULDER_LEFT.resetButtonState();
SHOULDER_RIGHT.resetButtonState();
THUMBSTICK_LEFT.resetButtonState();
THUMBSTICK_RIGHT.resetButtonState();
DPAD_UP.resetButtonState();
DPAD_RIGHT.resetButtonState();
DPAD_DOWN.resetButtonState();
DPAD_LEFT.resetButtonState();
}
/*
* Returns a pre-done mapping used when the mouse is grabbed by the game.
*/
public static GamepadMap getDefaultGameMap(){
GamepadMap gameMap = new GamepadMap();
gameMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE};
gameMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_Q};
gameMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_E};
gameMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_F};
gameMap.DIRECTION_FORWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_W};
gameMap.DIRECTION_BACKWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_S};
gameMap.DIRECTION_RIGHT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_D};
gameMap.DIRECTION_LEFT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_A};
gameMap.DPAD_UP.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
gameMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
gameMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
gameMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
gameMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
gameMap.TRIGGER_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
gameMap.TRIGGER_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL};
gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.THUMBSTICK_RIGHT.isToggleable = true;
gameMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
gameMap.BUTTON_SELECT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB};
return gameMap;
}
/*
* Returns a pre-done mapping used when the mouse is NOT grabbed by the game.
*/
public static GamepadMap getDefaultMenuMap(){
GamepadMap menuMap = new GamepadMap();
menuMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
menuMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
menuMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT, LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied.
menuMap.DIRECTION_FORWARD = new int[]{GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP};
menuMap.DIRECTION_BACKWARD = new int[]{GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN};
menuMap.DIRECTION_RIGHT = new int[]{};
menuMap.DIRECTION_LEFT = new int[]{};
menuMap.DPAD_UP.keycodes = new int[]{};
menuMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
menuMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
menuMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
menuMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
menuMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
menuMap.TRIGGER_LEFT.keycodes = new int[]{};
menuMap.TRIGGER_RIGHT.keycodes = new int[]{};
menuMap.THUMBSTICK_LEFT.keycodes = new int[]{};
menuMap.THUMBSTICK_RIGHT.keycodes = new int[]{};
menuMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_SELECT.keycodes = new int[]{};
return menuMap;
}
/*
* Returns all GamepadButtons, does not include directional keys
*/
public GamepadButton[] getButtons(){
return new GamepadButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y,
BUTTON_SELECT, BUTTON_START,
TRIGGER_LEFT, TRIGGER_RIGHT,
SHOULDER_LEFT, SHOULDER_RIGHT,
THUMBSTICK_LEFT, THUMBSTICK_RIGHT,
DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT};
}
/*
* Returns an pre-initialized GamepadMap with only empty keycodes
*/
public static GamepadMap getEmptyMap(){
GamepadMap emptyMap = new GamepadMap();
for(GamepadButton button : emptyMap.getButtons())
button.keycodes = new int[]{};
emptyMap.DIRECTION_LEFT = new int[]{};
emptyMap.DIRECTION_FORWARD = new int[]{};
emptyMap.DIRECTION_RIGHT = new int[]{};
emptyMap.DIRECTION_BACKWARD = new int[]{};
return emptyMap;
}
}

View file

@ -1,201 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.*;
import android.graphics.drawable.ColorDrawable;
import android.view.*;
import android.view.ViewGroup.*;
import android.widget.*;
import net.kdt.pojavlaunch.*;
import android.view.View.OnClickListener;
import net.kdt.pojavlaunch.customcontrols.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import androidx.appcompat.app.*;
import com.rarepebble.colorpicker.ColorPickerView;
public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
private TextView mEditTextView;
private TextView mDeleteTextView;
private TextView mCloneTextView;
private final ControlButton editedButton;
public ActionPopupWindow(HandleView handleView, ControlButton button){
super(handleView);
this.editedButton = button;
}
@Override
protected void createPopupWindow() {
mPopupWindow = new PopupWindow(mHandleView.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
mPopupWindow.setClippingEnabled(true);
}
@Override
protected void initContentView() {
LinearLayout linearLayout = new LinearLayout(mHandleView.getContext());
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
mContentView = linearLayout;
mContentView.setBackgroundResource(R.drawable.control_side_action_window);
LayoutInflater inflater = (LayoutInflater) mHandleView.getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutParams wrapContent = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mEditTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mEditTextView.setLayoutParams(wrapContent);
mContentView.addView(mEditTextView);
mEditTextView.setText(R.string.global_edit);
mEditTextView.setOnClickListener(this);
mDeleteTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mDeleteTextView.setLayoutParams(wrapContent);
mContentView.addView(mDeleteTextView);
mDeleteTextView.setText(R.string.global_remove);
mDeleteTextView.setOnClickListener(this);
mCloneTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mCloneTextView.setLayoutParams(wrapContent);
mContentView.addView(mCloneTextView);
mCloneTextView.setText(R.string.global_clone);
mCloneTextView.setOnClickListener(this);
}
@Override
public void show() {
super.show();
}
@Override
public void onClick(final View view) {
if (view == mEditTextView) {
if(editedButton instanceof ControlSubButton){
new EditControlSubButtonPopup((ControlSubButton) editedButton);
return;
}
if(editedButton instanceof ControlDrawer){
new EditControlDrawerPopup((ControlDrawer) editedButton);
return;
}
if(editedButton instanceof ControlButton){
new EditControlButtonPopup((ControlButton) editedButton);
return;
}
} else if (view == mDeleteTextView) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(view.getContext());
alertBuilder.setCancelable(false);
alertBuilder.setMessage(view.getContext().getString(R.string.customctrl_remove, mHandleView.mView.getText()) + "?");
alertBuilder.setPositiveButton(R.string.global_remove, (p1, p2) -> {
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
if(editedButton instanceof ControlSubButton){
layout.removeControlSubButton((ControlSubButton) editedButton);
return;
}
if(editedButton instanceof ControlDrawer){
layout.removeControlDrawer((ControlDrawer) editedButton);
return;
}
if(editedButton instanceof ControlButton){
layout.removeControlButton((ControlButton) editedButton);
}
layout.removeControlButton(mHandleView.mView);
});
alertBuilder.setNegativeButton(android.R.string.cancel, null);
alertBuilder.show();
}else if(view == mCloneTextView) {
if(editedButton instanceof ControlDrawer){
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer)editedButton).getDrawerData());
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData);
}else if(editedButton instanceof ControlSubButton){
ControlData cloneData = new ControlData(editedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addSubButton(((ControlSubButton) editedButton).parentDrawer, cloneData);
}else{
ControlData cloneData = new ControlData(editedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addControlButton(cloneData);
}
}
hide();
}
@Override
protected int getTextOffset() {
return 0;
}
@Override
protected int getVerticalLocalPosition(int line) {
return 0;
}
@Override
protected int clipVertically(int positionY) {
return positionY;
}
public static void showColorPicker(Context ctx,String title, boolean showAlpha, ImageView v){
int startColor = ((ColorDrawable)v.getDrawable()).getColor();
ColorPickerView picker = new ColorPickerView(ctx);
picker.setColor(startColor);
picker.showAlpha(showAlpha);
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
dialog.setTitle(title);
dialog.setView(picker);
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> v.setImageDrawable(new ColorDrawable(picker.getColor())));
dialog.show();
}
public static void setPercentageText(TextView textView, int progress){
textView.setText(progress + " %");
}
}

View file

@ -1,328 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
import static net.kdt.pojavlaunch.customcontrols.handleview.ActionPopupWindow.setPercentageText;
public class EditControlButtonPopup {
protected AlertDialog dialog;
protected View v;
protected AlertDialog.Builder builder;
protected EditText editName;
protected Spinner[] spinnersKeycode;
protected CheckBox checkToggle;
protected CheckBox checkPassThrough;
protected CheckBox checkBoxSwipeable;
protected CheckBox checkDynamicPosition;
protected EditText editWidth;
protected EditText editHeight;
protected EditText editDynamicX;
protected EditText editDynamicY;
protected SeekBar seekBarOpacity;
protected SeekBar seekBarCornerRadius;
protected SeekBar seekBarStrokeWidth;
protected ImageButton buttonBackgroundColor;
protected ImageButton buttonStrokeColor;
protected TextView textOpacity;
protected TextView textCornerRadius;
protected TextView textStrokeWidth;
protected TextView textStrokeColor;
protected final ControlButton button;
protected final ControlData properties;
protected ArrayAdapter<String> adapter;
protected String[] specialArr;
public EditControlButtonPopup(ControlButton button){
this.button = button;
this.properties = button.getProperties();
initializeEditDialog(button.getContext());
//Create the finalized dialog
dialog = builder.create();
dialog.setOnShowListener(dialogInterface -> setEditDialogValues());
dialog.show();
}
protected void initializeEditDialog(Context ctx){
//Create the editing dialog
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = layoutInflater.inflate(R.layout.control_button_setting,null);
builder = new AlertDialog.Builder(ctx);
builder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, properties.name));
builder.setView(v);
//Linking a lot of stuff
editName = v.findViewById(R.id.editName_editText);
spinnersKeycode = new Spinner[]{
v.findViewById(R.id.editMapping_spinner_1),
v.findViewById(R.id.editMapping_spinner_2),
v.findViewById(R.id.editMapping_spinner_3),
v.findViewById(R.id.editMapping_spinner_4)
};
checkToggle = v.findViewById(R.id.checkboxToggle);
checkPassThrough = v.findViewById(R.id.checkboxPassThrough);
checkBoxSwipeable = v.findViewById(R.id.checkboxSwipeable);
editWidth = v.findViewById(R.id.editSize_editTextX);
editHeight = v.findViewById(R.id.editSize_editTextY);
editDynamicX = v.findViewById(R.id.editDynamicPositionX_editText);
editDynamicY = v.findViewById(R.id.editDynamicPositionY_editText);
seekBarOpacity = v.findViewById(R.id.editButtonOpacity_seekbar);
seekBarCornerRadius = v.findViewById(R.id.editCornerRadius_seekbar);
seekBarStrokeWidth = v.findViewById(R.id.editStrokeWidth_seekbar);
SeekBar.OnSeekBarChangeListener changeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if(seekBar.equals(seekBarCornerRadius)) {
setPercentageText(textCornerRadius, i);
return;
}
if(seekBar.equals(seekBarOpacity)) {
setPercentageText(textOpacity, i);
return;
}
if(seekBar.equals(seekBarStrokeWidth)) {
setPercentageText(textStrokeWidth, i);
textStrokeColor.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {/*STUB*/}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {/*STUB*/}
};
//Add listeners, too bad I don't need all the methods
seekBarOpacity.setOnSeekBarChangeListener(changeListener);
seekBarCornerRadius.setOnSeekBarChangeListener(changeListener);
seekBarStrokeWidth.setOnSeekBarChangeListener(changeListener);
buttonBackgroundColor = v.findViewById(R.id.editBackgroundColor_imageButton);
buttonStrokeColor = v.findViewById(R.id.editStrokeColor_imageButton);
textOpacity = v.findViewById(R.id.editButtonOpacity_textView_percent);
textCornerRadius = v.findViewById(R.id.editCornerRadius_textView_percent);
textStrokeWidth = v.findViewById(R.id.editStrokeWidth_textView_percent);
textStrokeColor = v.findViewById(R.id.editStrokeColor_textView);
checkDynamicPosition = v.findViewById(R.id.checkboxDynamicPosition);
checkDynamicPosition.setOnCheckedChangeListener((btn, checked) -> {
editDynamicX.setEnabled(checked);
editDynamicY.setEnabled(checked);
});
//Initialize adapter for keycodes
adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
specialArr = new String[oldSpecialArr.length];
for (int i = 0; i < specialArr.length; i++) {
specialArr[i] = "SPECIAL_" + oldSpecialArr[specialArr.length - i - 1];
}
adapter.addAll(specialArr);
adapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
for (Spinner spinner : spinnersKeycode) {
spinner.setAdapter(adapter);
}
//Set color imageButton behavior
buttonBackgroundColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit background color", true, buttonBackgroundColor));
buttonStrokeColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit stroke color", false, buttonStrokeColor));
//Set dialog buttons behavior
setupDialogButtons();
hideUselessViews();
defineDynamicCheckChange();
setupCheckerboards();
}
protected void setupDialogButtons(){
//Set dialog buttons behavior
builder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
if(!hasPropertiesErrors(dialog.getContext())){
saveProperties();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
}
protected void hideUselessViews(){
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
editDynamicX.setVisibility(View.GONE);
editDynamicY.setVisibility(View.GONE);
//Hide the color choice if the width is 0.
textStrokeColor.setVisibility(properties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
}
protected void defineDynamicCheckChange(){
checkDynamicPosition.setOnCheckedChangeListener((compoundButton, b) -> {
int visibility = b ? View.VISIBLE : View.GONE;
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
editDynamicX.setVisibility(visibility);
editDynamicY.setVisibility(visibility);
});
}
private void setupCheckerboards(){
CheckerboardDrawable drawable = new CheckerboardDrawable.Builder()
.colorEven(Color.LTGRAY)
.colorOdd(Color.WHITE)
.size((int) Tools.dpToPx(20))
.build();
buttonBackgroundColor.setBackground(drawable);
buttonStrokeColor.setBackground(drawable);
}
protected void setEditDialogValues(){
editName.setText(properties.name);
checkToggle.setChecked(properties.isToggle);
checkPassThrough.setChecked(properties.passThruEnabled);
checkBoxSwipeable.setChecked(properties.isSwipeable);
editWidth.setText(Float.toString(properties.getWidth()));
editHeight.setText(Float.toString(properties.getHeight()));
editDynamicX.setEnabled(properties.isDynamicBtn);
editDynamicY.setEnabled(properties.isDynamicBtn);
editDynamicX.setText(properties.dynamicX);
editDynamicY.setText(properties.dynamicY);
seekBarOpacity.setProgress((int) (properties.opacity*100));
seekBarStrokeWidth.setProgress(properties.strokeWidth);
seekBarCornerRadius.setProgress((int)properties.cornerRadius);
buttonBackgroundColor.setImageDrawable(new ColorDrawable(properties.bgColor));
buttonStrokeColor.setImageDrawable(new ColorDrawable(properties.strokeColor));
setPercentageText(textCornerRadius,seekBarCornerRadius.getProgress());
setPercentageText(textOpacity,seekBarOpacity.getProgress());
setPercentageText(textStrokeWidth,seekBarStrokeWidth.getProgress());
checkDynamicPosition.setChecked(properties.isDynamicBtn);
for(int i=0; i< properties.keycodes.length; i++){
if (properties.keycodes[i] < 0) {
spinnersKeycode[i].setSelection(properties.keycodes[i] + specialArr.length);
} else {
spinnersKeycode[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(properties.keycodes[i]) + specialArr.length);
}
}
}
protected boolean hasPropertiesErrors(Context ctx){
if (editName.getText().toString().isEmpty()) {
editName.setError(ctx.getResources().getString(R.string.global_error_field_empty));
return true;
}
if (properties.isDynamicBtn) {
int errorAt = 0;
try {
properties.insertDynamicPos(editDynamicX.getText().toString());
errorAt = 1;
properties.insertDynamicPos(editDynamicY.getText().toString());
} catch (Throwable th) {
(errorAt == 0 ? editDynamicX : editDynamicY).setError(th.getMessage());
return true;
}
}
return false;
}
protected void saveProperties(){
//This method assumes there are no error.
properties.name = editName.getText().toString();
//Keycodes
for(int i=0; i<spinnersKeycode.length; ++i){
if (spinnersKeycode[i].getSelectedItemPosition() < specialArr.length) {
properties.keycodes[i] = spinnersKeycode[i].getSelectedItemPosition() - specialArr.length;
} else {
properties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(spinnersKeycode[i].getSelectedItemPosition() - specialArr.length);
}
}
properties.opacity = seekBarOpacity.getProgress()/100f;
properties.strokeWidth = seekBarStrokeWidth.getProgress();
properties.cornerRadius = seekBarCornerRadius.getProgress();
properties.bgColor = ((ColorDrawable)buttonBackgroundColor.getDrawable()).getColor();
properties.strokeColor = ((ColorDrawable) buttonStrokeColor.getDrawable()).getColor();
properties.isToggle = checkToggle.isChecked();
properties.passThruEnabled = checkPassThrough.isChecked();
properties.isSwipeable = checkBoxSwipeable.isChecked();
properties.setWidth(Float.parseFloat(editWidth.getText().toString()));
properties.setHeight(Float.parseFloat(editHeight.getText().toString()));
properties.isDynamicBtn = checkDynamicPosition.isChecked();
if(!editDynamicX.getText().toString().isEmpty()) properties.dynamicX = editDynamicX.getText().toString();
if(!editDynamicY.getText().toString().isEmpty()) properties.dynamicY = editDynamicY.getText().toString();
button.updateProperties();
}
}

View file

@ -1,90 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
public class EditControlDrawerPopup extends EditControlButtonPopup{
private Spinner spinnerOrientation;
private ControlDrawer drawer;
private ControlDrawerData drawerData;
public EditControlDrawerPopup(ControlDrawer editedButton) {
super(editedButton);
drawer = editedButton;
drawerData = editedButton.getDrawerData();
}
@Override
protected void hideUselessViews() {
(v.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
checkPassThrough.setVisibility(View.GONE);
checkToggle.setVisibility(View.GONE);
checkBoxSwipeable.setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
editDynamicX.setVisibility(View.GONE);
editDynamicY.setVisibility(View.GONE);
}
@Override
protected void initializeEditDialog(Context ctx) {
super.initializeEditDialog(ctx);
spinnerOrientation = v.findViewById(R.id.editOrientation_spinner);
ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
adapter.addAll(ControlDrawerData.getOrientations());
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
spinnerOrientation.setAdapter(adapter);
}
@Override
protected void setEditDialogValues() {
super.setEditDialogValues();
spinnerOrientation.setSelection(ControlDrawerData.orientationToInt(drawerData.orientation));
//Using the dialog to replace the button behavior allows us not to dismiss the window
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
ControlLayout layout = (ControlLayout) drawer.getParent();
ControlData controlData = new ControlData(drawerData.properties);
controlData.name = "new";
layout.addSubButton(drawer, controlData);
Context ctx = dialog.getContext();
Toast.makeText(ctx, ctx.getString(R.string.customctrl_add_subbutton_message,
drawer.getDrawerData().buttonProperties.size()), Toast.LENGTH_SHORT).show();
});
}
@Override
protected void setupDialogButtons() {
super.setupDialogButtons();
builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), null);
}
@Override
protected void saveProperties() {
drawerData.orientation = ControlDrawerData.intToOrientation(spinnerOrientation.getSelectedItemPosition());
super.saveProperties();
}
}

View file

@ -1,28 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.view.View;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public class EditControlSubButtonPopup extends EditControlButtonPopup{
public EditControlSubButtonPopup(ControlButton button){
super(button);
}
@Override
protected void hideUselessViews() {
(v.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
checkDynamicPosition.setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
editDynamicX.setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
editDynamicY.setVisibility(View.GONE);
}
}

View file

@ -1,405 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.graphics.*;
import android.graphics.drawable.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener
{
protected Drawable mDrawable;
protected Drawable mDrawableLtr;
protected Drawable mDrawableRtl;
private final PopupWindow mContainer;
// Position with respect to the parent TextView
private int mPositionX, mPositionY;
private boolean mIsDragging;
// Offset from touch position to mPosition
private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
protected int mHotspotX;
protected int mHorizontalGravity;
// Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility
private float mIdealVerticalOffset;
// Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
// Transient action popup window for Paste and Replace actions
protected ActionPopupWindow mActionPopupWindow;
// Previous text character offset
private int mPreviousOffset = -1;
// Previous text character offset
private boolean mPositionHasChanged = true;
// Used to delay the appearance of the action popup window
private Runnable mActionPopupShower;
// Minimum touch target size for handles
private int mMinSize;
protected ControlButton mView;
// MOD: Addition. Save old size of parent
private int mDownWidth, mDownHeight;
// int mWindowPosX, mWindowPosY;
private PositionListener mPositionListener;
public PositionListener getPositionListener() {
if (mPositionListener == null) {
mPositionListener = new PositionListener(mView);
}
return mPositionListener;
}
public HandleView(ControlButton view) {
super(view.getContext());
mView = view;
mDownWidth = view.getLayoutParams().width;
mDownHeight = view.getLayoutParams().height;
mContainer = new PopupWindow(view.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
mContainer.setContentView(this);
mDrawableLtr = view.getContext().getDrawable(R.drawable.text_select_handle_left_material);
mDrawableRtl = view.getContext().getDrawable(R.drawable.text_select_handle_right_material);
mMinSize = view.getContext().getResources().getDimensionPixelSize(R.dimen.text_handle_min_size);
setOnLongClickListener(this);
updateDrawable();
final int handleHeight = getPreferredHeight();
mTouchOffsetY = -0.3f * handleHeight;
mIdealVerticalOffset = 0.7f * handleHeight;
}
protected void updateDrawable() {
// final int offset = getCurrentCursorOffset();
final boolean isRtlCharAtOffset = true; // mView.getLayout().isRtlCharAt(offset);
mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
mHorizontalGravity = getHorizontalGravity(isRtlCharAtOffset);
}
protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
protected abstract int getHorizontalGravity(boolean isRtlRun);
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
private int mPreviousOffsetIndex = 0;
private int mNumberPreviousOffsets = 0;
private void startTouchUpFilter(int offset) {
mNumberPreviousOffsets = 0;
addPositionToTouchUpFilter(offset);
}
private void addPositionToTouchUpFilter(int offset) {
mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
mPreviousOffsets[mPreviousOffsetIndex] = offset;
mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
mNumberPreviousOffsets++;
}
private void filterOnTouchUp() {
final long now = SystemClock.uptimeMillis();
int i = 0;
int index = mPreviousOffsetIndex;
final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
i++;
index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
}
if (i > 0 && i < iMax &&
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
positionAtCursorOffset(mPreviousOffsets[index], false);
}
}
public boolean offsetHasBeenChanged() {
return mNumberPreviousOffsets > 1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
}
private int getPreferredWidth() {
return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
}
private int getPreferredHeight() {
return Math.max(mDrawable.getIntrinsicHeight(), mMinSize);
}
public void show() {
if (isShowing()) return;
getPositionListener().addSubscriber(this, true /* local position may change */);
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
positionAtCursorOffset(getCurrentCursorOffset(), false);
hideActionPopupWindow();
}
protected void dismiss() {
mIsDragging = false;
mContainer.dismiss();
onDetached();
}
public void hide() {
dismiss();
getPositionListener().removeSubscriber(this);
}
void showActionPopupWindow(int delay, ControlButton button) {
if (mActionPopupWindow == null) {
mActionPopupWindow = new ActionPopupWindow(this, button);
}
if (mActionPopupShower == null) {
mActionPopupShower = new Runnable() {
public void run() {
try {
mActionPopupWindow.show();
} catch (Throwable th) {
th.printStackTrace();
}
}
};
} else {
mView.removeCallbacks(mActionPopupShower);
}
mView.postDelayed(mActionPopupShower, delay);
}
protected void hideActionPopupWindow() {
if (mActionPopupShower != null) {
mView.removeCallbacks(mActionPopupShower);
}
if (mActionPopupWindow != null) {
mActionPopupWindow.hide();
}
}
public boolean isShowing() {
return mContainer.isShowing();
}
private boolean isVisible() {
// Always show a dragging handle.
if (mIsDragging) {
return true;
}
return mView.getVisibility() == View.VISIBLE;
}
public abstract int getCurrentCursorOffset();
protected abstract void updateSelection(int offset);
public abstract void updatePosition(float x, float y);
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
mPositionX = mView.getWidth();
mPositionY = mView.getHeight();
mPositionHasChanged = true;
}
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
if (parentPositionChanged || mPositionHasChanged) {
if (mIsDragging) {
// Update touchToWindow offset in case of parent scrolling while dragging
if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
mTouchToWindowOffsetX += parentPositionX - mLastParentX;
mTouchToWindowOffsetY += parentPositionY - mLastParentY;
mLastParentX = parentPositionX;
mLastParentY = parentPositionY;
}
onHandleMoved();
}
if (isVisible()) {
final int positionX = parentPositionX + mPositionX;
final int positionY = parentPositionY + mPositionY;
/*
mWindowPosX = positionX;
mWindowPosY = positionY;
*/
if (isShowing()) {
mContainer.update(positionX, positionY, -1, -1);
} else {
mContainer.showAtLocation(mView, Gravity.NO_GRAVITY,
positionX, positionY);
}
} else {
if (isShowing()) {
dismiss();
}
}
mPositionHasChanged = false;
}
}
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left = getHorizontalOffset();
mDrawable.setBounds(left, 0, left + drawWidth, mDrawable.getIntrinsicHeight());
mDrawable.draw(c);
}
private int getHorizontalOffset() {
final int width = getPreferredWidth();
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left;
switch (mHorizontalGravity) {
case Gravity.LEFT:
left = 0;
break;
default:
case Gravity.CENTER:
left = (width - drawWidth) / 2;
break;
case Gravity.RIGHT:
left = width - drawWidth;
break;
}
return left;
}
protected int getCursorOffset() {
return 0;
}
// Addition
private float mDownX, mDownY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
ViewGroup.LayoutParams params = mView.getLayoutParams();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
startTouchUpFilter(getCurrentCursorOffset());
mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final PositionListener positionListener = getPositionListener();
mLastParentX = positionListener.getPositionX();
mLastParentY = positionListener.getPositionY();
mIsDragging = true;
// MOD: Addition
mDownX = ev.getRawX();
mDownY = ev.getRawY();
mDownWidth = params.width;
mDownHeight = params.height;
break;
}
case MotionEvent.ACTION_MOVE: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
// Vertical hysteresis: vertical down movement tends to snap to ideal offset
final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
float newVerticalOffset;
if (previousVerticalOffset < mIdealVerticalOffset) {
newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
} else {
newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
}
mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
int newWidth = (int) (mDownWidth + (rawX - mDownX));
int newHeight = (int) (mDownHeight + (rawY - mDownY));
// mDownX = rawX;
// mDownY = rawY;
params.width = Math.max(50, newWidth);
params.height = Math.max(50, newHeight);
mView.setLayoutParams(params);
updatePosition(newPosX, newPosY);
// break;
return true;
}
case MotionEvent.ACTION_UP:
filterOnTouchUp();
mIsDragging = false;
break;
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
break;
}
return true;
}
public boolean isDragging() {
return mIsDragging;
}
void onHandleMoved() {
hideActionPopupWindow();
}
public void onDetached() {
hideActionPopupWindow();
}
}

View file

@ -1,142 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.util.*;
import android.view.*;
import android.view.ViewGroup.*;
import android.widget.*;
public abstract class PinnedPopupWindow implements ViewPositionListener {
protected PopupWindow mPopupWindow;
protected ViewGroup mContentView;
int mPositionX, mPositionY;
protected HandleView mHandleView;
protected abstract void createPopupWindow();
protected abstract void initContentView();
protected abstract int getTextOffset();
protected abstract int getVerticalLocalPosition(int line);
protected abstract int clipVertically(int positionY);
public PinnedPopupWindow(HandleView handleView) {
mHandleView = handleView;
createPopupWindow();
mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
initContentView();
LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mContentView.setLayoutParams(wrapContent);
mPopupWindow.setContentView(mContentView);
}
public void show() {
mHandleView.getPositionListener().addSubscriber(this, false /* offset is fixed */);
computeLocalPosition();
final PositionListener positionListener = mHandleView.getPositionListener();
updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
}
protected void measureContent() {
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
mContentView.measure(
View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
View.MeasureSpec.AT_MOST));
}
/* The popup window will be horizontally centered on the getTextOffset() and vertically
* positioned according to viewportToContentHorizontalOffset.
*
* This method assumes that mContentView has properly been measured from its content. */
private void computeLocalPosition() {
measureContent();
/*
final int width = mContentView.getMeasuredWidth();
final int offset = getTextOffset();
mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f);
mPositionX += mTextView.viewportToContentHorizontalOffset();
final int line = mTextView.getLayout().getLineForOffset(offset);
mPositionY = getVerticalLocalPosition(line);
mPositionY += mTextView.viewportToContentVerticalOffset();
*/
/*
mPositionX = (int) mHandleView.mView.getTranslationX();
mPositionY = (int) mHandleView.mView.getTranslationY();
*/
mPositionY = mHandleView.mView.getHeight();
}
private void updatePosition(int parentPositionX, int parentPositionY) {
int positionX = parentPositionX + mPositionX;
int positionY = parentPositionY + mPositionY;
positionY = clipVertically(positionY);
// Horizontal clipping
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
final int width = mContentView.getMeasuredWidth();
positionX = Math.min(displayMetrics.widthPixels - width, positionX);
positionX = Math.max(0, positionX);
if (isShowing()) {
mPopupWindow.update(positionX, positionY, -1, -1);
} else {
mPopupWindow.showAtLocation(mHandleView, Gravity.NO_GRAVITY,
positionX, positionY);
}
}
public void hide() {
mPopupWindow.dismiss();
mHandleView.getPositionListener().removeSubscriber(this);
}
@Override
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
// Either parentPositionChanged or parentScrolled is true, check if still visible
if (isShowing()) {
if (parentScrolled) computeLocalPosition();
updatePosition(parentPositionX, parentPositionY);
} else {
hide();
}
}
public boolean isShowing() {
return mPopupWindow.isShowing();
}
}

View file

@ -1,119 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.view.*;
public class PositionListener implements ViewTreeObserver.OnPreDrawListener {
// 3 handles
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
// 1 CursorAnchorInfoNotifier
private final int MAXIMUM_NUMBER_OF_LISTENERS = 7;
private ViewPositionListener[] mPositionListeners =
new ViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
private boolean mPositionHasChanged = true;
// Absolute position of the TextView with respect to its parent window
private int mPositionX, mPositionY;
private int mNumberOfListeners;
private boolean mScrollHasChanged;
final int[] mTempCoords = new int[2];
private View mView;
public PositionListener(View view) {
mView = view;
}
public void addSubscriber(ViewPositionListener positionListener, boolean canMove) {
if (mNumberOfListeners == 0) {
updatePosition();
ViewTreeObserver vto = mView.getViewTreeObserver();
vto.addOnPreDrawListener(this);
}
int emptySlotIndex = -1;
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
ViewPositionListener listener = mPositionListeners[i];
if (listener == positionListener) {
return;
} else if (emptySlotIndex < 0 && listener == null) {
emptySlotIndex = i;
}
}
mPositionListeners[emptySlotIndex] = positionListener;
mCanMove[emptySlotIndex] = canMove;
mNumberOfListeners++;
}
public void removeSubscriber(ViewPositionListener positionListener) {
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
if (mPositionListeners[i] == positionListener) {
mPositionListeners[i] = null;
mNumberOfListeners--;
break;
}
}
if (mNumberOfListeners == 0) {
ViewTreeObserver vto = mView.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
}
}
public int getPositionX() {
return mPositionX;
}
public int getPositionY() {
return mPositionY;
}
@Override
public boolean onPreDraw() {
updatePosition();
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
ViewPositionListener positionListener = mPositionListeners[i];
if (positionListener != null) {
positionListener.updatePosition(mPositionX, mPositionY,
mPositionHasChanged, mScrollHasChanged);
}
}
}
mScrollHasChanged = false;
return true;
}
private void updatePosition() {
mView.getLocationInWindow(mTempCoords);
mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
mPositionX = mTempCoords[0];
mPositionY = mTempCoords[1];
}
public void onScrollChanged() {
mScrollHasChanged = true;
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.graphics.drawable.*;
import android.view.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public class SelectionEndHandleView extends HandleView
{
public SelectionEndHandleView(ControlButton view) {
super(view);
}
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
if (isRtlRun) {
return (drawable.getIntrinsicWidth() * 3) / 4;
} else {
return drawable.getIntrinsicWidth() / 4;
}
}
@Override
protected int getHorizontalGravity(boolean isRtlRun) {
return isRtlRun ? Gravity.LEFT : Gravity.RIGHT;
}
@Override
public int getCurrentCursorOffset() {
return 0; // mView.getSelectionEnd();
}
public void show(ControlButton button){
super.show();
showActionPopupWindow(0, button);
}
@Override
public void updateSelection(int offset) {
// Selection.setSelection((Spannable) mView.getText(), mView.getSelectionStart(), offset);
updateDrawable();
}
@Override
public void updatePosition(float x, float y) {
// updatePosition((int) x, (int) y, false, false);
positionAtCursorOffset(0, false);
}
@Override
public boolean onLongClick(View view) {
//TODO stub
return false;
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
public interface ViewPositionListener {
public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled);
}

View file

@ -1,135 +0,0 @@
package net.kdt.pojavlaunch.extra;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Class providing callback across all of a program
* to allow easy thread safe implementations of UI update without context leak
* It is also perfectly engineered to make it unpleasant to use.
*
* This class uses a singleton pattern to simplify access to it
*/
public final class ExtraCore {
// No unwanted instantiation
private ExtraCore(){}
// Store the key-value pair
private final Map<String, Object> valueMap = new ConcurrentHashMap<>();
// Store what each ExtraListener listen to
private final Map<String, ConcurrentLinkedQueue<WeakReference<ExtraListener>>> listenerMap = new ConcurrentHashMap<>();
// Inner class for singleton implementation
private static class ExtraCoreSingleton {
private static final ExtraCore extraCore = new ExtraCore();
}
// All public methods will pass through this one
private static ExtraCore getInstance(){
return ExtraCoreSingleton.extraCore;
}
/**
* Set the value associated to a key and trigger all listeners
* @param key The key
* @param value The value
*/
public static void setValue(String key, Object value){
getInstance().valueMap.put(key, value);
ConcurrentLinkedQueue<WeakReference<ExtraListener>> extraListenerList = getInstance().listenerMap.get(key);
if(extraListenerList == null) return; //No listeners
for(WeakReference<ExtraListener> listener : extraListenerList){
if(listener.get() == null){
extraListenerList.remove(listener);
continue;
}
//Notify the listener about a state change and remove it if asked for
if(listener.get().onValueSet(key, value)){
ExtraCore.removeExtraListenerFromValue(key, listener.get());
}
}
}
/** @return The value behind the key */
public static Object getValue(String key){
return getInstance().valueMap.get(key);
}
/** Remove the key and its value from the valueMap */
public static void removeValue(String key){
getInstance().valueMap.remove(key);
}
/** Remove all values */
public static void removeAllValues(){
getInstance().valueMap.clear();
}
/**
* Link an ExtraListener to a value
* @param key The value key to look for
* @param listener The ExtraListener to link
*/
public static void addExtraListener(String key, ExtraListener listener){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().listenerMap.put(key, listenerList);
}
// This is kinda naive, I should look for duplicates
listenerList.add(new WeakReference<>(listener));
}
/**
* Unlink an ExtraListener from a value.
* Unlink null references found along the way
* @param key The value key to ignore now
* @param listener The ExtraListener to unlink
*/
public static void removeExtraListenerFromValue(String key, ExtraListener listener){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().listenerMap.put(key, listenerList);
}
// Removes all occurrences of ExtraListener and all null references
for(WeakReference<ExtraListener> listenerWeakReference : listenerList){
ExtraListener actualListener = listenerWeakReference.get();
if(actualListener == null || actualListener == listener){
listenerList.remove(listenerWeakReference);
}
}
}
/**
* Unlink all ExtraListeners from a value
* @param key The key to which ExtraListener are linked
*/
public static void removeAllExtraListenersFromValue(String key){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().listenerMap.put(key, listenerList);
}
listenerList.clear();
}
/**
* Remove all ExtraListeners from listening to any value
*/
public static void removeAllExtraListeners(){
getInstance().listenerMap.clear();
}
}

View file

@ -1,19 +0,0 @@
package net.kdt.pojavlaunch.extra;
import androidx.annotation.Nullable;
/**
* Listener class for the ExtraCore
* An ExtraListener can listen to a virtually unlimited amount of values
*/
public interface ExtraListener<T> {
/**
* Called upon a new value being set
* @param key The name of the value
* @param value The new value as a string
* @return Whether you consume the Listener (stop listening)
*/
boolean onValueSet(String key, @Nullable T value);
}

View file

@ -1,44 +0,0 @@
package net.kdt.pojavlaunch.multirt;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.webkit.MimeTypeMap;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import net.kdt.pojavlaunch.BaseLauncherActivity;
import net.kdt.pojavlaunch.R;
public class MultiRTConfigDialog {
public static final int MULTIRT_PICK_RUNTIME = 2048;
public static final int MULTIRT_PICK_RUNTIME_STARTUP = 2049;
public AlertDialog dialog;
public RecyclerView dialogView;
public void prepare(BaseLauncherActivity ctx) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.multirt_config_title);
dialogView = new RecyclerView(ctx);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ctx);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
dialogView.setLayoutManager(linearLayoutManager);
dialogView.setAdapter(new RTRecyclerViewAdapter(this));
builder.setView(dialogView);
builder.setPositiveButton(R.string.multirt_config_add, (dialog, which) -> openRuntimeSelector(ctx,MULTIRT_PICK_RUNTIME));
builder.setNegativeButton(R.string.mcn_exit_call, (dialog, which) -> dialog.cancel());
dialog = builder.create();
}
public void refresh() {
RecyclerView.Adapter adapter = dialogView.getAdapter();
if(adapter != null)dialogView.getAdapter().notifyDataSetChanged();
}
public static void openRuntimeSelector(Activity ctx, int code) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("xz");
if(mimeType == null) mimeType = "*/*";
intent.setType(mimeType);
ctx.startActivityForResult(intent,code);
}
}

View file

@ -1,264 +0,0 @@
package net.kdt.pojavlaunch.multirt;
import android.content.Context;
import android.system.Os;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.JREUtils;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
public class MultiRTUtils {
public static HashMap<String,Runtime> cache = new HashMap<>();
public static class Runtime {
public Runtime(String name) {
this.name = name;
}
public String name;
public String versionString;
public String arch;
public int javaVersion;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Runtime runtime = (Runtime) o;
return name.equals(runtime.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
public static interface ProgressReporterThingy {
void reportStringProgress(int resid, Object ... stuff);
}
private static final File runtimeFolder = new File(Tools.MULTIRT_HOME);
private static final String JAVA_VERSION_str = "JAVA_VERSION=\"";
private static final String OS_ARCH_str = "OS_ARCH=\"";
public static List<Runtime> getRuntimes() {
if(!runtimeFolder.exists()) runtimeFolder.mkdirs();
ArrayList<Runtime> ret = new ArrayList<>();
System.out.println("Fetch runtime list");
for(File f : runtimeFolder.listFiles()) {
ret.add(read(f.getName()));
}
return ret;
}
public static String getNearestJREName(int majorVersion) {
List<Runtime> runtimes = getRuntimes();
int diff_factor = Integer.MAX_VALUE;
String result = null;
for(Runtime r : runtimes) {
if(r.javaVersion >= majorVersion) { // lower - not useful
int currentFactor = r.javaVersion - majorVersion;
if(diff_factor > currentFactor) {
result = r.name;
diff_factor = currentFactor;
}
}
}
return result;
}
public static void installRuntimeNamed(InputStream runtimeInputStream, String name, ProgressReporterThingy thingy) throws IOException {
File dest = new File(runtimeFolder,"/"+name);
File tmp = new File(dest,"temporary");
if(dest.exists()) FileUtils.deleteDirectory(dest);
dest.mkdirs();
FileOutputStream fos = new FileOutputStream(tmp);
thingy.reportStringProgress(R.string.multirt_progress_caching);
IOUtils.copy(runtimeInputStream,fos);
fos.close();
runtimeInputStream.close();
uncompressTarXZ(tmp,dest,thingy);
tmp.delete();
read(name);
}
private static void __installRuntimeNamed__NoRM(InputStream runtimeInputStream, File dest, ProgressReporterThingy thingy) throws IOException {
File tmp = new File(dest,"temporary");
FileOutputStream fos = new FileOutputStream(tmp);
thingy.reportStringProgress(R.string.multirt_progress_caching);
IOUtils.copy(runtimeInputStream,fos);
fos.close();
runtimeInputStream.close();
uncompressTarXZ(tmp,dest,thingy);
tmp.delete();
}
public static void postPrepare(Context ctx, String name) throws IOException {
File dest = new File(runtimeFolder,"/"+name);
if(!dest.exists()) return;
Runtime r = read(name);
String libFolder = "lib";
if(new File(dest,libFolder+"/"+r.arch).exists()) libFolder = libFolder+"/"+r.arch;
File ftIn = new File(dest, libFolder+ "/libfreetype.so.6");
File ftOut = new File(dest, libFolder + "/libfreetype.so");
if (ftIn.exists() && (!ftOut.exists() || ftIn.length() != ftOut.length())) {
ftIn.renameTo(ftOut);
}
// Refresh libraries
copyDummyNativeLib(ctx,"libawt_xawt.so",dest,libFolder);
}
private static void copyDummyNativeLib(Context ctx, String name, File dest, String libFolder) throws IOException {
File fileLib = new File(dest, "/"+libFolder + "/" + name);
fileLib.delete();
FileInputStream is = new FileInputStream(new File(ctx.getApplicationInfo().nativeLibraryDir, name));
FileOutputStream os = new FileOutputStream(fileLib);
IOUtils.copy(is, os);
is.close();
os.close();
}
public static Runtime installRuntimeNamedBinpack(InputStream universalFileInputStream, InputStream platformBinsInputStream, String name, String binpackVersion, ProgressReporterThingy thingy) throws IOException {
File dest = new File(runtimeFolder,"/"+name);
if(dest.exists()) FileUtils.deleteDirectory(dest);
dest.mkdirs();
__installRuntimeNamed__NoRM(universalFileInputStream,dest,thingy);
__installRuntimeNamed__NoRM(platformBinsInputStream,dest,thingy);
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
FileOutputStream fos = new FileOutputStream(binpack_verfile);
fos.write(binpackVersion.getBytes());
fos.close();
cache.remove(name); // Force reread
return read(name);
}
public static String __internal__readBinpackVersion(String name) {
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
try {
if (binpack_verfile.exists()) {
return Tools.read(binpack_verfile.getAbsolutePath());
}else{
return null;
}
}catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static void removeRuntimeNamed(String name) throws IOException {
File dest = new File(runtimeFolder,"/"+name);
if(dest.exists()) {
FileUtils.deleteDirectory(dest);
cache.remove(name);
}
}
public static void setRuntimeNamed(Context ctx, String name) throws IOException {
File dest = new File(runtimeFolder,"/"+name);
if((!dest.exists()) || MultiRTUtils.forceReread(name).versionString == null) throw new RuntimeException("Selected runtime is broken!");
Tools.DIR_HOME_JRE = dest.getAbsolutePath();
JREUtils.relocateLibPath(ctx);
}
public static Runtime forceReread(String name) {
cache.remove(name);
return read(name);
}
public static Runtime read(String name) {
if(cache.containsKey(name)) return cache.get(name);
Runtime retur;
File release = new File(runtimeFolder,"/"+name+"/release");
if(!release.exists()) {
return new Runtime(name);
}
try {
String content = Tools.read(release.getAbsolutePath());
int _JAVA_VERSION_index = content.indexOf(JAVA_VERSION_str);
int _OS_ARCH_index = content.indexOf(OS_ARCH_str);
if(_JAVA_VERSION_index != -1 && _OS_ARCH_index != -1) {
_JAVA_VERSION_index += JAVA_VERSION_str.length();
_OS_ARCH_index += OS_ARCH_str.length();
String javaVersion = content.substring(_JAVA_VERSION_index,content.indexOf('"',_JAVA_VERSION_index));
String[] javaVersionSplit = javaVersion.split("\\.");
int javaVersionInt;
if (javaVersionSplit[0].equals("1")) {
javaVersionInt = Integer.parseInt(javaVersionSplit[1]);
} else {
javaVersionInt = Integer.parseInt(javaVersionSplit[0]);
}
Runtime r = new Runtime(name);
r.arch = content.substring(_OS_ARCH_index,content.indexOf('"',_OS_ARCH_index));
r.javaVersion = javaVersionInt;
r.versionString = javaVersion;
retur = r;
}else{
retur = new Runtime(name);
}
}catch(IOException e) {
retur = new Runtime(name);
}
cache.put(name,retur);
return retur;
}
private static void uncompressTarXZ(final File tarFile, final File dest, final ProgressReporterThingy thingy) throws IOException {
dest.mkdirs();
TarArchiveInputStream tarIn = null;
tarIn = new TarArchiveInputStream(
new XZCompressorInputStream(
new BufferedInputStream(
new FileInputStream(tarFile)
)
)
);
TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
// tarIn is a TarArchiveInputStream
while (tarEntry != null) {
/*
* Unpacking very small files in short time cause
* application to ANR or out of memory, so delay
* a little if size is below than 20kb (20480 bytes)
*/
if (tarEntry.getSize() <= 20480) {
try {
// 40 small files per second
Thread.sleep(25);
} catch (InterruptedException ignored) {}
}
final String tarEntryName = tarEntry.getName();
// publishProgress(null, "Unpacking " + tarEntry.getName());
thingy.reportStringProgress(R.string.global_unpacking,tarEntryName);
File destPath = new File(dest, tarEntry.getName());
if (tarEntry.isSymbolicLink()) {
destPath.getParentFile().mkdirs();
try {
// android.system.Os
// Libcore one support all Android versions
Os.symlink(tarEntry.getName(), tarEntry.getLinkName());
} catch (Throwable e) {
e.printStackTrace();
}
} else if (tarEntry.isDirectory()) {
destPath.mkdirs();
destPath.setExecutable(true);
} else if (!destPath.exists() || destPath.length() != tarEntry.getSize()) {
destPath.getParentFile().mkdirs();
destPath.createNewFile();
FileOutputStream os = new FileOutputStream(destPath);
IOUtils.copy(tarIn, os);
os.close();
}
tarEntry = tarIn.getNextTarEntry();
}
tarIn.close();
}
}

View file

@ -1,132 +0,0 @@
package net.kdt.pojavlaunch.multirt;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import java.io.IOException;
import java.util.List;
public class RTRecyclerViewAdapter extends RecyclerView.Adapter<RTRecyclerViewAdapter.RTViewHolder> {
MultiRTConfigDialog dialog;
public RTRecyclerViewAdapter(MultiRTConfigDialog dialog) {
this.dialog = dialog;
}
@NonNull
@Override
public RTViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View recyclableView = LayoutInflater.from(parent.getContext()).inflate(R.layout.multirt_recyclable_view,parent,false);
return new RTViewHolder(recyclableView);
}
@Override
public void onBindViewHolder(@NonNull RTViewHolder holder, int position) {
final List<MultiRTUtils.Runtime> runtimes = MultiRTUtils.getRuntimes();
holder.bindRuntime(runtimes.get(position),position);
}
public boolean isDefaultRuntime(MultiRTUtils.Runtime rt) {
return LauncherPreferences.PREF_DEFAULT_RUNTIME.equals(rt.name);
}
public void setDefault(MultiRTUtils.Runtime rt){
LauncherPreferences.PREF_DEFAULT_RUNTIME = rt.name;
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply();
RTRecyclerViewAdapter.this.notifyDataSetChanged();
}
@Override
public int getItemCount() {
return MultiRTUtils.getRuntimes().size();
}
public class RTViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
final TextView javaVersionView;
final TextView fullJavaVersionView;
final ColorStateList defaultColors;
final Button setDefaultButton;
final Context ctx;
MultiRTUtils.Runtime currentRuntime;
int currentPosition;
public RTViewHolder(View itemView) {
super(itemView);
javaVersionView = itemView.findViewById(R.id.multirt_view_java_version);
fullJavaVersionView = itemView.findViewById(R.id.multirt_view_java_version_full);
itemView.findViewById(R.id.multirt_view_removebtn).setOnClickListener(this);
setDefaultButton = itemView.findViewById(R.id.multirt_view_setdefaultbtn);
setDefaultButton.setOnClickListener(this);
defaultColors = fullJavaVersionView.getTextColors();
ctx = itemView.getContext();
}
public void bindRuntime(MultiRTUtils.Runtime rt, int pos) {
currentRuntime = rt;
currentPosition = pos;
if(rt.versionString != null) {
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
fullJavaVersionView.setText(rt.versionString);
fullJavaVersionView.setTextColor(defaultColors);
setDefaultButton.setVisibility(View.VISIBLE);
boolean default_ = isDefaultRuntime(rt);
setDefaultButton.setEnabled(!default_);
setDefaultButton.setText(default_?R.string.multirt_config_setdefault_already:R.string.multirt_config_setdefault);
}else{
javaVersionView.setText(rt.name);
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
fullJavaVersionView.setTextColor(Color.RED);
setDefaultButton.setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.multirt_view_removebtn) {
if (currentRuntime != null) {
if(MultiRTUtils.getRuntimes().size() < 2) {
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
bldr.setTitle(R.string.global_error);
bldr.setMessage(R.string.multirt_config_removeerror_last);
bldr.setPositiveButton(android.R.string.ok,(adapter, which)->adapter.dismiss());
bldr.show();
return;
}
final ProgressDialog barrier = new ProgressDialog(ctx);
barrier.setMessage(ctx.getString(R.string.global_waiting));
barrier.setProgressStyle(ProgressDialog.STYLE_SPINNER);
barrier.setCancelable(false);
barrier.show();
Thread t = new Thread(() -> {
try {
MultiRTUtils.removeRuntimeNamed(currentRuntime.name);
} catch (IOException e) {
Tools.showError(itemView.getContext(), e);
}
v.post(() -> {
if(isDefaultRuntime(currentRuntime)) setDefault(MultiRTUtils.getRuntimes().get(0));
barrier.dismiss();
RTRecyclerViewAdapter.this.notifyDataSetChanged();
dialog.dialog.show();
});
});
t.start();
}
}else if(v.getId() == R.id.multirt_view_setdefaultbtn) {
if(currentRuntime != null) {
setDefault(currentRuntime);
RTRecyclerViewAdapter.this.notifyDataSetChanged();
}
}
}
}
}

View file

@ -1,103 +0,0 @@
package net.kdt.pojavlaunch.multirt;
import android.content.Context;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import java.util.List;
public class RTSpinnerAdapter implements SpinnerAdapter {
final Context ctx;
List<MultiRTUtils.Runtime> runtimes;
public RTSpinnerAdapter(@NonNull Context context, List<MultiRTUtils.Runtime> runtimes) {
this.runtimes = runtimes;
MultiRTUtils.Runtime runtime = new MultiRTUtils.Runtime("<Default>");
runtime.versionString = "";
this.runtimes.add(runtime);
ctx = context;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
@Override
public int getCount() {
return runtimes.size();
}
@Override
public Object getItem(int position) {
return runtimes.get(position);
}
@Override
public long getItemId(int position) {
return runtimes.get(position).name.hashCode();
}
@Override
public boolean hasStableIds() {
return true;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View v = convertView!=null?
convertView:
LayoutInflater.from(ctx).inflate(R.layout.multirt_recyclable_view,parent,false);
MultiRTUtils.Runtime rt = runtimes.get(position);
final TextView javaVersionView = v.findViewById(R.id.multirt_view_java_version);
final TextView fullJavaVersionView = v.findViewById(R.id.multirt_view_java_version_full);
v.findViewById(R.id.multirt_view_removebtn).setVisibility(View.GONE);
v.findViewById(R.id.multirt_view_setdefaultbtn).setVisibility(View.GONE);
if(rt.versionString != null) {
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
fullJavaVersionView.setText(rt.versionString);
}else{
javaVersionView.setText(rt.name);
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
}
return v;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean isEmpty() {
return runtimes.isEmpty();
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position,convertView,parent);
}
}

View file

@ -1,118 +0,0 @@
package net.kdt.pojavlaunch.prefs;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter;
import net.kdt.pojavlaunch.value.PerVersionConfig;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class PerVersionConfigDialog{
final Context ctx;
final AlertDialog dialog;
final View v;
List<MultiRTUtils.Runtime> runtimes;
final Spinner javaVMSpinner;
final Spinner rendererSpinner;
final EditText customDirText;
final EditText jvmArgsEditText;
final List<String> renderNames;
String selectedGameVersion = null;
public PerVersionConfigDialog(Context _ctx) {
ctx = _ctx;
v = LayoutInflater.from(ctx).inflate(R.layout.pvc_popup,null);
javaVMSpinner = v.findViewById(R.id.pvc_javaVm);
rendererSpinner = v.findViewById(R.id.pvc_renderer);
{
List<String> renderList = new ArrayList<>();
Collections.addAll(renderList, ctx.getResources().getStringArray(R.array.renderer));
renderList.add("Default");
renderNames = Arrays.asList(ctx.getResources().getStringArray(R.array.renderer_values));
rendererSpinner.setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_dropdown_item,renderList));
}
customDirText = v.findViewById(R.id.pvc_customDir);
jvmArgsEditText = v.findViewById(R.id.pvc_jvmArgs);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setView(v);
builder.setTitle(R.string.pvc_title);
builder.setNegativeButton(android.R.string.cancel,(dialogInterface,i)->dialogInterface.dismiss());
builder.setPositiveButton(android.R.string.ok,this::save);
dialog = builder.create();
}
public void refreshRuntimes() {
if(runtimes!=null)runtimes.clear();
runtimes = MultiRTUtils.getRuntimes();
//runtimes.add(new MultiRTUtils.Runtime("<Default>"));
}
private void save(DialogInterface i, int which) {
if(selectedGameVersion == null) {
i.dismiss();
return;
}
PerVersionConfig.VersionConfig conf1 = PerVersionConfig.configMap.get(selectedGameVersion);
if(conf1==null){
conf1=new PerVersionConfig.VersionConfig();
}
conf1.jvmArgs=jvmArgsEditText.getText().toString();
conf1.gamePath=customDirText.getText().toString();
if(rendererSpinner.getSelectedItemPosition() == renderNames.size()) conf1.renderer = null;
else conf1.renderer = renderNames.get(rendererSpinner.getSelectedItemPosition());
String runtime=((MultiRTUtils.Runtime)javaVMSpinner.getSelectedItem()).name;;
if(!runtime.equals("<Default>"))conf1.selectedRuntime=runtime;
else conf1.selectedRuntime=null;
PerVersionConfig.configMap.put(selectedGameVersion,conf1);
try{
PerVersionConfig.update();
}catch(IOException e){
e.printStackTrace();
}
}
public boolean openConfig(String selectedVersion) {
selectedGameVersion = selectedVersion;
try{
PerVersionConfig.update();
}catch(IOException e){
e.printStackTrace();
}
PerVersionConfig.VersionConfig conf=PerVersionConfig.configMap.get(selectedGameVersion);
refreshRuntimes();
javaVMSpinner.setAdapter(new RTSpinnerAdapter(ctx,runtimes));
{
int jvm_index = runtimes.indexOf(new MultiRTUtils.Runtime("<Default>"));
int rnd_index = rendererSpinner.getAdapter().getCount()-1;
if (conf != null) {
customDirText.setText(conf.gamePath);
jvmArgsEditText.setText(conf.jvmArgs);
if (conf.selectedRuntime != null) {
int nindex = runtimes.indexOf(new MultiRTUtils.Runtime(conf.selectedRuntime));
if (nindex != -1) jvm_index = nindex;
}
if(conf.renderer != null) {
int nindex = renderNames.indexOf(conf.renderer);
if (nindex != -1) rnd_index = nindex;
}
}
javaVMSpinner.setSelection(jvm_index);
rendererSpinner.setSelection(rnd_index);
}
dialog.show();
return true;
}
}

View file

@ -1,24 +0,0 @@
package net.kdt.pojavlaunch.prefs;
import android.content.Context;
import android.util.AttributeSet;
import androidx.preference.Preference;
import net.kdt.pojavlaunch.BaseLauncherActivity;
public class RuntimeManagerPreference extends Preference{
public RuntimeManagerPreference(Context ctx) {
this(ctx, null);
}
public RuntimeManagerPreference(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
setPersistent(false);
}
@Override
protected void onClick() {
super.onClick();
((BaseLauncherActivity)this.getContext()).mRuntimeConfigDialog.dialog.show();
}
}

View file

@ -1,58 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import android.content.SharedPreferences;
import android.os.Bundle;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
public class LauncherPreferenceControlFragment extends LauncherPreferenceFragment {
@Override
public void onCreatePreferences(Bundle b, String str) {
// Get values
int longPressTrigger = LauncherPreferences.PREF_LONGPRESS_TRIGGER;
int prefButtonSize = (int) LauncherPreferences.PREF_BUTTONSIZE;
int mouseScale = (int) LauncherPreferences.PREF_MOUSESCALE;
float mouseSpeed = LauncherPreferences.PREF_MOUSESPEED;
//Triggers a write for some reason which resets the value
addPreferencesFromResource(R.xml.pref_control);
CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger");
seek2.setRange(100, 1000);
seek2.setValue(longPressTrigger);
seek2.setSuffix(" ms");
CustomSeekBarPreference seek3 = findPreference("buttonscale");
seek3.setRange(80, 250);
seek3.setValue(prefButtonSize);
seek3.setSuffix(" %");
CustomSeekBarPreference seek4 = findPreference("mousescale");
seek4.setRange(25, 300);
seek4.setValue(mouseScale);
seek4.setSuffix(" %");
CustomSeekBarPreference seek6 = findPreference("mousespeed");
seek6.setRange(25, 300);
seek6.setValue((int)(mouseSpeed *100f));
seek6.setSuffix(" %");
computeVisibility();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences p, String s) {
super.onSharedPreferenceChanged(p, s);
computeVisibility();
}
private void computeVisibility(){
CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger");
seek2.setVisible(!LauncherPreferences.PREF_DISABLE_GESTURES);
}
}

View file

@ -1,13 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import android.os.Bundle;
import net.kdt.pojavlaunch.R;
public class LauncherPreferenceExperimentalFragment extends LauncherPreferenceFragment {
@Override
public void onCreatePreferences(Bundle b, String str) {
addPreferencesFromResource(R.xml.pref_experimental);
}
}

View file

@ -1,51 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import android.graphics.Color;
import android.os.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.*;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import android.content.*;
import android.view.View;
/**
* Preference for the main screen, any sub-screen should inherit this class for consistent behavior,
* overriding only onCreatePreferences
*/
public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
view.setBackgroundColor(Color.parseColor("#232323"));
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onCreatePreferences(Bundle b, String str) {
addPreferencesFromResource(R.xml.pref_main);
}
@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences p, String s) {
LauncherPreferences.loadPreferences(getContext());
}
}

View file

@ -1,43 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import static net.kdt.pojavlaunch.Architecture.is32BitsDevice;
import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory;
import android.os.Bundle;
import android.widget.TextView;
import androidx.preference.EditTextPreference;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
public class LauncherPreferenceJavaFragment extends LauncherPreferenceFragment {
@Override
public void onCreatePreferences(Bundle b, String str) {
int ramAllocation = LauncherPreferences.PREF_RAM_ALLOCATION;
// Triggers a write for some reason
addPreferencesFromResource(R.xml.pref_java);
int maxRAM;
int deviceRam = getTotalDeviceMemory(getContext());
CustomSeekBarPreference seek7 = findPreference("allocation");
seek7.setMin(256);
if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam);
else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe
seek7.setMax(maxRAM);
seek7.setValue(ramAllocation);
seek7.setSuffix(" MB");
EditTextPreference editJVMArgs = findPreference("javaArgs");
if (editJVMArgs != null) {
editJVMArgs.setOnBindEditTextListener(TextView::setSingleLine);
}
}
}

View file

@ -1,19 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import android.os.Build;
import android.os.Bundle;
import androidx.preference.SwitchPreference;
import net.kdt.pojavlaunch.R;
public class LauncherPreferenceMiscellaneousFragment extends LauncherPreferenceFragment {
@Override
public void onCreatePreferences(Bundle b, String str) {
addPreferencesFromResource(R.xml.pref_misc);
// Sustained performance is only available since Nougat
SwitchPreference sustainedPerfSwitch = findPreference("sustainedPerformance");
sustainedPerfSwitch.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
}
}

View file

@ -1,31 +0,0 @@
package net.kdt.pojavlaunch.prefs.screens;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
import android.os.Build;
import android.os.Bundle;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
/**
* Fragment for any settings video related
*/
public class LauncherPreferenceVideoFragment extends LauncherPreferenceFragment {
@Override
public void onCreatePreferences(Bundle b, String str) {
addPreferencesFromResource(R.xml.pref_video);
//Disable notch checking behavior on android 8.1 and below.
findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0);
CustomSeekBarPreference seek5 = findPreference("resolutionRatio");
seek5.setMin(25);
seek5.setSuffix(" %");
// #724 bug fix
if (seek5.getValue() < 25) {
seek5.setValue(100);
}
}
}

View file

@ -1,176 +0,0 @@
package net.kdt.pojavlaunch.scoped;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
public class GameFolderProvider extends DocumentsProvider {
static File baseDir = new File(Tools.DIR_GAME_HOME);
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE
};
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath());
row.add(Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath());
row.add(Root.COLUMN_SUMMARY, null);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
row.add(Root.COLUMN_MIME_TYPES, "*/*");
row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result,documentId,null);
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
for(File f : new File(parentDocumentId).listFiles()) {
includeFile(result,null,f);
}
return result;
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
return ParcelFileDescriptor.open(new File(documentId),ParcelFileDescriptor.parseMode(mode));
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
File f = new File(documentId);
return new AssetFileDescriptor(ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY), 0, f.length());
}
@Override
public boolean onCreate() {
return true;
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
if (!new File(documentId).delete()) throw new FileNotFoundException("Can't remove "+documentId);
}
@Override
public String getDocumentType(String documentId) throws FileNotFoundException {
return getMimeType(new File(documentId));
}
@Override
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = new File(rootId);
final LinkedList<File> pending = new LinkedList<>();
pending.add(parent);
final int MAX_SEARCH_RESULTS = 50;
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
final File file = pending.removeFirst();
boolean isInsideGameDir;
try {
isInsideGameDir = file.getCanonicalPath().startsWith(baseDir.getCanonicalPath());
} catch (IOException e) {
isInsideGameDir = true;
}
if (isInsideGameDir) {
if (file.isDirectory()) {
Collections.addAll(pending, file.listFiles());
} else {
if (file.getName().toLowerCase().contains(query)) {
includeFile(result, null, file);
}
}
}
}
return result;
}
private static String getMimeType(File file) {
if (file.isDirectory()) {
return Document.MIME_TYPE_DIR;
} else {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime;
}
return "application/octet-stream";
}
}
private void includeFile(MatrixCursor result, String docId, File file)
throws FileNotFoundException {
if (docId == null) {
docId = file.getAbsolutePath();
} else {
file = new File(docId);
}
int flags = 0;
if (file.isDirectory()) {
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
}
final String displayName = file.getName();
final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
}
}

View file

@ -1,473 +0,0 @@
package net.kdt.pojavlaunch.tasks;
import android.app.*;
import android.content.*;
import android.os.*;
import android.util.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.*;
import net.kdt.pojavlaunch.value.*;
import org.apache.commons.io.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable>
{
private BaseLauncherActivity mActivity;
private boolean launchWithError = false;
MinecraftDownloaderTask thiz = this;
public MinecraftDownloaderTask(BaseLauncherActivity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
mActivity.mLaunchProgress.setMax(1);
mActivity.statusIsLaunching(true);
}
private JMinecraftVersionList.Version verInfo;
@Override
protected Throwable doInBackground(final String[] p1) {
Throwable throwable = null;
try {
final String downVName = "/" + p1[0] + "/" + p1[0];
//Downloading libraries
String minecraftMainJar = Tools.DIR_HOME_VERSION + downVName + ".jar";
JAssets assets = null;
try {
File verJsonDir = new File(Tools.DIR_HOME_VERSION + downVName + ".json");
verInfo = findVersion(p1[0]);
if (verInfo.url != null) {
boolean isManifestGood = true; // assume it is dy default
if(verJsonDir.exists()) {//if the file exists
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) {// check if the checker is on
if (verInfo.sha1 != null) {//check if the SHA is available
if (Tools.compareSHA1(verJsonDir, verInfo.sha1)) // check the SHA
publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass, p1[0] + ".json")); // and say that SHA was verified
else{
verJsonDir.delete(); // if it wasn't, delete the old one
publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail, p1[0] + ".json")); // put it into log
isManifestGood = false; // and mark manifest as bad
}
}else{
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown, p1[0] + ".json")); // say that the sha is unknown, but assume that the manifest is good
}
}else{
Log.w("Chk","Checker is off");// if the checker is off, manifest will be always good, unless it doesn't exist
}
} else {
isManifestGood = false;
}
if(!isManifestGood) {
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".json"));
Tools.downloadFileMonitored(
verInfo.url,
verJsonDir.getAbsolutePath(),
new Tools.DownloaderFeedback() {
@Override
public void updateProgress(int curr, int max) {
publishDownloadProgress(p1[0] + ".json", curr, max);
}
}
);
}
}
verInfo = Tools.getVersionInfo(mActivity,p1[0]);
//Now we have the reliable information to check if our runtime settings are good enough
if(verInfo.javaVersion != null) { //1.17+
PerVersionConfig.update();
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]);
if(cfg == null) {
cfg = new PerVersionConfig.VersionConfig();
PerVersionConfig.configMap.put(p1[0],cfg);
}
MultiRTUtils.Runtime r = cfg.selectedRuntime != null?MultiRTUtils.read(cfg.selectedRuntime):MultiRTUtils.read(LauncherPreferences.PREF_DEFAULT_RUNTIME);
if(r.javaVersion < verInfo.javaVersion.majorVersion) {
String appropriateRuntime = MultiRTUtils.getNearestJREName(verInfo.javaVersion.majorVersion);
if(appropriateRuntime != null) {
cfg.selectedRuntime = appropriateRuntime;
PerVersionConfig.update();
}else{
mActivity.runOnUiThread(()->{
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
bldr.setTitle(R.string.global_error);
bldr.setMessage(R.string.multirt_nocompartiblert);
bldr.setPositiveButton(android.R.string.ok,(dialog, which)->{
dialog.dismiss();
});
bldr.show();
});
throw new SilentException();
}
} //if else, we are satisfied
}
{ //run the checks to detect if we have a *brand new* engine
int mcReleaseDate = Integer.parseInt(verInfo.releaseTime.substring(0, 10).replace("-", ""));
if(mcReleaseDate > 20210225 && verInfo.javaVersion != null && verInfo.javaVersion.majorVersion > 15)
V117CompatUtil.runCheck(p1[0],mActivity);
}
try {
assets = downloadIndex(verInfo.assets, new File(Tools.ASSETS_PATH, "indexes/" + verInfo.assets + ".json"));
} catch (IOException e) {
publishProgress("0", Log.getStackTraceString(e));
}
File outLib;
setMax(verInfo.libraries.length);
zeroProgress();
for (final DependentLibrary libItem : verInfo.libraries) {
if (
// libItem.name.startsWith("net.java.jinput") ||
libItem.name.startsWith("org.lwjgl")
) { // Black list
publishProgress("1", "Ignored " + libItem.name);
//Thread.sleep(100);
} else {
String[] libInfo = libItem.name.split(":");
String libArtifact = Tools.artifactToPath(libInfo[0], libInfo[1], libInfo[2]);
outLib = new File(Tools.DIR_HOME_LIBRARY + "/" + libArtifact);
outLib.getParentFile().mkdirs();
if (!outLib.exists()) {
downloadLibrary(libItem,libArtifact,outLib);
}else{
if(libItem.downloads != null && libItem.downloads.artifact != null && libItem.downloads.artifact.sha1 != null && !libItem.downloads.artifact.sha1.isEmpty()) {
if(!Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1)) {
outLib.delete();
publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail,libItem.name));
downloadLibrary(libItem,libArtifact,outLib);
}else{
publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass,libItem.name));
}
}else{
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown,libItem.name));
}
}
}
}
setMax(3);
zeroProgress();
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".jar"));
File minecraftMainFile = new File(minecraftMainJar);
if ((!minecraftMainFile.exists() || minecraftMainFile.length() == 0l) &&
verInfo.downloads != null) {
try {
Tools.downloadFileMonitored(
verInfo.downloads.values().toArray(new MinecraftClientInfo[0])[0].url,
minecraftMainJar,
new Tools.DownloaderFeedback() {
@Override
public void updateProgress(int curr, int max) {
publishDownloadProgress(p1[0] + ".jar", curr, max);
}
}
);
} catch (Throwable th) {
if (verInfo.inheritsFrom != null) {
minecraftMainFile.delete();
FileInputStream is = new FileInputStream(new File(Tools.DIR_HOME_VERSION, verInfo.inheritsFrom + "/" + verInfo.inheritsFrom + ".jar"));
FileOutputStream os = new FileOutputStream(minecraftMainFile);
IOUtils.copy(is, os);
is.close();
os.close();
} else {
throw th;
}
}
}
} catch (Throwable e) {
launchWithError = true;
throw e;
}
publishProgress("1", mActivity.getString(R.string.mcl_launch_cleancache));
// new File(inputPath).delete();
for (File f : new File(Tools.DIR_HOME_VERSION).listFiles()) {
if(f.getName().endsWith(".part")) {
Log.d(Tools.APP_NAME, "Cleaning cache: " + f);
f.delete();
}
}
mActivity.mIsAssetsProcessing = true;
mActivity.mPlayButton.post(new Runnable(){
@Override
public void run()
{
mActivity.mPlayButton.setText("Skip");
mActivity.mPlayButton.setEnabled(true);
}
});
if (assets == null) {
return null;
}
publishProgress("1", mActivity.getString(R.string.mcl_launch_download_assets));
setMax(assets.objects.size());
zeroProgress();
try {
downloadAssets(assets, verInfo.assets, assets.map_to_resources ? new File(Tools.OBSOLETE_RESOURCES_PATH) : new File(Tools.ASSETS_PATH));
} catch (Exception e) {
e.printStackTrace();
// Ignore it
launchWithError = false;
} finally {
mActivity.mIsAssetsProcessing = false;
}
} catch (Throwable th){
throwable = th;
} finally {
return throwable;
}
}
private int addProgress = 0;
public static class SilentException extends Exception{}
public void zeroProgress() {
addProgress = 0;
}
protected void downloadLibrary(DependentLibrary libItem,String libArtifact,File outLib) throws Throwable{
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, libItem.name));
String libPathURL;
boolean skipIfFailed = false;
if (libItem.downloads == null || libItem.downloads.artifact == null) {
System.out.println("UnkLib:"+libArtifact);
MinecraftLibraryArtifact artifact = new MinecraftLibraryArtifact();
artifact.url = (libItem.url == null ? "https://libraries.minecraft.net/" : libItem.url.replace("http://","https://")) + libArtifact;
libItem.downloads = new DependentLibrary.LibraryDownloads(artifact);
skipIfFailed = true;
}
try {
libPathURL = libItem.downloads.artifact.url;
boolean isFileGood = false;
byte timesChecked=0;
while(!isFileGood) {
timesChecked++;
if(timesChecked > 5) throw new RuntimeException("Library download failed after 5 retries");
Tools.downloadFileMonitored(
libPathURL,
outLib.getAbsolutePath(),
new Tools.DownloaderFeedback() {
@Override
public void updateProgress(int curr, int max) {
publishDownloadProgress(libItem.name, curr, max);
}
}
);
if(libItem.downloads.artifact.sha1 != null) {
isFileGood = LauncherPreferences.PREF_CHECK_LIBRARY_SHA ? Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1) : true;
if(!isFileGood) publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail,libItem.name));
else publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass,libItem.name));
}else{
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown,libItem.name));
isFileGood = true;
}
}
} catch (Throwable th) {
if (!skipIfFailed) {
throw th;
} else {
th.printStackTrace();
publishProgress("0", th.getMessage());
}
}
}
public void setMax(final int value)
{
mActivity.mLaunchProgress.post(new Runnable(){
@Override
public void run()
{
mActivity.mLaunchProgress.setMax(value);
}
});
}
private void publishDownloadProgress(String target, int curr, int max) {
// array length > 2 ignores append log on dev console
publishProgress("0", mActivity.getString(R.string.mcl_launch_downloading_progress, target,
curr / 1024d / 1024d, max / 1024d / 1024d), "");
}
@Override
protected void onProgressUpdate(String... p1)
{
int addedProg = Integer.parseInt(p1[0]);
if (addedProg != -1) {
addProgress = addProgress + addedProg;
mActivity.mLaunchProgress.setProgress(addProgress);
if(p1[1] != null) mActivity.mLaunchTextStatus.setText(p1[1]);
}
if (p1.length < 3) {
//mActivity.mConsoleView.putLog(p1[1] + "\n");
}
}
@Override
protected void onPostExecute(Throwable p1)
{
mActivity.mPlayButton.setText("Play");
mActivity.mPlayButton.setEnabled(true);
mActivity.mLaunchProgress.setMax(100);
mActivity.mLaunchProgress.setProgress(0);
mActivity.statusIsLaunching(false);
if(p1 != null && !(p1 instanceof SilentException)) {
p1.printStackTrace();
Tools.showError(mActivity, p1);
}
if(!launchWithError) {
//mActivity.mCrashView.setLastCrash("");
try {
Intent mainIntent = new Intent(mActivity, MainActivity.class /* MainActivity.class */);
// mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mActivity.startActivity(mainIntent);
}
catch (Throwable e) {
Tools.showError(mActivity, e);
}
}
mActivity.mTask = null;
}
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
public JAssets downloadIndex(String versionName, File output) throws IOException {
if (!output.exists()) {
output.getParentFile().mkdirs();
DownloadUtils.downloadFile(verInfo.assetIndex != null ? verInfo.assetIndex.url : "https://s3.amazonaws.com/Minecraft.Download/indexes/" + versionName + ".json", output);
}
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
}
public void downloadAsset(JAssetInfo asset, File objectsDir, AtomicInteger downloadCounter) throws IOException {
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = new File(objectsDir, assetPath);
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
int prevCurr;
@Override
public void updateProgress(int curr, int max) {
downloadCounter.addAndGet(curr - prevCurr);
prevCurr = curr;
}
});
}
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir, AtomicInteger downloadCounter) throws IOException {
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = new File(resDir,"/"+assetName);
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
int prevCurr;
@Override
public void updateProgress(int curr, int max) {
downloadCounter.addAndGet(curr - prevCurr);
prevCurr = curr;
}
});
}
public void downloadAssets(final JAssets assets, String assetsVersion, final File outputDir) throws IOException {
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 500, TimeUnit.MILLISECONDS, workQueue);
mActivity.mIsAssetsProcessing = true;
File hasDownloadedFile = new File(outputDir, "downloaded/" + assetsVersion + ".downloaded");
if (!hasDownloadedFile.exists()) {
System.out.println("Assets begin time: " + System.currentTimeMillis());
Map<String, JAssetInfo> assetsObjects = assets.objects;
int assetsSizeBytes=0;
AtomicInteger downloadedSize = new AtomicInteger(0);
AtomicBoolean localInterrupt = new AtomicBoolean(false);
File objectsDir = new File(outputDir, "objects");
zeroProgress();
for(String assetKey : assetsObjects.keySet()) {
if(!mActivity.mIsAssetsProcessing) break;
JAssetInfo asset = assetsObjects.get(assetKey);
assetsSizeBytes+=asset.size;
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = assets.map_to_resources?new File(objectsDir,"/"+assetKey):new File(objectsDir, assetPath);
boolean skip = outFile.exists();// skip if the file exists
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) //if sha checking is enabled
if(skip) skip = Tools.compareSHA1(outFile, asset.hash); //check hash
if(skip) {
downloadedSize.addAndGet(asset.size);
}else{
if(outFile.exists()) publishProgress("0",mActivity.getString(R.string.dl_library_sha_fail,assetKey));
executor.execute(()->{
try {
if (!assets.map_to_resources) {
downloadAsset(asset, objectsDir, downloadedSize);
} else {
downloadAssetMapped(asset, assetKey, outputDir, downloadedSize);
}
}catch (IOException e) {
e.printStackTrace();
localInterrupt.set(true);
}
});
}
}
mActivity.mLaunchProgress.setMax(assetsSizeBytes);
executor.shutdown();
try {
int prevDLSize=0;
System.out.println("Queue size: "+workQueue.size());
while ((!executor.awaitTermination(250, TimeUnit.MILLISECONDS))&&(!localInterrupt.get())&&mActivity.mIsAssetsProcessing) {
int DLSize = downloadedSize.get();
publishProgress(Integer.toString(DLSize-prevDLSize),null,"");
publishDownloadProgress("assets", DLSize, assetsSizeBytes);
prevDLSize = downloadedSize.get();
}
if(mActivity.mIsAssetsProcessing) {
System.out.println("Unskipped download done!");
if(!hasDownloadedFile.getParentFile().exists())hasDownloadedFile.getParentFile().mkdirs();
hasDownloadedFile.createNewFile();
}else{
System.out.println("Skipped!");
}
executor.shutdownNow();
while (!executor.awaitTermination(250, TimeUnit.MILLISECONDS)) {}
System.out.println("Fully shut down!");
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("Assets end time: " + System.currentTimeMillis());
}
}
private JMinecraftVersionList.Version findVersion(String version) {
if (mActivity.mVersionList != null) {
for (JMinecraftVersionList.Version valueVer: mActivity.mVersionList.versions) {
if (valueVer.id.equals(version)) {
return valueVer;
}
}
}
// Custom version, inherits from base.
return Tools.getVersionInfo(mActivity,version);
}
}

View file

@ -1,140 +0,0 @@
package net.kdt.pojavlaunch.tasks;
import android.os.*;
import android.util.Log;
import android.view.*;
import android.widget.*;
import android.widget.AdapterView.*;
import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.*;
import androidx.appcompat.widget.PopupMenu;
public class RefreshVersionListTask extends AsyncTask<Void, Void, ArrayList<String>>
{
private BaseLauncherActivity mActivity;
public RefreshVersionListTask(BaseLauncherActivity activity) {
mActivity = activity;
}
@Override
protected ArrayList<String> doInBackground(Void[] p1)
{
try {
//mActivity.mVersionList = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString(""), JMinecraftVersionList.class);
{
ArrayList<JMinecraftVersionList.Version> versions = new ArrayList<>();
String[] repositories = LauncherPreferences.PREF_VERSION_REPOS.split(";");
for (String url : repositories) {
JMinecraftVersionList list;
Log.i("ExtVL", "Syncing to external: " + url);
list = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString(url), JMinecraftVersionList.class);
Log.i("ExtVL","Downloaded the version list, len="+list.versions.length);
Collections.addAll(versions,list.versions);
}
mActivity.mVersionList = new JMinecraftVersionList();
mActivity.mVersionList.versions = versions.toArray(new JMinecraftVersionList.Version[versions.size()]);
Log.i("ExtVL","Final list size: " + mActivity.mVersionList.versions.length);
}
return filter(mActivity.mVersionList.versions, new File(Tools.DIR_HOME_VERSION).listFiles());
} catch (Exception e){
System.out.println("Refreshing version list failed !");
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(ArrayList<String> result)
{
super.onPostExecute(result);
final PopupMenu popup = new PopupMenu(mActivity, mActivity.mVersionSelector);
popup.getMenuInflater().inflate(R.menu.menu_versionopt, popup.getMenu());
if(result != null && result.size() > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item, result);
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mActivity.mVersionSelector.setAdapter(adapter);
mActivity.mVersionSelector.setSelection(selectAt(result.toArray(new String[0]), mActivity.mProfile.selectedVersion));
} else {
mActivity.mVersionSelector.setSelection(selectAt(mActivity.mAvailableVersions, mActivity.mProfile.selectedVersion));
}
PerVersionConfigDialog dialog = new PerVersionConfigDialog(this.mActivity);
mActivity.mVersionSelector.setOnLongClickListener((v)->dialog.openConfig(mActivity.mProfile.selectedVersion));
mActivity.mVersionSelector.setOnItemSelectedListener(new OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView<?> p1, View p2, int p3, long p4)
{
mActivity.mProfile.selectedVersion = p1.getItemAtPosition(p3).toString();
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile);
if (PojavProfile.isFileType(mActivity)) {
try {
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile.save());
} catch (IOException e) {
Tools.showError(mActivity, e);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> p1)
{
// TODO: Implement this method
}
});
/*mActivity.mVersionSelector.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){
@Override
public boolean onItemLongClick(AdapterView<?> p1, View p2, int p3, long p4)
{
// Implement copy, remove, reinstall,...
return true;
}
});
*/
popup.setOnMenuItemClickListener(item -> true);
}
private ArrayList<String> filter(JMinecraftVersionList.Version[] list1, File[] list2) {
ArrayList<String> output = new ArrayList<String>();
for (JMinecraftVersionList.Version value1: list1) {
if ((value1.type.equals("release") && LauncherPreferences.PREF_VERTYPE_RELEASE) ||
(value1.type.equals("snapshot") && LauncherPreferences.PREF_VERTYPE_SNAPSHOT) ||
(value1.type.equals("old_alpha") && LauncherPreferences.PREF_VERTYPE_OLDALPHA) ||
(value1.type.equals("old_beta") && LauncherPreferences.PREF_VERTYPE_OLDBETA) ||
(value1.type.equals("modified"))) {
output.add(value1.id);
}
}
if(list2 != null) for (File value2: list2) {
if (!output.contains(value2.getName())) {
output.add(value2.getName());
}
}
return output;
}
private int selectAt(String[] strArr, String select) {
int count = 0;
for(String str : strArr){
if (str.equals(select)) {
return count;
}
count++;
}
return -1;
}
}

View file

@ -1,138 +0,0 @@
package net.kdt.pojavlaunch.utils;
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
import android.os.Build;
import android.os.FileObserver;
import android.util.Log;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.Tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
public class MCOptionUtils
{
private static final HashMap<String,String> parameterMap = new HashMap<>();
private static final ArrayList<WeakReference<MCOptionListener>> optionListeners = new ArrayList<>();
private static FileObserver fileObserver;
public interface MCOptionListener {
/** Called when an option is changed. Don't know which one though */
void onOptionChanged();
}
public static void load() {
if(fileObserver == null){
setupFileObserver();
}
parameterMap.clear();
try {
BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt"));
String line;
while ((line = reader.readLine()) != null) {
int firstColonIndex = line.indexOf(':');
if(firstColonIndex < 0) {
Log.w(Tools.APP_NAME, "No colon on line \""+line+"\", skipping");
continue;
}
parameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1));
}
reader.close();
} catch (IOException e) {
Log.w(Tools.APP_NAME, "Could not load options.txt", e);
}
}
public static void set(String key, String value) {
parameterMap.put(key,value);
}
public static String get(String key){
return parameterMap.get(key);
}
public static void save() {
StringBuilder result = new StringBuilder();
for(String key : parameterMap.keySet())
result.append(key)
.append(':')
.append(parameterMap.get(key))
.append('\n');
try {
Tools.write(Tools.DIR_GAME_NEW + "/options.txt", result.toString());
} catch (IOException e) {
Log.w(Tools.APP_NAME, "Could not save options.txt", e);
}
}
/** @return The stored Minecraft GUI scale, also auto-computed if on auto-mode or improper setting */
public static int getMcScale() {
MCOptionUtils.load();
String str = MCOptionUtils.get("guiScale");
int guiScale = (str == null ? 0 :Integer.parseInt(str));
int scale = Math.max(Math.min(windowWidth / 320, windowHeight / 240), 1);
if(scale < guiScale || guiScale == 0){
guiScale = scale;
}
return guiScale;
}
private static void setupFileObserver(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
fileObserver = new FileObserver(new File(Tools.DIR_GAME_NEW + "/options.txt"), FileObserver.MODIFY) {
@Override
public void onEvent(int i, @Nullable String s) {
MCOptionUtils.load();
notifyListeners();
}
};
}else{
fileObserver = new FileObserver(Tools.DIR_GAME_NEW + "/options.txt", FileObserver.MODIFY) {
@Override
public void onEvent(int i, @Nullable String s) {
MCOptionUtils.load();
notifyListeners();
}
};
}
fileObserver.startWatching();
}
public static void notifyListeners(){
for(WeakReference<MCOptionListener> weakReference : optionListeners){
MCOptionListener optionListener = weakReference.get();
if(optionListener == null) continue;
optionListener.onOptionChanged();
}
}
public static void addMCOptionListener(MCOptionListener listener){
optionListeners.add(new WeakReference<>(listener));
}
public static void removeMCOptionListener(MCOptionListener listener){
for(WeakReference<MCOptionListener> weakReference : optionListeners){
MCOptionListener optionListener = weakReference.get();
if(optionListener == null) continue;
if(optionListener == listener){
optionListeners.remove(weakReference);
return;
}
}
}
}

View file

@ -1,167 +0,0 @@
package net.kdt.pojavlaunch.utils;
import android.app.Activity;
import android.content.res.AssetManager;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.tasks.MinecraftDownloaderTask;
import net.kdt.pojavlaunch.value.PerVersionConfig;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class V117CompatUtil {
/*
/*
New rendering engine was added in snapshot 21w10a
21a08b (FP (GL2) engine): 20210225
21w10a (non-FP engine): 20210310
boolean skipResDialog = false;
if(mcReleaseDate > 20210225) skipResDialog = true;
PerVersionConfig.update(); //Prepare the PVC
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]); //Get the version config...
if (cfg == null) {
cfg = new PerVersionConfig.VersionConfig();//or create a new one!
PerVersionConfig.configMap.put(p1[0], cfg);//and put it into the base
}
MCOptionUtils.load();
if(!skipResDialog) {
AtomicBoolean proceed = new AtomicBoolean(false);
Object lock = new Object();
mActivity.runOnUiThread(() -> {
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
bldr.setTitle(R.string.global_warinng);
bldr.setMessage(R.string.compat_117_message);
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
proceed.set(true);
synchronized (lock) { lock.notifyAll(); }
dialog.dismiss();
});
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
synchronized (lock) { lock.notifyAll(); }
dialog.dismiss();
});
bldr.setCancelable(false);
bldr.show();
});
synchronized (lock) {
lock.wait();
}
if(proceed.get()) {
File resourcepacksDir = new File(cfg.gamePath==null? Tools.DIR_GAME_NEW:cfg.gamePath,"resourcepacks");
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
InputStream is = this.mActivity.getAssets().open("assets-v0.zip");
IOUtils.copy(is,fos);
is.close();fos.close();
String resourcepacks = MCOptionUtils.get("resourcePacks");
if(resourcepacks == null || !resourcepacks.contains("assets-v0.zip")) {
List<String> resPacksArray = resourcepacks == null ? new ArrayList(): Arrays.asList()
}
}else throw new MinecraftDownloaderTask.SilentException();
}
*/
private static List<String> getTexturePackList(String param) {
if (param == null) {
Log.i("V117CompatDebug","null, defaulting to empty");
return new ArrayList<>();
}
Log.i("V117CompatDebug",param);
if("[]".equals(param)) return new ArrayList<>();
Log.i("V117CompatDebug","ph2");
if(param == null) return new ArrayList<>();
Log.i("V117CompatDebug","ph3");
String rawList = param.substring(1,param.length()-1);
Log.i("V117CompatDebug",rawList);
return new ArrayList<>(Arrays.asList(rawList.split(",")));
}
private static String regenPackList(List<String> packs) {
if(packs.size()==0) return "[]";
String ret = "["+packs.get(0);
for(int i = 1; i < packs.size(); i++) {
ret += ","+packs.get(i);
}
ret += "]";
return ret;
}
public static void runCheck(String version, Activity ctx) throws Exception{
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(version);
MCOptionUtils.load();
List<String> packList =getTexturePackList(MCOptionUtils.get("resourcePacks"));
String renderer = cfg != null && cfg.renderer != null?cfg.renderer:LauncherPreferences.PREF_RENDERER;
if(renderer.equals("vulkan_zink") || renderer.equals("opengles3_virgl")) return; //don't install for zink/virgl users;
if(packList.contains("\"assets-v0.zip\"") && renderer.equals("opengles3")) return;
Object lock = new Object();
AtomicInteger proceed = new AtomicInteger(0);
ctx.runOnUiThread(() -> {
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
bldr.setTitle(R.string.global_warinng);
bldr.setMessage(R.string.compat_117_message);
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
proceed.set(1);
synchronized (lock) { lock.notifyAll(); }
dialog.dismiss();
});
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
synchronized (lock) { lock.notifyAll(); }
dialog.dismiss();
});
bldr.setNeutralButton(R.string.compat_11x_playanyway, (dialog, which) -> {
proceed.set(2);
synchronized (lock) { lock.notifyAll(); }
dialog.dismiss();
});
bldr.setCancelable(false);
bldr.show();
});
synchronized (lock) {
lock.wait();
}
switch(proceed.get()) {
case 1:
if (cfg == null) {
cfg = new PerVersionConfig.VersionConfig();
PerVersionConfig.configMap.put(version, cfg);
}
cfg.renderer = "opengles3";
String path = Tools.DIR_GAME_NEW;
if(cfg.gamePath != null && !cfg.gamePath.isEmpty()) path = cfg.gamePath;
copyResourcePack(path,ctx.getAssets());
if(!packList.contains("\"assets-v0.zip\"")) packList.add(0,"\"assets-v0.zip\"");
MCOptionUtils.set("resourcePacks",regenPackList(packList));
MCOptionUtils.save();
PerVersionConfig.update();
break;
case 0:
throw new MinecraftDownloaderTask.SilentException();
}
}
public static void copyResourcePack(String gameDir, AssetManager am) throws IOException {
File resourcepacksDir = new File(gameDir,"resourcepacks");
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
InputStream is = am.open("assets-v0.zip");
IOUtils.copy(is,fos);
is.close();fos.close();
}
}

View file

@ -1,51 +0,0 @@
package net.kdt.pojavlaunch.value;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.DownloadUtils;
public class AccountSkin {
public static Bitmap getSkin(String uuid) throws IOException {
Profile p = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid), Profile.class);
for (Property property : p.properties) {
if (property.name.equals("textures")) {
return getSkinFromProperty(Tools.GLOBAL_GSON.fromJson(new String(Base64.decode(property.value, Base64.DEFAULT), "UTF-8"), SkinProperty.class));
}
}
return null;
}
private static Bitmap getSkinFromProperty(SkinProperty p) throws IOException {
for (Map.Entry<String, Texture> texture : p.textures.entrySet()) {
if (texture.getKey().equals("SKIN")) {
String skinFile = File.createTempFile("skin", ".png", new File(Tools.DIR_DATA, "cache")).getAbsolutePath();
Tools.downloadFile(texture.getValue().url.replace("http://","https://"), skinFile);
return BitmapFactory.decodeFile(skinFile);
}
}
return null;
}
public static class Texture {
public String url;
}
public static class SkinProperty {
public Map<String, Texture> textures;
}
public static class Property {
public String name, value;
}
public static class Profile {
public Property[] properties;
}
}

View file

@ -1,19 +0,0 @@
package net.kdt.pojavlaunch.value;
import androidx.annotation.Keep;
@Keep
public class DependentLibrary {
public String name;
public LibraryDownloads downloads;
public String url;
@Keep
public static class LibraryDownloads {
public MinecraftLibraryArtifact artifact;
public LibraryDownloads(MinecraftLibraryArtifact artifact) {
this.artifact = artifact;
}
}
}

View file

@ -1,24 +0,0 @@
package net.kdt.pojavlaunch.value;
import net.kdt.pojavlaunch.*;
public class ForgeInstallProfile {
// ----- < 1.12.2 Forge Install Profile -----
public ForgeInstallProperties install;
public JMinecraftVersionList.Version versionInfo;
public static class ForgeInstallProperties {
public String profileName;
public String target;
public String path;
public String version;
public String filePath; // universal file .jar
public String minecraft; // target Minecraft version
}
// ----- 1.12.2+ Forge Install Profile -----
public String version;
public String json;
public String path;
public String minecraft; // target Minecraft version
}

View file

@ -1,110 +0,0 @@
package net.kdt.pojavlaunch.value;
import android.graphics.BitmapFactory;
import android.util.Log;
import net.kdt.pojavlaunch.*;
import java.io.*;
import com.google.gson.*;
import android.graphics.Bitmap;
import android.util.Base64;
import org.apache.commons.io.IOUtils;
public class MinecraftAccount
{
public String accessToken = "0"; // access token
public String clientToken = "0"; // clientID: refresh and invalidate
public String profileId = "00000000-0000-0000-0000-000000000000"; // profile UUID, for obtaining skin
public String username = "Steve";
public String selectedVersion = "1.7.10";
public boolean isMicrosoft = false;
public String msaRefreshToken = "0";
public String skinFaceBase64;
void updateSkinFace(String uuid) {
try {
File skinFile = File.createTempFile("skin", ".png", new File(Tools.DIR_DATA, "cache"));
Tools.downloadFile("https://mc-heads.net/head/" + uuid + "/100", skinFile.getAbsolutePath());
skinFaceBase64 = Base64.encodeToString(IOUtils.toByteArray(new FileInputStream(skinFile)), Base64.DEFAULT);
Log.i("SkinLoader", "Update skin face success");
} catch (IOException e) {
// Skin refresh limit, no internet connection, etc...
// Simply ignore updating skin face
Log.w("SkinLoader", "Could not update skin face", e);
}
}
public void updateSkinFace() {
updateSkinFace(profileId);
}
public String save(String outPath) throws IOException {
Tools.write(outPath, Tools.GLOBAL_GSON.toJson(this));
return username;
}
public String save() throws IOException {
return save(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json");
}
public static MinecraftAccount parse(String content) throws JsonSyntaxException {
return Tools.GLOBAL_GSON.fromJson(content, MinecraftAccount.class);
}
public static MinecraftAccount load(String name) throws JsonSyntaxException {
if(!accountExists(name)) return new MinecraftAccount();
try {
MinecraftAccount acc = parse(Tools.read(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json"));
if (acc.accessToken == null) {
acc.accessToken = "0";
}
if (acc.clientToken == null) {
acc.clientToken = "0";
}
if (acc.profileId == null) {
acc.profileId = "00000000-0000-0000-0000-000000000000";
}
if (acc.username == null) {
acc.username = "0";
}
if (acc.selectedVersion == null) {
acc.selectedVersion = "1.7.10";
}
if (acc.msaRefreshToken == null) {
acc.msaRefreshToken = "0";
}
if (acc.skinFaceBase64 == null) {
// acc.updateSkinFace("MHF_Steve");
}
return acc;
} catch(IOException e) {
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the profile",e);
return null;
}
}
public Bitmap getSkinFace(){
if(skinFaceBase64 == null){
return Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
}
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
}
private static boolean accountExists(String username){
return new File(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json").exists();
}
public static void clearTempAccount() {
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
tempAccFile.delete();
}
public static void saveTempAccount(MinecraftAccount acc) throws IOException {
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
tempAccFile.delete();
acc.save(tempAccFile.getAbsolutePath());
}
}

View file

@ -1,8 +0,0 @@
package net.kdt.pojavlaunch.value;
public class MinecraftClientInfo
{
public String sha1;
public int size;
public String url;
}

View file

@ -1,6 +0,0 @@
package net.kdt.pojavlaunch.value;
public class MinecraftLibraryArtifact extends MinecraftClientInfo
{
public String path;
}

View file

@ -1,32 +0,0 @@
package net.kdt.pojavlaunch.value;
import com.google.gson.reflect.TypeToken;
import net.kdt.pojavlaunch.Tools;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
public class PerVersionConfig {
static File pvcFile;
public static HashMap<String,VersionConfig> configMap;
public static void update() throws IOException {
if(configMap == null) {
pvcFile = new File(Tools.DIR_GAME_HOME,"per-version-config.json");
if(pvcFile.exists()) {
configMap = Tools.GLOBAL_GSON.fromJson(Tools.read(pvcFile.getAbsolutePath()), new TypeToken<HashMap<String,VersionConfig>>() {}.getType());
}else{
configMap = new HashMap<>();
}
}else{
Tools.write(pvcFile.getAbsolutePath(),Tools.GLOBAL_GSON.toJson(configMap));
}
}
public static class VersionConfig {
public String jvmArgs;
public String gamePath;
public String selectedRuntime;
public String renderer;
}
}

View file

@ -1,37 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import net.kdt.pojavlaunch.*;
import java.io.*;
public class LauncherProfiles
{
public static MinecraftLauncherProfiles mainProfileJson;
public static File launcherProfilesFile = new File(Tools.DIR_GAME_NEW + "/launcher_profiles.json");
public static MinecraftLauncherProfiles update() {
try {
if (mainProfileJson == null) {
if (launcherProfilesFile.exists()) {
mainProfileJson = Tools.GLOBAL_GSON.fromJson(Tools.read(launcherProfilesFile.getAbsolutePath()), MinecraftLauncherProfiles.class);
} else {
mainProfileJson = new MinecraftLauncherProfiles();
}
} else {
Tools.write(launcherProfilesFile.getAbsolutePath(), mainProfileJson.toJson());
}
// insertMissing();
return mainProfileJson;
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
/*
public static String insert;
private static void insertMissing() {
if (mainProfileJson.authenticationDatabase == null) {
MinecraftAuthenticationDatabase mad = new MinecraftAuthenticationDatabase();
mainProfileJson.authenticationDatabase = mad;
}
}
*/
}

View file

@ -1,10 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftAuthenticationDatabase
{
public String accessToken;
public String displayName;
public String username;
public String uuid;
// public MinecraftProfile[] profiles;
}

View file

@ -1,19 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import java.util.*;
import net.kdt.pojavlaunch.*;
public class MinecraftLauncherProfiles
{
public Map<String, MinecraftProfile> profiles;
public String clientToken;
public Map<String, MinecraftAuthenticationDatabase> authenticationDatabase;
// public Map launcherVersion;
public MinecraftLauncherSettings settings;
// public Map analyticsToken;
public int analyticsFailcount;
public MinecraftSelectedUser selectedUser;
public String toJson() {
return Tools.GLOBAL_GSON.toJson(this);
}
}

View file

@ -1,15 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftLauncherSettings
{
public boolean enableSnapshots;
public boolean enableAdvanced;
public boolean keepLauncherOpen;
public boolean showGameLog;
public String locale;
public boolean showMenu;
public boolean enableHistorical;
public String profileSorting;
public boolean crashAssistance;
public boolean enableAnalytics;
}

View file

@ -1,17 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftProfile
{
public String name;
public String type;
public String created;
public String lastUsed;
public String icon;
public String lastVersionId;
public String gameDir;
public String javaDir;
public String javaArgs;
public String logConfig;
public boolean logConfigIsXML;
public MinecraftResolution[] resolution;
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftResolution
{
public int width;
public int height;
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftSelectedUser
{
public String account;
public String profile;
}