mirror of
https://github.com/brunodev85/winlator.git
synced 2026-10-20 22:11:47 +03:00
Update app
This commit is contained in:
1 parent
440956199e
commit
e6dd7a5976
45 files changed
+1168
-415
No files matched your search
+2
-2
@@ -8,8 +8,8 @@ android {
|
||||
applicationId 'com.winlator'
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 28
|
||||
versionCode 9
|
||||
versionName "3.2"
|
||||
versionCode 10
|
||||
versionName "4.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
<activity android:name="com.winlator.ExternalControllerBindingsActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:screenOrientation="sensor" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,4 +1,5 @@
|
||||
[
|
||||
{"name" : "BOX64_DYNAREC", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
{"name" : "BOX64_DYNAREC_SAFEFLAGS", "values" : ["0", "1", "2"], "defaultValue" : "2"},
|
||||
{"name" : "BOX64_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
{"name" : "BOX64_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
|
||||
BIN
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
[
|
||||
{"name" : "BOX86_DYNAREC", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
{"name" : "BOX86_DYNAREC_SAFEFLAGS", "values" : ["0", "1", "2"], "defaultValue" : "2"},
|
||||
{"name" : "BOX86_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
{"name" : "BOX86_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"id":3,"name":"Virtual Gamepad","cursorSpeed":1,"elements":[{"type":"D_PAD","shape":"CIRCLE","bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.85,"x":0.21568627655506134,"y":0.35555556416511536,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.8133170008659363,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.8721405267715454,"y":0.6000000238418579,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.8721405267715454,"y":0.8666666746139526,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.9309640526771545,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1,"x":0.9215686321258545,"y":0.2888889014720917,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1,"x":0.9215686321258545,"y":0.42222222685813904,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1,"x":0.0784313753247261,"y":0.42222222685813904,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1,"x":0.0784313753247261,"y":0.2888889014720917,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.85,"x":0.538807213306427,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.85,"x":0.46078431606292725,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1,"x":0.10784313827753067,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1,"x":0.7843137383460999,"y":0.35555556416511536,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.85,"x":0.21568627655506134,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.85,"x":0.7843137383460999,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0}]}
|
||||
Binary file not shown.
@@ -58,7 +58,6 @@ public class ContainerDetailFragment extends Fragment {
|
||||
private PreloaderDialog preloaderDialog;
|
||||
private JSONArray gpuNames;
|
||||
private Callback<String> openDirectoryCallback;
|
||||
private final String[] defaultDLLOverrides = {"d3d8", "d3d9", "d3d10", "d3d10_1", "d3d10core", "d3d11", "d3d12", "d3d12core", "ddraw", "dxgi", "wined3d"};
|
||||
|
||||
public ContainerDetailFragment() {
|
||||
this(0);
|
||||
@@ -276,9 +275,6 @@ public class ContainerDetailFragment extends Fragment {
|
||||
|
||||
registryEditor.setStringValue("Software\\Wine\\Direct3D", "shader_backend", "glsl");
|
||||
registryEditor.setStringValue("Software\\Wine\\Direct3D", "UseGLSL", "enabled");
|
||||
|
||||
final String dllOverridesKey = "Software\\Wine\\DllOverrides";
|
||||
for (String name : defaultDLLOverrides) registryEditor.setStringValue(dllOverridesKey, name, "native,builtin");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.winlator.core.AppUtils;
|
||||
import com.winlator.core.FileUtils;
|
||||
import com.winlator.core.UnitUtils;
|
||||
import com.winlator.widget.InputControlsView;
|
||||
import com.winlator.widget.NumberPicker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -117,6 +118,14 @@ public class ControlsEditorActivity extends AppCompatActivity implements View.On
|
||||
inputControlsView.invalidate();
|
||||
});
|
||||
|
||||
NumberPicker npColumns = view.findViewById(R.id.NPColumns);
|
||||
npColumns.setValue(element.getBindingCount());
|
||||
npColumns.setOnValueChangeListener((numberPicker, value) -> {
|
||||
element.setBindingCount(value);
|
||||
profile.save();
|
||||
inputControlsView.invalidate();
|
||||
});
|
||||
|
||||
final TextView tvScale = view.findViewById(R.id.TVScale);
|
||||
SeekBar sbScale = view.findViewById(R.id.SBScale);
|
||||
sbScale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@@ -229,7 +238,19 @@ public class ControlsEditorActivity extends AppCompatActivity implements View.On
|
||||
final Spinner sBinding = view.findViewById(R.id.SBinding);
|
||||
|
||||
Runnable update = () -> {
|
||||
String[] bindingEntries = sBindingType.getSelectedItemPosition() == 0 ? Binding.keyboardBindingLabels() : Binding.mouseBindingLabels();
|
||||
String[] bindingEntries = null;
|
||||
switch (sBindingType.getSelectedItemPosition()) {
|
||||
case 0:
|
||||
bindingEntries = Binding.keyboardBindingLabels();
|
||||
break;
|
||||
case 1:
|
||||
bindingEntries = Binding.mouseBindingLabels();
|
||||
break;
|
||||
case 2:
|
||||
bindingEntries = Binding.gamepadBindingLabels();
|
||||
break;
|
||||
}
|
||||
|
||||
sBinding.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, bindingEntries));
|
||||
AppUtils.setSpinnerSelectionFromValue(sBinding, element.getBindingAt(index).toString());
|
||||
};
|
||||
@@ -243,13 +264,34 @@ public class ControlsEditorActivity extends AppCompatActivity implements View.On
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
sBindingType.setSelection(element.getBindingAt(index).isKeyboard() ? 0 : 1, false);
|
||||
|
||||
Binding selectedBinding = element.getBindingAt(index);
|
||||
if (selectedBinding.isKeyboard()) {
|
||||
sBindingType.setSelection(0, false);
|
||||
}
|
||||
else if (selectedBinding.isMouse()) {
|
||||
sBindingType.setSelection(1, false);
|
||||
}
|
||||
else if (selectedBinding.isGamepad()) {
|
||||
sBindingType.setSelection(2, false);
|
||||
}
|
||||
|
||||
sBinding.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
boolean isKeyboard = sBindingType.getSelectedItemPosition() == 0;
|
||||
Binding binding = isKeyboard ? Binding.keyboardBindingValues()[position] : Binding.mouseBindingValues()[position];
|
||||
Binding binding = Binding.NONE;
|
||||
switch (sBindingType.getSelectedItemPosition()) {
|
||||
case 0:
|
||||
binding = Binding.keyboardBindingValues()[position];
|
||||
break;
|
||||
case 1:
|
||||
binding = Binding.mouseBindingValues()[position];
|
||||
break;
|
||||
case 2:
|
||||
binding = Binding.gamepadBindingValues()[position];
|
||||
break;
|
||||
}
|
||||
|
||||
if (binding != element.getBindingAt(index)) {
|
||||
element.setBindingAt(index, binding);
|
||||
profile.save();
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -76,6 +75,7 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
|
||||
|
||||
private void updateControllerBinding(int keyCode, Binding binding) {
|
||||
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) return;
|
||||
|
||||
ExternalControllerBinding controllerBinding = controller.getControllerBinding(keyCode);
|
||||
int position;
|
||||
if (controllerBinding == null) {
|
||||
@@ -93,28 +93,29 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
|
||||
recyclerView.scrollToPosition(position);
|
||||
}
|
||||
|
||||
private void processJoystickInput(MotionEvent event, int historyPos) {
|
||||
private void processJoystickInput() {
|
||||
int keyCode = KeyEvent.KEYCODE_UNKNOWN;
|
||||
byte sign;
|
||||
Binding binding = Binding.NONE;
|
||||
|
||||
final int[] axes = {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
|
||||
for (int axis : axes) {
|
||||
if ((sign = Mathf.sign(ExternalController.getCenteredAxis(event, axis, historyPos))) != 0) {
|
||||
if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Z) {
|
||||
final float[] values = {controller.state.thumbLX, controller.state.thumbLY, controller.state.thumbRX, controller.state.thumbRY, controller.state.getDPadX(), controller.state.getDPadY()};
|
||||
|
||||
byte sign;
|
||||
for (int i = 0; i < axes.length; i++) {
|
||||
if ((sign = Mathf.sign(values[i])) != 0) {
|
||||
if (axes[i] == MotionEvent.AXIS_X || axes[i] == MotionEvent.AXIS_Z) {
|
||||
binding = sign > 0 ? Binding.MOUSE_MOVE_RIGHT : Binding.MOUSE_MOVE_LEFT;
|
||||
}
|
||||
else if (axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_RZ) {
|
||||
else if (axes[i] == MotionEvent.AXIS_Y || axes[i] == MotionEvent.AXIS_RZ) {
|
||||
binding = sign > 0 ? Binding.MOUSE_MOVE_DOWN : Binding.MOUSE_MOVE_UP;
|
||||
}
|
||||
else if (axis == MotionEvent.AXIS_HAT_X) {
|
||||
else if (axes[i] == MotionEvent.AXIS_HAT_X) {
|
||||
binding = sign > 0 ? Binding.KEY_D : Binding.KEY_A;
|
||||
}
|
||||
else if (axis == MotionEvent.AXIS_HAT_Y) {
|
||||
else if (axes[i] == MotionEvent.AXIS_HAT_Y) {
|
||||
binding = sign > 0 ? Binding.KEY_S : Binding.KEY_W;
|
||||
}
|
||||
|
||||
keyCode = ExternalControllerBinding.getKeyCodeForAxis(axis, sign);
|
||||
keyCode = ExternalControllerBinding.getKeyCodeForAxis(axes[i], sign);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -122,41 +123,24 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
|
||||
updateControllerBinding(keyCode, binding);
|
||||
}
|
||||
|
||||
private void processTriggerButton(MotionEvent event) {
|
||||
if (event.getAxisValue(MotionEvent.AXIS_LTRIGGER) == 1 || event.getAxisValue(MotionEvent.AXIS_BRAKE) == 1) {
|
||||
updateControllerBinding(ExternalControllerBinding.AXIS_LTRIGGER, Binding.NONE);
|
||||
}
|
||||
if (event.getAxisValue(MotionEvent.AXIS_RTRIGGER) == 1 || event.getAxisValue(MotionEvent.AXIS_GAS) == 1) {
|
||||
updateControllerBinding(ExternalControllerBinding.AXIS_RTRIGGER, Binding.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if (event.getDeviceId() == controller.getDeviceId()) {
|
||||
if (ExternalController.isDPadDevice(event)) {
|
||||
processTriggerButton(event);
|
||||
processJoystickInput(event, -1);
|
||||
return true;
|
||||
}
|
||||
else if (ExternalController.isJoystickDevice(event)) {
|
||||
int historySize = event.getHistorySize();
|
||||
for (int i = 0; i < historySize; i++) processJoystickInput(event, i);
|
||||
processJoystickInput(event, -1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD &&
|
||||
event.getDeviceId() == controller.getDeviceId() && event.getRepeatCount() == 0) {
|
||||
updateControllerBinding(keyCode, Binding.NONE);
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
||||
if (event.getDeviceId() == controller.getDeviceId() && controller.updateStateFromMotionEvent(event)) {
|
||||
if (controller.state.isPressed(ExternalController.IDX_BUTTON_L2)) updateControllerBinding(KeyEvent.KEYCODE_BUTTON_L2, Binding.NONE);
|
||||
if (controller.state.isPressed(ExternalController.IDX_BUTTON_R2)) updateControllerBinding(KeyEvent.KEYCODE_BUTTON_R2, Binding.NONE);
|
||||
processJoystickInput();
|
||||
return true;
|
||||
}
|
||||
else return super.onKeyDown(keyCode, event);
|
||||
return super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getDeviceId() == controller.getDeviceId() && event.getRepeatCount() == 0) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) updateControllerBinding(event.getKeyCode(), Binding.NONE);
|
||||
return true;
|
||||
}
|
||||
else return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -208,7 +192,19 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
|
||||
final Context $this = ExternalControllerBindingsActivity.this;
|
||||
|
||||
Runnable update = () -> {
|
||||
String[] bindingEntries = holder.bindingType.getSelectedItemPosition() == 0 ? Binding.keyboardBindingLabels() : Binding.mouseBindingLabels();
|
||||
String[] bindingEntries = null;
|
||||
switch (holder.bindingType.getSelectedItemPosition()) {
|
||||
case 0:
|
||||
bindingEntries = Binding.keyboardBindingLabels();
|
||||
break;
|
||||
case 1:
|
||||
bindingEntries = Binding.mouseBindingLabels();
|
||||
break;
|
||||
case 2:
|
||||
bindingEntries = Binding.gamepadBindingLabels();
|
||||
break;
|
||||
}
|
||||
|
||||
holder.binding.setAdapter(new ArrayAdapter<>($this, android.R.layout.simple_spinner_dropdown_item, bindingEntries));
|
||||
AppUtils.setSpinnerSelectionFromValue(holder.binding, item.getBinding().toString());
|
||||
};
|
||||
@@ -227,8 +223,19 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
|
||||
holder.binding.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
boolean isKeyboard = holder.bindingType.getSelectedItemPosition() == 0;
|
||||
Binding binding = isKeyboard ? Binding.keyboardBindingValues()[position] : Binding.mouseBindingValues()[position];
|
||||
Binding binding = Binding.NONE;
|
||||
switch (holder.bindingType.getSelectedItemPosition()) {
|
||||
case 0:
|
||||
binding = Binding.keyboardBindingValues()[position];
|
||||
break;
|
||||
case 1:
|
||||
binding = Binding.mouseBindingValues()[position];
|
||||
break;
|
||||
case 2:
|
||||
binding = Binding.gamepadBindingValues()[position];
|
||||
break;
|
||||
}
|
||||
|
||||
if (binding != item.getBinding()) {
|
||||
item.setBinding(binding);
|
||||
profile.save();
|
||||
|
||||
@@ -26,10 +26,10 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.winlator.contentdialog.ContentDialog;
|
||||
import com.winlator.core.Callback;
|
||||
import com.winlator.core.OBBImageInstaller;
|
||||
import com.winlator.core.PreloaderDialog;
|
||||
import com.winlator.contentdialog.ContentDialog;
|
||||
import com.winlator.xenvironment.ImageFs;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -53,7 +53,7 @@ import java.util.concurrent.Executors;
|
||||
|
||||
public class SettingsFragment extends Fragment {
|
||||
public static final String DEFAULT_BOX86_VERSION = "0.3.0";
|
||||
public static final String DEFAULT_BOX64_VERSION = "0.2.5";
|
||||
public static final String DEFAULT_BOX64_VERSION = "0.2.6";
|
||||
private Callback<Uri> selectWineFileCallback;
|
||||
private PreloaderDialog preloaderDialog;
|
||||
private SharedPreferences preferences;
|
||||
@@ -96,17 +96,27 @@ public class SettingsFragment extends Fragment {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final Spinner sBox86Version = view.findViewById(R.id.SBox86Version);
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sBox86Version, preferences.getString("box86_version", DEFAULT_BOX86_VERSION));
|
||||
String box86Version = preferences.getString("box86_version", DEFAULT_BOX86_VERSION);
|
||||
if (!AppUtils.setSpinnerSelectionFromIdentifier(sBox86Version, box86Version)) {
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sBox86Version, DEFAULT_BOX86_VERSION);
|
||||
}
|
||||
|
||||
final Spinner sBox64Version = view.findViewById(R.id.SBox64Version);
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sBox64Version, preferences.getString("box64_version", DEFAULT_BOX64_VERSION));
|
||||
String box64Version = preferences.getString("box64_version", DEFAULT_BOX64_VERSION);
|
||||
if (!AppUtils.setSpinnerSelectionFromIdentifier(sBox64Version, box64Version)) {
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sBox64Version, DEFAULT_BOX64_VERSION);
|
||||
}
|
||||
|
||||
final Spinner sBox86Preset = view.findViewById(R.id.SBox86Preset);
|
||||
final Spinner sBox64Preset = view.findViewById(R.id.SBox64Preset);
|
||||
loadBox86_64PresetSpinners(view, sBox86Preset, sBox64Preset);
|
||||
|
||||
final Spinner sTurnipVersion = view.findViewById(R.id.STurnipVersion);
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sTurnipVersion, preferences.getString("turnip_version", getDefaultTurnipVersion(context)));
|
||||
String defaultTurnipVersion = getDefaultTurnipVersion(context);
|
||||
String turnipVersion = preferences.getString("turnip_version", defaultTurnipVersion);
|
||||
if (!AppUtils.setSpinnerSelectionFromIdentifier(sTurnipVersion, turnipVersion)) {
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sTurnipVersion, defaultTurnipVersion);
|
||||
}
|
||||
|
||||
final CheckBox cbUseDRI3 = view.findViewById(R.id.CBUseDRI3);
|
||||
cbUseDRI3.setChecked(preferences.getBoolean("use_dri3", true));
|
||||
@@ -351,4 +361,14 @@ public class SettingsFragment extends Fragment {
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static void resetBox86_64Version(AppCompatActivity activity) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("box86_version", DEFAULT_BOX86_VERSION);
|
||||
editor.putString("box64_version", DEFAULT_BOX64_VERSION);
|
||||
editor.remove("current_box86_version");
|
||||
editor.remove("current_box64_version");
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
@@ -27,6 +28,7 @@ import com.winlator.core.AppUtils;
|
||||
import com.winlator.core.CursorLocker;
|
||||
import com.winlator.core.EnvVars;
|
||||
import com.winlator.core.FileUtils;
|
||||
import com.winlator.core.OBBImageInstaller;
|
||||
import com.winlator.core.OnExtractFileListener;
|
||||
import com.winlator.core.PreloaderDialog;
|
||||
import com.winlator.core.ProcessHelper;
|
||||
@@ -89,7 +91,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
private boolean firstTimeBoot = false;
|
||||
private SharedPreferences preferences;
|
||||
private OnExtractFileListener onExtractFileListener;
|
||||
private final WinHandler winHandler = new WinHandler();
|
||||
private final WinHandler winHandler = new WinHandler(this);
|
||||
private float globalCursorSpeed = 1.0f;
|
||||
|
||||
@Override
|
||||
@@ -136,6 +138,9 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
graphicsDriver = shortcut.getExtra("graphicsDriver", container.getGraphicsDriver());
|
||||
audioDriver = shortcut.getExtra("audioDriver", container.getAudioDriver());
|
||||
screenSize = shortcut.getExtra("screenSize", container.getScreenSize());
|
||||
|
||||
String dinputMapperType = shortcut.getExtra("dinputMapperType");
|
||||
if (!dinputMapperType.isEmpty()) winHandler.setDInputMapperType(Byte.parseByte(dinputMapperType));
|
||||
}
|
||||
|
||||
if (!wineInfo.isWin64()) {
|
||||
@@ -199,6 +204,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
winHandler.stop();
|
||||
if (environment != null) environment.stopEnvironmentComponents();
|
||||
super.onDestroy();
|
||||
}
|
||||
@@ -243,6 +249,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
}
|
||||
|
||||
private void exit() {
|
||||
winHandler.stop();
|
||||
if (environment != null) environment.stopEnvironmentComponents();
|
||||
AppUtils.restartApplication(this);
|
||||
}
|
||||
@@ -257,9 +264,10 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
File pulseaudioDir = new File(getFilesDir(), "pulseaudio");
|
||||
TarZstdUtils.extract(this, "patches.tzst", rootDir, onExtractFileListener);
|
||||
TarZstdUtils.extract(this, "pulseaudio.tzst", pulseaudioDir);
|
||||
WineUtils.applyRegistryKeyTweaks(this);
|
||||
WineUtils.applyRegistryTweaks(this);
|
||||
container.putExtra("appVersion", appVersion);
|
||||
container.putExtra("imgVersion", imgVersion);
|
||||
SettingsFragment.resetBox86_64Version(this);
|
||||
containerDataChanged = true;
|
||||
}
|
||||
|
||||
@@ -288,9 +296,11 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
envVars.put("MESA_DEBUG", "silent");
|
||||
envVars.put("MESA_NO_ERROR", "1");
|
||||
envVars.put("WINEPREFIX", ImageFs.WINEPREFIX);
|
||||
envVars.put("WINEESYNC", "1");
|
||||
if (MainActivity.DEBUG_LEVEL <= 1) envVars.put("WINEDEBUG", "-all");
|
||||
|
||||
String rootPath = imageFs.getRootDir().getPath();
|
||||
FileUtils.clear(imageFs.getTmpDir());
|
||||
|
||||
GuestProgramLauncherComponent guestProgramLauncherComponent = new GuestProgramLauncherComponent();
|
||||
|
||||
@@ -399,10 +409,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
};
|
||||
loadProfileSpinner.run();
|
||||
|
||||
final GLRenderer renderer = xServerView.getRenderer();
|
||||
final CheckBox cbCursorVisible = dialog.findViewById(R.id.CBCursorVisible);
|
||||
cbCursorVisible.setChecked(renderer.isCursorVisible());
|
||||
|
||||
final CheckBox cbLockCursor = dialog.findViewById(R.id.CBLockCursor);
|
||||
cbLockCursor.setChecked(xServer.cursorLocker.getState() == CursorLocker.State.LOCKED);
|
||||
|
||||
@@ -423,7 +429,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
});
|
||||
|
||||
dialog.setOnConfirmCallback(() -> {
|
||||
renderer.setCursorVisible(cbCursorVisible.isChecked());
|
||||
xServer.cursorLocker.setState(cbLockCursor.isChecked() ? CursorLocker.State.LOCKED : CursorLocker.State.CONFINED);
|
||||
inputControlsView.setShowTouchscreenControls(cbShowTouchscreenControls.isChecked());
|
||||
int position = sProfile.getSelectedItemPosition();
|
||||
@@ -510,12 +515,21 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
||||
return !winHandler.onGenericMotionEvent(event) && super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
return (!inputControlsView.onKeyEvent(event) && xServer.keyboard.onKeyEvent(event)) ||
|
||||
return (!inputControlsView.onKeyEvent(event) && !winHandler.onKeyEvent(event) && xServer.keyboard.onKeyEvent(event)) ||
|
||||
(!ExternalController.isGameController(event.getDevice()) && super.dispatchKeyEvent(event));
|
||||
}
|
||||
|
||||
public InputControlsView getInputControlsView() {
|
||||
return inputControlsView;
|
||||
}
|
||||
|
||||
private void generateWineprefix() {
|
||||
Intent intent = getIntent();
|
||||
|
||||
@@ -731,7 +745,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
|
||||
|
||||
String args = "";
|
||||
if (shortcut != null) {
|
||||
if (shortcut.getExtra("singleCPU", "0").equals("1")) args += "/affinity "+ProcessHelper.getSingleCPUAffinityMask()+" ";
|
||||
String execArgs = shortcut.getExtra("execArgs");
|
||||
execArgs = !execArgs.isEmpty() ? " "+execArgs : "";
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Container {
|
||||
public static final String DEFAULT_GRAPHICS_DRIVER = "turnip-zink";
|
||||
public static final String DEFAULT_AUDIO_DRIVER = "alsa";
|
||||
public static final String DEFAULT_DXWRAPPER = "original-wined3d";
|
||||
public static final String DEFAULT_DXCOMPONENTS = "direct3d=0,directsound=0,directmusic=0,directshow=0,directplay=0";
|
||||
public static final String DEFAULT_DXCOMPONENTS = "direct3d=1,directsound=1,directmusic=0,directshow=0,directplay=0";
|
||||
public static final String DEFAULT_DRIVES = "D:"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
public static final byte MAX_DRIVE_LETTERS = 8;
|
||||
public final int id;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.winlator.contentdialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -24,6 +23,7 @@ import com.winlator.core.AppUtils;
|
||||
import com.winlator.core.ArrayUtils;
|
||||
import com.winlator.core.EnvVars;
|
||||
import com.winlator.core.StringUtils;
|
||||
import com.winlator.winhandler.WinHandler;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -75,9 +75,6 @@ public class ShortcutSettingsDialog extends ContentDialog {
|
||||
sAudioDriver.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_spinner_dropdown_item, audioDriverEntries));
|
||||
AppUtils.setSpinnerSelectionFromIdentifier(sAudioDriver, shortcut.getExtra("audioDriver"));
|
||||
|
||||
final CheckBox cbSingleCPU = findViewById(R.id.CBSingleCPU);
|
||||
cbSingleCPU.setChecked(shortcut.getExtra("singleCPU", "0").equals("1"));
|
||||
|
||||
final CheckBox cbForceFullscreen = findViewById(R.id.CBForceFullscreen);
|
||||
cbForceFullscreen.setChecked(shortcut.getExtra("forceFullscreen", "0").equals("1"));
|
||||
|
||||
@@ -87,6 +84,9 @@ public class ShortcutSettingsDialog extends ContentDialog {
|
||||
final Spinner sBox64Preset = findViewById(R.id.SBox64Preset);
|
||||
Box86_64PresetManager.loadSpinner("box64", sBox64Preset, shortcut.getExtra("box64Preset", shortcut.container.getBox64Preset()));
|
||||
|
||||
final Spinner sDInputMapperType = findViewById(R.id.SDInputMapperType);
|
||||
sDInputMapperType.setSelection(Byte.parseByte(shortcut.getExtra("dinputMapperType", String.valueOf(WinHandler.DINPUT_MAPPER_TYPE_XINPUT))));
|
||||
|
||||
createDXComponentsTab();
|
||||
createEnvVarsTab();
|
||||
|
||||
@@ -118,7 +118,6 @@ public class ShortcutSettingsDialog extends ContentDialog {
|
||||
shortcut.putExtra("dxwrapper", sDXWrapper.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sDXWrapper.getSelectedItem()) : null);
|
||||
shortcut.putExtra("audioDriver", sAudioDriver.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sAudioDriver.getSelectedItem()) : null);
|
||||
shortcut.putExtra("forceFullscreen", cbForceFullscreen.isChecked() ? "1" : null);
|
||||
shortcut.putExtra("singleCPU", cbSingleCPU.isChecked() ? "1" : null);
|
||||
shortcut.putExtra("dxcomponents", getDXComponents());
|
||||
shortcut.putExtra("envVars", getEnvVars());
|
||||
|
||||
@@ -127,6 +126,8 @@ public class ShortcutSettingsDialog extends ContentDialog {
|
||||
shortcut.putExtra("box86Preset", !box86Preset.equals(shortcut.container.getBox86Preset()) ? box86Preset : null);
|
||||
shortcut.putExtra("box64Preset", !box64Preset.equals(shortcut.container.getBox64Preset()) ? box64Preset : null);
|
||||
|
||||
int dinputMapperType = sDInputMapperType.getSelectedItemPosition();
|
||||
shortcut.putExtra("dinputMapperType", dinputMapperType != WinHandler.DINPUT_MAPPER_TYPE_XINPUT ? String.valueOf(dinputMapperType) : null);
|
||||
shortcut.saveData();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,12 @@ package com.winlator.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.winlator.MainActivity;
|
||||
import com.winlator.R;
|
||||
import com.winlator.SettingsFragment;
|
||||
import com.winlator.contentdialog.ContentDialog;
|
||||
import com.winlator.xenvironment.ImageFs;
|
||||
|
||||
@@ -39,11 +37,7 @@ public abstract class OBBImageInstaller {
|
||||
ImageFs imageFs = ImageFs.find(activity);
|
||||
final File rootDir = imageFs.getRootDir();
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.remove("current_box86_version");
|
||||
editor.remove("current_box64_version");
|
||||
editor.apply();
|
||||
SettingsFragment.resetBox86_64Version(activity);
|
||||
|
||||
if (foundVersion <= 0) foundVersion = imageFs.isValid() ? imageFs.getVersion() + 1 : 1;
|
||||
final int finalVersion = foundVersion;
|
||||
|
||||
@@ -131,12 +131,6 @@ public abstract class ProcessHelper {
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String getSingleCPUAffinityMask() {
|
||||
int numProcessors = (byte)Runtime.getRuntime().availableProcessors();
|
||||
int affinityMask = (int)Math.pow(2, numProcessors-1);
|
||||
return Integer.toHexString(affinityMask);
|
||||
}
|
||||
|
||||
public static String getAffinityMask(String cpuList) {
|
||||
String[] values = cpuList.split(",");
|
||||
int affinityMask = 0;
|
||||
|
||||
@@ -11,14 +11,45 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class TarXZUtils {
|
||||
public static boolean extract(Context context, String assetFile, File destination) {
|
||||
try {
|
||||
return extract(context.getAssets().open(assetFile), destination);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean extract(File source, File destination) {
|
||||
if (source == null || !source.isFile()) return false;
|
||||
try {
|
||||
return extract(new BufferedInputStream(new FileInputStream(source), StreamUtils.BUFFER_SIZE), destination);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean extract(Context context, Uri source, File destination) {
|
||||
if (source == null) return false;
|
||||
try (InputStream inStream = new XZCompressorInputStream(new BufferedInputStream(context.getContentResolver().openInputStream(source), StreamUtils.BUFFER_SIZE));
|
||||
try {
|
||||
return extract(context.getContentResolver().openInputStream(source), destination);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean extract(InputStream source, File destination) {
|
||||
if (source == null) return false;
|
||||
try (InputStream inStream = new XZCompressorInputStream(source);
|
||||
ArchiveInputStream tar = new TarArchiveInputStream(inStream)) {
|
||||
TarArchiveEntry entry;
|
||||
while ((entry = (TarArchiveEntry)tar.getNextEntry()) != null) {
|
||||
|
||||
@@ -83,17 +83,20 @@ public abstract class WineUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
File wineBin = new File(binDir, "wine");
|
||||
if (!wineBin.isFile()) {
|
||||
File wineBin32 = new File(binDir, "wine");
|
||||
File wineBin64 = new File(binDir, "wine64");
|
||||
|
||||
if (!wineBin32.isFile()) {
|
||||
callback.call(null);
|
||||
return;
|
||||
}
|
||||
|
||||
final String arch = (new File(binDir, "wine64")).isFile() ? "x86_64" : "x86";
|
||||
final String arch = wineBin64.isFile() ? "x86_64" : "x86";
|
||||
|
||||
ImageFs imageFs = ImageFs.find(context);
|
||||
File rootDir = ImageFs.find(context).getRootDir();
|
||||
String wineBinRelPath = FileUtils.toRelativePath(rootDir.getPath(), wineBin.getPath());
|
||||
String wineBinAbsPath = arch.equals("x86_64") ? wineBin64.getPath() : wineBin32.getPath();
|
||||
String wineBinRelPath = FileUtils.toRelativePath(rootDir.getPath(), wineBinAbsPath);
|
||||
final String winePath = wineDir.getPath();
|
||||
|
||||
try {
|
||||
@@ -140,14 +143,26 @@ public abstract class WineUtils {
|
||||
return wineInfos;
|
||||
}
|
||||
|
||||
public static void applyRegistryKeyTweaks(Context context) {
|
||||
public static void applyRegistryTweaks(Context context) {
|
||||
File rootDir = ImageFs.find(context).getRootDir();
|
||||
File systemRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/system.reg");
|
||||
File userRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/user.reg");
|
||||
|
||||
try (WineRegistryEditor registryEditor = new WineRegistryEditor(systemRegFile)) {
|
||||
registryEditor.setStringValue("Software\\Classes\\.reg", null, "REGfile");
|
||||
registryEditor.setStringValue("Software\\Classes\\.reg", "Content Type", "application/reg");
|
||||
registryEditor.setStringValue("Software\\Classes\\REGfile\\Shell\\Open\\command", null, "C:\\windows\\regedit.exe /C \"%1\"");
|
||||
|
||||
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "DisableHidraw", 1);
|
||||
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "DisableInput", 1);
|
||||
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "Enable SDL", 0);
|
||||
}
|
||||
|
||||
final String[] dllOverrides = {"d3d8", "d3d9", "d3d10", "d3d10_1", "d3d10core", "d3d11", "d3d12", "d3d12core", "ddraw", "dxgi", "wined3d", "dinput", "dinput8", "xinput1_1", "xinput1_2", "xinput1_3", "xinput1_4", "xinput9_1_0", "xinputuap"};
|
||||
final String dllOverridesKey = "Software\\Wine\\DllOverrides";
|
||||
|
||||
try (WineRegistryEditor registryEditor = new WineRegistryEditor(userRegFile)) {
|
||||
for (String name : dllOverrides) registryEditor.setStringValue(dllOverridesKey, name, "native,builtin");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.winlator.xserver.XKeycode;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public enum Binding {
|
||||
NONE, MOUSE_LEFT_BUTTON, MOUSE_MIDDLE_BUTTON, MOUSE_RIGHT_BUTTON, MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_LEFT, KEY_ENTER, KEY_ESC, KEY_BKSP, KEY_DEL, KEY_TAB, KEY_SPACE, KEY_CTRL_L, KEY_CTRL_R, KEY_SHIFT_L, KEY_SHIFT_R, KEY_ALT_L, KEY_ALT_R, KEY_HOME, KEY_PRTSCN, KEY_CAPS_LOCK, KEY_NUM_LOCK, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_SLASH, KEY_SEMICOLON, KEY_COMMA, KEY_PERIOD, KEY_APOSTROPHE, KEY_KP_ADD, KEY_MINUS, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_KP_0, KEY_KP_1, KEY_KP_2, KEY_KP_3, KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_7, KEY_KP_8, KEY_KP_9;
|
||||
NONE, MOUSE_LEFT_BUTTON, MOUSE_MIDDLE_BUTTON, MOUSE_RIGHT_BUTTON, MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_LEFT, KEY_ENTER, KEY_ESC, KEY_BKSP, KEY_DEL, KEY_TAB, KEY_SPACE, KEY_CTRL_L, KEY_CTRL_R, KEY_SHIFT_L, KEY_SHIFT_R, KEY_ALT_L, KEY_ALT_R, KEY_HOME, KEY_PRTSCN, KEY_CAPS_LOCK, KEY_NUM_LOCK, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_SLASH, KEY_SEMICOLON, KEY_COMMA, KEY_PERIOD, KEY_APOSTROPHE, KEY_KP_ADD, KEY_MINUS, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_KP_0, KEY_KP_1, KEY_KP_2, KEY_KP_3, KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_7, KEY_KP_8, KEY_KP_9, GAMEPAD_BUTTON_A, GAMEPAD_BUTTON_B, GAMEPAD_BUTTON_X, GAMEPAD_BUTTON_Y, GAMEPAD_BUTTON_L1, GAMEPAD_BUTTON_R1, GAMEPAD_BUTTON_SELECT, GAMEPAD_BUTTON_START, GAMEPAD_BUTTON_L3, GAMEPAD_BUTTON_R3, GAMEPAD_BUTTON_L2, GAMEPAD_BUTTON_R2, GAMEPAD_LEFT_THUMB_UP, GAMEPAD_LEFT_THUMB_RIGHT, GAMEPAD_LEFT_THUMB_DOWN, GAMEPAD_LEFT_THUMB_LEFT, GAMEPAD_RIGHT_THUMB_UP, GAMEPAD_RIGHT_THUMB_RIGHT, GAMEPAD_RIGHT_THUMB_DOWN, GAMEPAD_RIGHT_THUMB_LEFT, GAMEPAD_DPAD_UP, GAMEPAD_DPAD_RIGHT, GAMEPAD_DPAD_DOWN, GAMEPAD_DPAD_LEFT;
|
||||
public final XKeycode keycode;
|
||||
|
||||
Binding() {
|
||||
@@ -59,7 +59,7 @@ public enum Binding {
|
||||
case KEY_KP_ADD:
|
||||
return "+";
|
||||
default:
|
||||
return super.toString().replaceAll("^(MOUSE_)|(KEY_)", "").replace("KP_", "NUMPAD_").replace("_", " ");
|
||||
return super.toString().replaceAll("^(MOUSE_)|(KEY_)|(GAMEPAD_)", "").replace("KP_", "NUMPAD_").replace("_", " ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,10 @@ public enum Binding {
|
||||
return name().startsWith("KEY_") || this == NONE;
|
||||
}
|
||||
|
||||
public boolean isGamepad() {
|
||||
return name().startsWith("GAMEPAD_");
|
||||
}
|
||||
|
||||
public static String[] mouseBindingLabels() {
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
for (Binding binding : values()) if (binding.isMouse()) names.add(binding.toString());
|
||||
@@ -113,6 +117,12 @@ public enum Binding {
|
||||
return labels.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String[] gamepadBindingLabels() {
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
for (Binding binding : values()) if (binding.isGamepad()) names.add(binding.toString());
|
||||
return names.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static Binding[] mouseBindingValues() {
|
||||
ArrayList<Binding> labels = new ArrayList<>();
|
||||
for (Binding binding : values()) if (binding.isMouse()) labels.add(binding);
|
||||
@@ -124,4 +134,10 @@ public enum Binding {
|
||||
for (Binding binding : values()) if (binding.isKeyboard()) values.add(binding);
|
||||
return values.toArray(new Binding[0]);
|
||||
}
|
||||
|
||||
public static Binding[] gamepadBindingValues() {
|
||||
ArrayList<Binding> labels = new ArrayList<>();
|
||||
for (Binding binding : values()) if (binding.isGamepad()) labels.add(binding);
|
||||
return labels.toArray(new Binding[0]);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import com.winlator.math.Mathf;
|
||||
@@ -17,7 +16,12 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ControlElement {
|
||||
public static final float STICK_DEAD_ZONE = 0.15f;
|
||||
public static final float DPAD_DEAD_ZONE = 0.3f;
|
||||
public static final float STICK_SENSITIVITY = 3.5f;
|
||||
public enum Type {
|
||||
BUTTON, D_PAD, RANGE_BUTTON, STICK;
|
||||
|
||||
@@ -56,7 +60,7 @@ public class ControlElement {
|
||||
private final InputControlsView inputControlsView;
|
||||
private Type type = Type.BUTTON;
|
||||
private Shape shape = Shape.CIRCLE;
|
||||
private final Binding[] bindings = {Binding.NONE, Binding.NONE, Binding.NONE, Binding.NONE};
|
||||
private Binding[] bindings = {Binding.NONE, Binding.NONE, Binding.NONE, Binding.NONE};
|
||||
private float scale = 1.0f;
|
||||
private short x;
|
||||
private short y;
|
||||
@@ -64,7 +68,7 @@ public class ControlElement {
|
||||
private boolean toggleSwitch = false;
|
||||
private int currentPointerId = -1;
|
||||
private final Rect boundingBox = new Rect();
|
||||
private final boolean[] states = new boolean[4];
|
||||
private boolean[] states = new boolean[4];
|
||||
private boolean boundingBoxNeedsUpdate = true;
|
||||
private String text = "";
|
||||
private byte iconId;
|
||||
@@ -79,7 +83,7 @@ public class ControlElement {
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
bindings[0] = bindings[1] = bindings[2] = bindings[3] = Binding.NONE;
|
||||
Arrays.fill(bindings, Binding.NONE);
|
||||
if (type == Type.D_PAD || type == Type.STICK) {
|
||||
bindings[0] = Binding.KEY_W;
|
||||
bindings[1] = Binding.KEY_D;
|
||||
@@ -102,6 +106,17 @@ public class ControlElement {
|
||||
reset();
|
||||
}
|
||||
|
||||
public int getBindingCount() {
|
||||
return bindings.length;
|
||||
}
|
||||
|
||||
public void setBindingCount(int bindingCount) {
|
||||
bindings = new Binding[bindingCount];
|
||||
Arrays.fill(bindings, Binding.NONE);
|
||||
states = new boolean[bindingCount];
|
||||
boundingBoxNeedsUpdate = true;
|
||||
}
|
||||
|
||||
public Shape getShape() {
|
||||
return shape;
|
||||
}
|
||||
@@ -136,12 +151,19 @@ public class ControlElement {
|
||||
this.toggleSwitch = toggleSwitch;
|
||||
}
|
||||
|
||||
public Binding getBindingAt(@IntRange(from = 0, to = 4) int index) {
|
||||
return bindings[index];
|
||||
public Binding getBindingAt(int index) {
|
||||
return index < bindings.length ? bindings[index] : Binding.NONE;
|
||||
}
|
||||
|
||||
public void setBindingAt(@IntRange(from = 0, to = 4) int index, Binding binding) {
|
||||
this.bindings[index] = binding;
|
||||
public void setBindingAt(int index, Binding binding) {
|
||||
if (index >= bindings.length) {
|
||||
int oldLength = bindings.length;
|
||||
bindings = Arrays.copyOf(bindings, index+1);
|
||||
Arrays.fill(bindings, oldLength-1, bindings.length, Binding.NONE);
|
||||
states = new boolean[bindings.length];
|
||||
boundingBoxNeedsUpdate = true;
|
||||
}
|
||||
bindings[index] = binding;
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
@@ -223,24 +245,27 @@ public class ControlElement {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case D_PAD:
|
||||
case D_PAD: {
|
||||
halfWidth = snappingSize * 7;
|
||||
halfHeight = snappingSize * 7;
|
||||
break;
|
||||
case STICK:
|
||||
}
|
||||
case STICK: {
|
||||
halfWidth = snappingSize * 6;
|
||||
halfHeight = snappingSize * 6;
|
||||
break;
|
||||
case RANGE_BUTTON:
|
||||
if (orientation == 0) {
|
||||
halfWidth = snappingSize * 12;
|
||||
halfHeight = (int)(snappingSize * 1.5f);
|
||||
}
|
||||
else {
|
||||
halfWidth = (int)(snappingSize * 1.5f);
|
||||
halfHeight = snappingSize * 12;
|
||||
}
|
||||
case RANGE_BUTTON: {
|
||||
halfWidth = snappingSize * ((bindings.length * 4 + 8) / 2);
|
||||
halfHeight = snappingSize * 2;
|
||||
|
||||
if (orientation == 1) {
|
||||
int tmp = halfWidth;
|
||||
halfWidth = halfHeight;
|
||||
halfHeight = tmp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
halfWidth *= scale;
|
||||
@@ -256,7 +281,7 @@ public class ControlElement {
|
||||
}
|
||||
else {
|
||||
Binding binding = getBindingAt(0);
|
||||
String text = binding.toString().replace("NUMPAD ", "NP");
|
||||
String text = binding.toString().replace("NUMPAD ", "NP").replace("BUTTON ", "");
|
||||
if (text.length() > 7) {
|
||||
String[] parts = text.split(" ");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -326,7 +351,8 @@ public class ControlElement {
|
||||
|
||||
if (iconId > 0) {
|
||||
drawIcon(canvas, cx, cy, boundingBox.width(), boundingBox.height(), iconId);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String text = getDisplayText();
|
||||
paint.setTextSize(Math.min(getTextSizeForWidth(paint, text, boundingBox.width() - strokeWidth * 2), snappingSize * 2 * scale));
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
@@ -379,9 +405,9 @@ public class ControlElement {
|
||||
case RANGE_BUTTON: {
|
||||
Range range = getRange();
|
||||
int oldColor = paint.getColor();
|
||||
float radius = snappingSize * 0.5f * scale;
|
||||
float minSize = Math.min(boundingBox.width(), boundingBox.height());
|
||||
float radius = minSize * 0.5f;
|
||||
float elementSize = (float)Math.max(boundingBox.width(), boundingBox.height()) / (bindings.length + 2);
|
||||
float iconSize = Math.min(boundingBox.width(), boundingBox.height()) * 1.35f;
|
||||
float minTextSize = snappingSize * 2 * scale;
|
||||
|
||||
if (orientation == 0) {
|
||||
@@ -391,7 +417,7 @@ public class ControlElement {
|
||||
|
||||
float startX = boundingBox.left;
|
||||
canvas.drawRoundRect(startX, boundingBox.top, boundingBox.right, boundingBox.bottom, radius, radius, paint);
|
||||
drawIcon(canvas, startX + elementSize * 0.5f, cy, iconSize, iconSize, 8);
|
||||
drawIcon(canvas, startX + elementSize * 0.5f, cy, minSize, minSize, 8);
|
||||
startX += elementSize;
|
||||
|
||||
for (byte i = 0; i < bindings.length; i++) {
|
||||
@@ -412,7 +438,7 @@ public class ControlElement {
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setColor(oldColor);
|
||||
canvas.drawLine(startX, lineTop, startX, lineBottom, paint);
|
||||
drawIcon(canvas, startX + elementSize * 0.5f, cy, iconSize, iconSize, 10);
|
||||
drawIcon(canvas, startX + elementSize * 0.5f, cy, minSize, minSize, 10);
|
||||
}
|
||||
else {
|
||||
float lineLeft = boundingBox.left + strokeWidth * 0.5f;
|
||||
@@ -421,7 +447,7 @@ public class ControlElement {
|
||||
|
||||
float startY = boundingBox.top;
|
||||
canvas.drawRoundRect(boundingBox.left, startY, boundingBox.right, boundingBox.bottom, radius, radius, paint);
|
||||
drawIcon(canvas, cx, startY + elementSize * 0.5f, iconSize, iconSize, 9);
|
||||
drawIcon(canvas, cx, startY + elementSize * 0.5f, minSize, minSize, 9);
|
||||
startY += elementSize;
|
||||
|
||||
for (byte i = 0; i < bindings.length; i++) {
|
||||
@@ -442,7 +468,7 @@ public class ControlElement {
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setColor(oldColor);
|
||||
canvas.drawLine(lineLeft, startY, lineRight, startY, paint);
|
||||
drawIcon(canvas, cx, startY + elementSize * 0.5f, iconSize, iconSize, 11);
|
||||
drawIcon(canvas, cx, startY + elementSize * 0.5f, minSize, minSize, 11);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -536,7 +562,7 @@ public class ControlElement {
|
||||
break;
|
||||
}
|
||||
|
||||
bindings[0] = bindings[1] = bindings[2] = bindings[3] = Binding.NONE;
|
||||
Arrays.fill(bindings, Binding.NONE);
|
||||
bindings[currentElementIndex-1] = binding;
|
||||
states[currentElementIndex-1] = true;
|
||||
inputControlsView.handleInputEvent(binding, true);
|
||||
@@ -551,62 +577,51 @@ public class ControlElement {
|
||||
public boolean handleTouchMove(int pointerId, float x, float y) {
|
||||
if (pointerId == currentPointerId && (type == ControlElement.Type.D_PAD || type == Type.STICK)) {
|
||||
Rect boundingBox = getBoundingBox();
|
||||
x -= boundingBox.left;
|
||||
y -= boundingBox.top;
|
||||
|
||||
float radius = boundingBox.width() * 0.5f;
|
||||
float distance = Mathf.lengthSq(radius - x, radius - y);
|
||||
float localX = x - boundingBox.left;
|
||||
float localY = y - boundingBox.top;
|
||||
float offsetX = localX - radius;
|
||||
float offsetY = localY - radius;
|
||||
|
||||
float distance = Mathf.lengthSq(radius - localX, radius - localY);
|
||||
if (distance > radius * radius) {
|
||||
float angle = (float)Math.atan2(y - radius, x - radius);
|
||||
x = (float)((Math.cos(angle) * radius) + radius);
|
||||
y = (float)((Math.sin(angle) * radius) + radius);
|
||||
float angle = (float)Math.atan2(offsetY, offsetX);
|
||||
offsetX = (float)(Math.cos(angle) * radius);
|
||||
offsetY = (float)(Math.sin(angle) * radius);
|
||||
}
|
||||
|
||||
float invWidth = 1.0f / boundingBox.width();
|
||||
float deltaX = Mathf.clamp(-1 + 2 * ((radius - x) + radius) * invWidth, -1, 1) * -1;
|
||||
float deltaY = Mathf.clamp(-1 + 2 * ((radius - y) + radius) * invWidth, -1, 1) * -1;
|
||||
float deltaX = Mathf.clamp(offsetX / radius, -1, 1);
|
||||
float deltaY = Mathf.clamp(offsetY / radius, -1, 1);
|
||||
|
||||
if (type == Type.STICK) {
|
||||
if (thumbstickPosition == null) thumbstickPosition = new PointF();
|
||||
thumbstickPosition.x = boundingBox.left + deltaX * radius + radius;
|
||||
thumbstickPosition.y = boundingBox.top + deltaY * radius + radius;
|
||||
|
||||
boolean[] states = {
|
||||
deltaY <= -InputControlsView.JOYSTICK_DEAD_ZONE,
|
||||
deltaX >= InputControlsView.JOYSTICK_DEAD_ZONE,
|
||||
deltaY >= InputControlsView.JOYSTICK_DEAD_ZONE,
|
||||
deltaX <= -InputControlsView.JOYSTICK_DEAD_ZONE
|
||||
};
|
||||
|
||||
for (byte i = 0; i < 4; i++) {
|
||||
float value = i == 1 || i == 3 ? deltaX : deltaY;
|
||||
if (states[i]) {
|
||||
inputControlsView.handleInputEvent(getBindingAt(i), true, value);
|
||||
this.states[i] = true;
|
||||
}
|
||||
else {
|
||||
inputControlsView.handleInputEvent(getBindingAt(i), false, value);
|
||||
this.states[i] = false;
|
||||
}
|
||||
value = Mathf.clamp(Math.max(0, Math.abs(value) - STICK_DEAD_ZONE) * Mathf.sign(value) * STICK_SENSITIVITY, -1, 1);
|
||||
inputControlsView.handleInputEvent(getBindingAt(i), true, value);
|
||||
this.states[i] = true;
|
||||
}
|
||||
|
||||
inputControlsView.invalidate();
|
||||
}
|
||||
else {
|
||||
boolean[] states = {
|
||||
deltaY <= -InputControlsView.DPAD_DEAD_ZONE,
|
||||
deltaX >= InputControlsView.DPAD_DEAD_ZONE,
|
||||
deltaY >= InputControlsView.DPAD_DEAD_ZONE,
|
||||
deltaX <= -InputControlsView.DPAD_DEAD_ZONE
|
||||
};
|
||||
final boolean[] states = {deltaY <= -DPAD_DEAD_ZONE, deltaX >= DPAD_DEAD_ZONE, deltaY >= DPAD_DEAD_ZONE, deltaX <= -DPAD_DEAD_ZONE};
|
||||
|
||||
for (byte i = 0; i < 4; i++) {
|
||||
if (states[i] && !this.states[i]) {
|
||||
inputControlsView.handleInputEvent(getBindingAt(i), true);
|
||||
Binding binding = getBindingAt(i);
|
||||
if (binding.isGamepad()) {
|
||||
inputControlsView.handleInputEvent(binding, states[i]);
|
||||
this.states[i] = states[i];
|
||||
}
|
||||
else if (states[i] && !this.states[i]) {
|
||||
inputControlsView.handleInputEvent(binding, true);
|
||||
this.states[i] = true;
|
||||
}
|
||||
else if (!states[i] && this.states[i]) {
|
||||
inputControlsView.handleInputEvent(getBindingAt(i), false);
|
||||
inputControlsView.handleInputEvent(binding, false);
|
||||
this.states[i] = false;
|
||||
}
|
||||
}
|
||||
@@ -627,7 +642,7 @@ public class ControlElement {
|
||||
}
|
||||
}
|
||||
else if (type == Type.RANGE_BUTTON || type == Type.D_PAD || type == Type.STICK) {
|
||||
for (byte i = 0; i < 4; i++) {
|
||||
for (byte i = 0; i < states.length; i++) {
|
||||
if (states[i]) inputControlsView.handleInputEvent(getBindingAt(i), false);
|
||||
states[i] = false;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
|
||||
private final List<ControlElement> immutableElements = Collections.unmodifiableList(elements);
|
||||
private boolean elementsLoaded = false;
|
||||
private boolean controllersLoaded = false;
|
||||
private boolean virtualGamepad = false;
|
||||
private final Context context;
|
||||
private GamepadState gamepadState;
|
||||
|
||||
public ControlsProfile(Context context, int id) {
|
||||
this.context = context;
|
||||
@@ -49,9 +51,19 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
|
||||
this.cursorSpeed = cursorSpeed;
|
||||
}
|
||||
|
||||
public boolean isVirtualGamepad() {
|
||||
return virtualGamepad;
|
||||
}
|
||||
|
||||
public GamepadState getGamepadState() {
|
||||
if (gamepadState == null) gamepadState = new GamepadState();
|
||||
return gamepadState;
|
||||
}
|
||||
|
||||
public ExternalController addController(String id) {
|
||||
ExternalController controller = getController(id);
|
||||
if (controller == null) controllers.add(controller = ExternalController.getController(id));
|
||||
controllersLoaded = true;
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -95,6 +107,7 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
|
||||
data.put("id", id);
|
||||
data.put("name", name);
|
||||
data.put("cursorSpeed", Float.valueOf(cursorSpeed));
|
||||
if (virtualGamepad) data.put("virtualGamepad", true);
|
||||
|
||||
JSONArray elementsJSONArray = new JSONArray();
|
||||
if (!elementsLoaded && file.isFile()) {
|
||||
@@ -183,8 +196,9 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
|
||||
public void loadElements(InputControlsView inputControlsView) {
|
||||
elements.clear();
|
||||
elementsLoaded = false;
|
||||
virtualGamepad = false;
|
||||
|
||||
File file = getProfileFile(inputControlsView.getContext(), id);
|
||||
File file = getProfileFile(context, id);
|
||||
if (!file.isFile()) return;
|
||||
|
||||
try {
|
||||
@@ -204,10 +218,15 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
|
||||
if (elementJSONObject.has("range")) element.setRange(ControlElement.Range.valueOf(elementJSONObject.getString("range")));
|
||||
if (elementJSONObject.has("orientation")) element.setOrientation((byte)elementJSONObject.getInt("orientation"));
|
||||
|
||||
boolean hasGamepadBinding = true;
|
||||
JSONArray bindingsJSONArray = elementJSONObject.getJSONArray("bindings");
|
||||
for (int j = 0; j < bindingsJSONArray.length(); j++) {
|
||||
Binding binding = Binding.fromString(bindingsJSONArray.getString(j));
|
||||
element.setBindingAt(j, Binding.fromString(bindingsJSONArray.getString(j)));
|
||||
if (!binding.isGamepad()) hasGamepadBinding = false;
|
||||
}
|
||||
|
||||
if (!virtualGamepad && hasGamepadBinding) virtualGamepad = true;
|
||||
elements.add(element);
|
||||
}
|
||||
elementsLoaded = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.winlator.inputcontrols;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -12,10 +13,23 @@ import org.json.JSONObject;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ExternalController {
|
||||
public static final byte IDX_BUTTON_A = 0;
|
||||
public static final byte IDX_BUTTON_B = 1;
|
||||
public static final byte IDX_BUTTON_X = 2;
|
||||
public static final byte IDX_BUTTON_Y = 3;
|
||||
public static final byte IDX_BUTTON_L1 = 4;
|
||||
public static final byte IDX_BUTTON_R1 = 5;
|
||||
public static final byte IDX_BUTTON_SELECT = 6;
|
||||
public static final byte IDX_BUTTON_START = 7;
|
||||
public static final byte IDX_BUTTON_L3 = 8;
|
||||
public static final byte IDX_BUTTON_R3 = 9;
|
||||
public static final byte IDX_BUTTON_L2 = 10;
|
||||
public static final byte IDX_BUTTON_R2 = 11;
|
||||
private String name;
|
||||
private String id;
|
||||
private int deviceId = -1;
|
||||
private final ArrayList<ExternalControllerBinding> controllerBindings = new ArrayList<>();
|
||||
public final GamepadState state = new GamepadState();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -104,11 +118,70 @@ public class ExternalController {
|
||||
return obj instanceof ExternalController ? ((ExternalController)obj).id.equals(this.id) : super.equals(obj);
|
||||
}
|
||||
|
||||
private void processJoystickInput(MotionEvent event, int historyPos) {
|
||||
state.thumbLX = getCenteredAxis(event, MotionEvent.AXIS_X, historyPos);
|
||||
state.thumbLY = getCenteredAxis(event, MotionEvent.AXIS_Y, historyPos);
|
||||
state.thumbRX = getCenteredAxis(event, MotionEvent.AXIS_Z, historyPos);
|
||||
state.thumbRY = getCenteredAxis(event, MotionEvent.AXIS_RZ, historyPos);
|
||||
|
||||
if (historyPos == -1) {
|
||||
float axisX = getCenteredAxis(event, MotionEvent.AXIS_HAT_X, historyPos);
|
||||
float axisY = getCenteredAxis(event, MotionEvent.AXIS_HAT_Y, historyPos);
|
||||
|
||||
state.dpad[0] = axisY == -1.0f && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
|
||||
state.dpad[1] = axisX == 1.0f && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
|
||||
state.dpad[2] = axisY == 1.0f && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
|
||||
state.dpad[3] = axisX == -1.0f && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
|
||||
}
|
||||
}
|
||||
|
||||
private void processTriggerButton(MotionEvent event) {
|
||||
state.setPressed(IDX_BUTTON_L2, event.getAxisValue(MotionEvent.AXIS_LTRIGGER) == 1.0f || event.getAxisValue(MotionEvent.AXIS_BRAKE) == 1.0f);
|
||||
state.setPressed(IDX_BUTTON_R2, event.getAxisValue(MotionEvent.AXIS_RTRIGGER) == 1.0f || event.getAxisValue(MotionEvent.AXIS_GAS) == 1.0f);
|
||||
}
|
||||
|
||||
public boolean updateStateFromMotionEvent(MotionEvent event) {
|
||||
if (isJoystickDevice(event)) {
|
||||
processTriggerButton(event);
|
||||
int historySize = event.getHistorySize();
|
||||
for (int i = 0; i < historySize; i++) processJoystickInput(event, i);
|
||||
processJoystickInput(event, -1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean updateStateFromKeyEvent(KeyEvent event) {
|
||||
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
int keyCode = event.getKeyCode();
|
||||
int buttonIdx = getButtonIdxByKeyCode(keyCode);
|
||||
if (buttonIdx != -1) {
|
||||
state.setPressed(buttonIdx, pressed);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
state.dpad[0] = pressed && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
state.dpad[1] = pressed && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
state.dpad[2] = pressed && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
state.dpad[3] = pressed && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ArrayList<ExternalController> getControllers() {
|
||||
ArrayList<ExternalController> controllers = new ArrayList<>();
|
||||
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device != null && !device.isVirtual() && isGameController(device)) {
|
||||
if (isGameController(device)) {
|
||||
ExternalController controller = new ExternalController();
|
||||
controller.setId(device.getDescriptor());
|
||||
controller.setName(device.getName());
|
||||
@@ -123,19 +196,42 @@ public class ExternalController {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ExternalController getController(int deviceId) {
|
||||
for (int deviceId2 : InputDevice.getDeviceIds()) {
|
||||
if (deviceId2 == deviceId || deviceId == 0) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId2);
|
||||
if (isGameController(device)) {
|
||||
ExternalController controller = new ExternalController();
|
||||
controller.setId(device.getDescriptor());
|
||||
controller.setName(device.getName());
|
||||
controller.deviceId = deviceId2;
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isGameController(InputDevice device) {
|
||||
if (device == null) return false;
|
||||
int sources = device.getSources();
|
||||
return (sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD ||
|
||||
(sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
|
||||
return !device.isVirtual() && ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD ||
|
||||
(sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
|
||||
}
|
||||
|
||||
public static float getCenteredAxis(MotionEvent event, int axis, int historyPos) {
|
||||
InputDevice device = event.getDevice();
|
||||
InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
|
||||
if (range != null) {
|
||||
float flat = range.getFlat();
|
||||
float value = historyPos < 0 ? event.getAxisValue(axis) : event.getHistoricalAxisValue(axis, historyPos);
|
||||
if (Math.abs(value) > flat) return value;
|
||||
if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) {
|
||||
float value = event.getAxisValue(axis);
|
||||
if (Math.abs(value) == 1.0f) return value;
|
||||
}
|
||||
else {
|
||||
InputDevice device = event.getDevice();
|
||||
InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
|
||||
if (range != null) {
|
||||
float flat = range.getFlat();
|
||||
float value = historyPos < 0 ? event.getAxisValue(axis) : event.getHistoricalAxisValue(axis, historyPos);
|
||||
if (Math.abs(value) > flat) return value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -144,7 +240,65 @@ public class ExternalController {
|
||||
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE;
|
||||
}
|
||||
|
||||
public static boolean isDPadDevice(MotionEvent event) {
|
||||
return (event.getSource() & InputDevice.SOURCE_DPAD) != InputDevice.SOURCE_DPAD && event.getAction() == MotionEvent.ACTION_MOVE;
|
||||
public static int getButtonIdxByKeyCode(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return IDX_BUTTON_A;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return IDX_BUTTON_B;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return IDX_BUTTON_X;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return IDX_BUTTON_Y;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return IDX_BUTTON_L1;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
return IDX_BUTTON_R1;
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return IDX_BUTTON_SELECT;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return IDX_BUTTON_START;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
return IDX_BUTTON_L3;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
return IDX_BUTTON_R3;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return IDX_BUTTON_L2;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return IDX_BUTTON_R2;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getButtonIdxByName(String name) {
|
||||
switch (name) {
|
||||
case "A":
|
||||
return IDX_BUTTON_A;
|
||||
case "B":
|
||||
return IDX_BUTTON_B;
|
||||
case "X":
|
||||
return IDX_BUTTON_X;
|
||||
case "Y":
|
||||
return IDX_BUTTON_Y;
|
||||
case "L1":
|
||||
return IDX_BUTTON_L1;
|
||||
case "R1":
|
||||
return IDX_BUTTON_R1;
|
||||
case "SELECT":
|
||||
return IDX_BUTTON_SELECT;
|
||||
case "START":
|
||||
return IDX_BUTTON_START;
|
||||
case "L3":
|
||||
return IDX_BUTTON_L3;
|
||||
case "R3":
|
||||
return IDX_BUTTON_R3;
|
||||
case "L2":
|
||||
return IDX_BUTTON_L2;
|
||||
case "R2":
|
||||
return IDX_BUTTON_R2;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@ public class ExternalControllerBinding {
|
||||
public static final byte AXIS_Z_POSITIVE = -6;
|
||||
public static final byte AXIS_RZ_NEGATIVE = -7;
|
||||
public static final byte AXIS_RZ_POSITIVE = -8;
|
||||
public static final byte AXIS_LTRIGGER = -9;
|
||||
public static final byte AXIS_RTRIGGER = -10;
|
||||
private short keyCode;
|
||||
private Binding binding = Binding.NONE;
|
||||
|
||||
@@ -70,10 +68,6 @@ public class ExternalControllerBinding {
|
||||
return "AXIS RZ-";
|
||||
case AXIS_RZ_POSITIVE:
|
||||
return "AXIS RZ+";
|
||||
case AXIS_LTRIGGER:
|
||||
return "L TRIGGER";
|
||||
case AXIS_RTRIGGER:
|
||||
return "R TRIGGER";
|
||||
default:
|
||||
return KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", "").replace("_", " ");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.winlator.inputcontrols;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class GamepadState {
|
||||
public float thumbLX = 0;
|
||||
public float thumbLY = 0;
|
||||
public float thumbRX = 0;
|
||||
public float thumbRY = 0;
|
||||
public final boolean[] dpad = new boolean[4];
|
||||
public short buttons = 0;
|
||||
|
||||
public byte getPovHat() {
|
||||
byte povHat = -1;
|
||||
if (dpad[0] && dpad[1]) povHat = 1;
|
||||
else if (dpad[1] && dpad[2]) povHat = 3;
|
||||
else if (dpad[2] && dpad[3]) povHat = 5;
|
||||
else if (dpad[3] && dpad[0]) povHat = 7;
|
||||
else if (dpad[0]) povHat = 0;
|
||||
else if (dpad[1]) povHat = 2;
|
||||
else if (dpad[2]) povHat = 4;
|
||||
else if (dpad[3]) povHat = 6;
|
||||
return povHat;
|
||||
}
|
||||
|
||||
public void writeTo(ByteBuffer buffer) {
|
||||
buffer.putShort(buttons);
|
||||
buffer.put(getPovHat());
|
||||
buffer.putShort((short)(thumbLX * Short.MAX_VALUE));
|
||||
buffer.putShort((short)(thumbLY * Short.MAX_VALUE));
|
||||
buffer.putShort((short)(thumbRX * Short.MAX_VALUE));
|
||||
buffer.putShort((short)(thumbRY * Short.MAX_VALUE));
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(11).order(ByteOrder.LITTLE_ENDIAN);
|
||||
writeTo(buffer);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
public void setPressed(int buttonIdx, boolean pressed) {
|
||||
int flag = 1<<buttonIdx;
|
||||
if (pressed) {
|
||||
buttons |= flag;
|
||||
}
|
||||
else buttons &= ~flag;
|
||||
}
|
||||
|
||||
public boolean isPressed(int buttonIdx) {
|
||||
return (buttons & (1<<buttonIdx)) != 0;
|
||||
}
|
||||
|
||||
public byte getDPadX() {
|
||||
return (byte)(dpad[1] ? 1 : (dpad[3] ? -1 : 0));
|
||||
}
|
||||
|
||||
public byte getDPadY() {
|
||||
return (byte)(dpad[0] ? -1 : (dpad[2] ? 1 : 0));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.winlator.inputcontrols.ControlElement;
|
||||
import com.winlator.inputcontrols.ControlsProfile;
|
||||
import com.winlator.inputcontrols.ExternalController;
|
||||
import com.winlator.inputcontrols.ExternalControllerBinding;
|
||||
import com.winlator.inputcontrols.GamepadState;
|
||||
import com.winlator.math.Mathf;
|
||||
import com.winlator.xserver.Pointer;
|
||||
import com.winlator.xserver.XServer;
|
||||
@@ -34,8 +35,6 @@ import java.util.TimerTask;
|
||||
|
||||
public class InputControlsView extends View {
|
||||
public static final float DEFAULT_OVERLAY_OPACITY = 0.4f;
|
||||
public static final float JOYSTICK_DEAD_ZONE = 0.15f;
|
||||
public static final float DPAD_DEAD_ZONE = 0.3f;
|
||||
private boolean editMode = false;
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path path = new Path();
|
||||
@@ -241,6 +240,10 @@ public class InputControlsView extends View {
|
||||
this.touchpadView = touchpadView;
|
||||
}
|
||||
|
||||
public XServer getXServer() {
|
||||
return xServer;
|
||||
}
|
||||
|
||||
public void setXServer(XServer xServer) {
|
||||
this.xServer = xServer;
|
||||
createMouseMoveTimer();
|
||||
@@ -267,58 +270,41 @@ public class InputControlsView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
private void processJoystickInput(MotionEvent event, ExternalController controller, int historyPos) {
|
||||
private void processJoystickInput(ExternalController controller) {
|
||||
ExternalControllerBinding controllerBinding;
|
||||
final int[] axes = {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
|
||||
for (int axis : axes) {
|
||||
float value = ExternalController.getCenteredAxis(event, axis, historyPos);
|
||||
if (Math.abs(value) > JOYSTICK_DEAD_ZONE) {
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, Mathf.sign(value)));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), true, value);
|
||||
final float[] values = {controller.state.thumbLX, controller.state.thumbLY, controller.state.thumbRX, controller.state.thumbRY, controller.state.getDPadX(), controller.state.getDPadY()};
|
||||
|
||||
for (byte i = 0; i < axes.length; i++) {
|
||||
if (Math.abs(values[i]) > ControlElement.STICK_DEAD_ZONE) {
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], Mathf.sign(values[i])));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), true, values[i]);
|
||||
}
|
||||
else {
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, (byte) 1));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, value);
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, (byte)-1));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, value);
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], (byte) 1));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, values[i]);
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], (byte)-1));
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processTriggerButton(MotionEvent event, ExternalController controller) {
|
||||
ExternalControllerBinding controllerBinding;
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.AXIS_LTRIGGER);
|
||||
if (controllerBinding != null) {
|
||||
boolean pressed = event.getAxisValue(MotionEvent.AXIS_LTRIGGER) == 1 || event.getAxisValue(MotionEvent.AXIS_BRAKE) == 1;
|
||||
handleInputEvent(controllerBinding.getBinding(), pressed);
|
||||
}
|
||||
|
||||
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.AXIS_RTRIGGER);
|
||||
if (controllerBinding != null) {
|
||||
boolean pressed = event.getAxisValue(MotionEvent.AXIS_RTRIGGER) == 1 || event.getAxisValue(MotionEvent.AXIS_GAS) == 1;
|
||||
handleInputEvent(controllerBinding.getBinding(), pressed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if (!editMode && profile != null) {
|
||||
ExternalController controller = profile.getController(event.getDeviceId());
|
||||
if (controller != null) {
|
||||
if (ExternalController.isDPadDevice(event)) {
|
||||
processTriggerButton(event, controller);
|
||||
processJoystickInput(event, controller, -1);
|
||||
return true;
|
||||
}
|
||||
else if (ExternalController.isJoystickDevice(event)) {
|
||||
int historySize = event.getHistorySize();
|
||||
for (int i = 0; i < historySize; i++) processJoystickInput(event, controller, i);
|
||||
processJoystickInput(event, controller, -1);
|
||||
return true;
|
||||
}
|
||||
if (controller != null && controller.updateStateFromMotionEvent(event)) {
|
||||
ExternalControllerBinding controllerBinding;
|
||||
controllerBinding = controller.getControllerBinding(KeyEvent.KEYCODE_BUTTON_L2);
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), controller.state.isPressed(ExternalController.IDX_BUTTON_L2));
|
||||
|
||||
controllerBinding = controller.getControllerBinding(KeyEvent.KEYCODE_BUTTON_R2);
|
||||
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), controller.state.isPressed(ExternalController.IDX_BUTTON_R2));
|
||||
|
||||
processJoystickInput(controller);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
@@ -395,9 +381,6 @@ public class InputControlsView extends View {
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
for (ControlElement element : profile.getElements()) if (element.handleTouchUp(pointerId)) handled = true;
|
||||
if (!handled) touchpadView.onTouchEvent(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (ControlElement element : profile.getElements()) if (element.handleTouchUp(pointerId)) handled = true;
|
||||
if (!handled) touchpadView.onTouchEvent(event);
|
||||
@@ -433,36 +416,54 @@ public class InputControlsView extends View {
|
||||
}
|
||||
|
||||
public void handleInputEvent(Binding binding, boolean isActionDown, float offset) {
|
||||
if (isActionDown) {
|
||||
if (binding == Binding.MOUSE_MOVE_LEFT || binding == Binding.MOUSE_MOVE_RIGHT) {
|
||||
mouseMoveOffset.x = offset != 0 ? offset : (binding == Binding.MOUSE_MOVE_LEFT ? -1 : 1);
|
||||
createMouseMoveTimer();
|
||||
if (binding.isGamepad()) {
|
||||
GamepadState state = profile.getGamepadState();
|
||||
|
||||
int buttonIdx = binding.ordinal() - Binding.GAMEPAD_BUTTON_A.ordinal();
|
||||
if (buttonIdx <= 11) {
|
||||
state.setPressed(buttonIdx, isActionDown);
|
||||
if (xServer != null) xServer.getWinHandler().saveGamepadState(state);
|
||||
}
|
||||
else if (binding == Binding.MOUSE_MOVE_DOWN || binding == Binding.MOUSE_MOVE_UP) {
|
||||
mouseMoveOffset.y = offset != 0 ? offset : (binding == Binding.MOUSE_MOVE_UP ? -1 : 1);
|
||||
createMouseMoveTimer();
|
||||
else if (binding == Binding.GAMEPAD_LEFT_THUMB_UP || binding == Binding.GAMEPAD_LEFT_THUMB_DOWN) {
|
||||
state.thumbLY = isActionDown ? offset : 0;
|
||||
}
|
||||
else {
|
||||
Pointer.Button pointerButton = binding.getPointerButton();
|
||||
if (pointerButton != null) {
|
||||
xServer.injectPointerButtonPress(pointerButton);
|
||||
}
|
||||
else xServer.injectKeyPress(binding.keycode);
|
||||
else if (binding == Binding.GAMEPAD_LEFT_THUMB_LEFT || binding == Binding.GAMEPAD_LEFT_THUMB_RIGHT) {
|
||||
state.thumbLX = isActionDown ? offset : 0;
|
||||
}
|
||||
else if (binding == Binding.GAMEPAD_RIGHT_THUMB_UP || binding == Binding.GAMEPAD_RIGHT_THUMB_DOWN) {
|
||||
state.thumbRY = isActionDown ? offset : 0;
|
||||
}
|
||||
else if (binding == Binding.GAMEPAD_RIGHT_THUMB_LEFT || binding == Binding.GAMEPAD_RIGHT_THUMB_RIGHT) {
|
||||
state.thumbRX = isActionDown ? offset : 0;
|
||||
}
|
||||
else if (binding == Binding.GAMEPAD_DPAD_UP || binding == Binding.GAMEPAD_DPAD_RIGHT ||
|
||||
binding == Binding.GAMEPAD_DPAD_DOWN || binding == Binding.GAMEPAD_DPAD_LEFT) {
|
||||
state.dpad[binding.ordinal() - Binding.GAMEPAD_DPAD_UP.ordinal()] = isActionDown;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (binding == Binding.MOUSE_MOVE_LEFT || binding == Binding.MOUSE_MOVE_RIGHT) {
|
||||
if (Math.abs(offset) < InputControlsView.JOYSTICK_DEAD_ZONE) mouseMoveOffset.x = 0;
|
||||
mouseMoveOffset.x = isActionDown ? offset : 0;
|
||||
createMouseMoveTimer();
|
||||
}
|
||||
else if (binding == Binding.MOUSE_MOVE_UP || binding == Binding.MOUSE_MOVE_DOWN) {
|
||||
if (Math.abs(offset) < InputControlsView.JOYSTICK_DEAD_ZONE) mouseMoveOffset.y = 0;
|
||||
else if (binding == Binding.MOUSE_MOVE_DOWN || binding == Binding.MOUSE_MOVE_UP) {
|
||||
mouseMoveOffset.y = isActionDown ? offset : 0;
|
||||
createMouseMoveTimer();
|
||||
}
|
||||
else {
|
||||
Pointer.Button pointerButton = binding.getPointerButton();
|
||||
if (pointerButton != null) {
|
||||
xServer.injectPointerButtonRelease(pointerButton);
|
||||
if (isActionDown) {
|
||||
if (pointerButton != null) {
|
||||
xServer.injectPointerButtonPress(pointerButton);
|
||||
}
|
||||
else xServer.injectKeyPress(binding.keycode);
|
||||
}
|
||||
else {
|
||||
if (pointerButton != null) {
|
||||
xServer.injectPointerButtonRelease(pointerButton);
|
||||
}
|
||||
else xServer.injectKeyRelease(binding.keycode);
|
||||
}
|
||||
else xServer.injectKeyRelease(binding.keycode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.winlator.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.winlator.R;
|
||||
import com.winlator.math.Mathf;
|
||||
|
||||
public class NumberPicker extends FrameLayout implements View.OnTouchListener {
|
||||
private int value = 0;
|
||||
private int minValue = 0;
|
||||
private int maxValue = 100;
|
||||
private int step = 1;
|
||||
private final EditText editText;
|
||||
private OnValueChangeListener onValueChangeListener;
|
||||
|
||||
public interface OnValueChangeListener {
|
||||
void onValueChange(NumberPicker numberPicker, int value);
|
||||
}
|
||||
|
||||
public NumberPicker(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public NumberPicker(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.number_picker, this, true);
|
||||
editText = findViewById(R.id.EditText);
|
||||
findViewById(R.id.BTDecrement).setOnTouchListener(this);
|
||||
findViewById(R.id.BTIncrement).setOnTouchListener(this);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumberPicker);
|
||||
minValue = ta.getInt(R.styleable.NumberPicker_minValue, minValue);
|
||||
maxValue = ta.getInt(R.styleable.NumberPicker_maxValue, maxValue);
|
||||
setStep(ta.getInt(R.styleable.NumberPicker_step, step));
|
||||
int value = ta.getInt(R.styleable.NumberPicker_value, 0);
|
||||
ta.recycle();
|
||||
setValue(value);
|
||||
}
|
||||
else setStep(step);
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = Mathf.clamp(value, minValue, maxValue);
|
||||
editText.setText(String.valueOf(this.value));
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public void setMinValue(int minValue) {
|
||||
this.minValue = minValue;
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
setValue(value+step);
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
setValue(value-step);
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return step;
|
||||
}
|
||||
|
||||
public void setStep(int step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public OnValueChangeListener getOnValueChangeListener() {
|
||||
return onValueChangeListener;
|
||||
}
|
||||
|
||||
public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener) {
|
||||
this.onValueChangeListener = onValueChangeListener;
|
||||
}
|
||||
|
||||
private void onButtonClick(View v) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
int id = v.getId();
|
||||
if (id == R.id.BTIncrement) {
|
||||
increment();
|
||||
}
|
||||
else if (id == R.id.BTDecrement) {
|
||||
decrement();
|
||||
}
|
||||
if (onValueChangeListener != null) {
|
||||
onValueChangeListener.onValueChange(NumberPicker.this, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(final View v, MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
onButtonClick(v);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.winlator.winhandler;
|
||||
|
||||
public interface OnGetProcessInfoListener {
|
||||
void onGetProcessInfo(int index, int count, ProcessInfo processInfo);
|
||||
}
|
||||
@@ -2,9 +2,14 @@ package com.winlator.winhandler;
|
||||
|
||||
abstract class RequestCodes {
|
||||
public static final byte EXIT = 0;
|
||||
public static final byte EXEC = 1;
|
||||
public static final byte KILL_PROCESS = 2;
|
||||
public static final byte GET_PROCESSES = 3;
|
||||
public static final byte SET_PROCESS_AFFINITY = 4;
|
||||
public static final byte MOUSE_EVENT = 5;
|
||||
}
|
||||
public static final byte INIT = 1;
|
||||
public static final byte EXEC = 2;
|
||||
public static final byte KILL_PROCESS = 3;
|
||||
public static final byte LIST_PROCESSES = 4;
|
||||
public static final byte GET_PROCESS = 5;
|
||||
public static final byte SET_PROCESS_AFFINITY = 6;
|
||||
public static final byte MOUSE_EVENT = 7;
|
||||
public static final byte GET_GAMEPAD = 8;
|
||||
public static final byte GET_GAMEPAD_STATE = 9;
|
||||
public static final byte RELEASE_GAMEPAD = 10;
|
||||
}
|
||||
@@ -19,10 +19,11 @@ import com.winlator.contentdialog.ContentDialog;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class TaskManagerDialog extends ContentDialog {
|
||||
public class TaskManagerDialog extends ContentDialog implements OnGetProcessInfoListener {
|
||||
private final XServerDisplayActivity activity;
|
||||
private final LayoutInflater inflater;
|
||||
private Timer timer;
|
||||
private final Object lock = new Object();
|
||||
|
||||
public TaskManagerDialog(XServerDisplayActivity activity) {
|
||||
super(activity, R.layout.task_manager_dialog);
|
||||
@@ -43,32 +44,17 @@ public class TaskManagerDialog extends ContentDialog {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
activity.getWinHandler().setOnGetProcessInfoListener(null);
|
||||
});
|
||||
|
||||
inflater = LayoutInflater.from(activity);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
WinHandler winHandler = activity.getWinHandler();
|
||||
winHandler.getProcesses((processes) -> activity.runOnUiThread(() -> {
|
||||
final LinearLayout container = findViewById(R.id.LLProcessList);
|
||||
container.removeAllViews();
|
||||
setBottomBarText(activity.getString(R.string.processes)+": "+processes.size());
|
||||
|
||||
if (!processes.isEmpty()) {
|
||||
findViewById(R.id.TVEmptyText).setVisibility(View.GONE);
|
||||
|
||||
for (final ProcessInfo processInfo : processes) {
|
||||
View itemView = inflater.inflate(R.layout.process_info_list_item, container, false);
|
||||
((TextView)itemView.findViewById(R.id.TVName)).setText(processInfo.name);
|
||||
((TextView)itemView.findViewById(R.id.TVPID)).setText(String.valueOf(processInfo.pid));
|
||||
((TextView)itemView.findViewById(R.id.TVMemoryUsage)).setText(processInfo.getFormattedMemoryUsage());
|
||||
itemView.findViewById(R.id.BTMenu).setOnClickListener((v) -> showListItemMenu(v, processInfo));
|
||||
container.addView(itemView);
|
||||
}
|
||||
}
|
||||
else findViewById(R.id.TVEmptyText).setVisibility(View.VISIBLE);
|
||||
}));
|
||||
synchronized (lock) {
|
||||
activity.getWinHandler().listProcesses();
|
||||
}
|
||||
|
||||
LinearLayout tableHead = findViewById(R.id.LLTableHead);
|
||||
TextView tvMemoryInfo = (TextView)tableHead.getChildAt(2);
|
||||
@@ -120,13 +106,45 @@ public class TaskManagerDialog extends ContentDialog {
|
||||
@Override
|
||||
public void show() {
|
||||
update();
|
||||
activity.getWinHandler().setOnGetProcessInfoListener(this);
|
||||
|
||||
timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
update();
|
||||
activity.runOnUiThread(TaskManagerDialog.this::update);
|
||||
}
|
||||
}, 0, 1000);
|
||||
super.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetProcessInfo(int index, int numProcesses, ProcessInfo processInfo) {
|
||||
activity.runOnUiThread(() -> {
|
||||
synchronized (lock) {
|
||||
final LinearLayout container = findViewById(R.id.LLProcessList);
|
||||
setBottomBarText(activity.getString(R.string.processes)+": " + numProcesses);
|
||||
|
||||
if (numProcesses == 0) {
|
||||
container.removeAllViews();
|
||||
findViewById(R.id.TVEmptyText).setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
findViewById(R.id.TVEmptyText).setVisibility(View.GONE);
|
||||
|
||||
int childCount = container.getChildCount();
|
||||
View itemView = index < childCount ? container.getChildAt(index) : inflater.inflate(R.layout.process_info_list_item, container, false);
|
||||
((TextView)itemView.findViewById(R.id.TVName)).setText(processInfo.name);
|
||||
((TextView)itemView.findViewById(R.id.TVPID)).setText(String.valueOf(processInfo.pid));
|
||||
((TextView)itemView.findViewById(R.id.TVMemoryUsage)).setText(processInfo.getFormattedMemoryUsage());
|
||||
itemView.findViewById(R.id.BTMenu).setOnClickListener((v) -> showListItemMenu(v, processInfo));
|
||||
if (index >= childCount) container.addView(itemView);
|
||||
|
||||
if (index == numProcesses-1 && childCount > numProcesses) {
|
||||
for (int i = numProcesses; i < childCount; i++) container.removeViewAt(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,55 @@
|
||||
package com.winlator.winhandler;
|
||||
|
||||
import com.winlator.core.Callback;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.winlator.XServerDisplayActivity;
|
||||
import com.winlator.core.StringUtils;
|
||||
import com.winlator.inputcontrols.ControlsProfile;
|
||||
import com.winlator.inputcontrols.ExternalController;
|
||||
import com.winlator.inputcontrols.GamepadState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class WinHandler {
|
||||
private static final short DEFAULT_PORT = 7946;
|
||||
private Socket socket;
|
||||
private final Object lock = new Object();
|
||||
private ByteBuffer data = ByteBuffer.allocate(128).order(ByteOrder.LITTLE_ENDIAN);
|
||||
private static final short SERVER_PORT = 7947;
|
||||
private static final short CLIENT_PORT = 7946;
|
||||
public static final byte DINPUT_MAPPER_TYPE_STANDARD = 0;
|
||||
public static final byte DINPUT_MAPPER_TYPE_XINPUT = 1;
|
||||
private DatagramSocket socket;
|
||||
private final ByteBuffer sendData = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
private final ByteBuffer receiveData = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
private final DatagramPacket sendPacket = new DatagramPacket(sendData.array(), 64);
|
||||
private final DatagramPacket receivePacket = new DatagramPacket(receiveData.array(), 64);
|
||||
private final ArrayDeque<Runnable> actions = new ArrayDeque<>();
|
||||
private boolean initReceived = false;
|
||||
private boolean running = false;
|
||||
private OnGetProcessInfoListener onGetProcessInfoListener;
|
||||
private ExternalController currentController;
|
||||
private final ArrayDeque<byte[]> gamepadStateQueue = new ArrayDeque<>();
|
||||
private InetAddress localhost;
|
||||
private byte dinputMapperType = DINPUT_MAPPER_TYPE_XINPUT;
|
||||
private final XServerDisplayActivity activity;
|
||||
|
||||
private boolean sendData() {
|
||||
public WinHandler(XServerDisplayActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
private boolean sendPacket(int port) {
|
||||
try {
|
||||
int size = data.position();
|
||||
if (size == 0) return true;
|
||||
OutputStream outStream = socket.getOutputStream();
|
||||
outStream.write(data.array(), 0, size);
|
||||
int size = sendData.position();
|
||||
if (size == 0) return false;
|
||||
sendPacket.setAddress(localhost);
|
||||
sendPacket.setPort(port);
|
||||
socket.send(sendPacket);
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
@@ -37,158 +57,269 @@ public class WinHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean receiveData(int length) {
|
||||
try {
|
||||
while (length > data.capacity()) {
|
||||
data = ByteBuffer.allocate(data.capacity() * 2).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
data.rewind();
|
||||
InputStream inStream = socket.getInputStream();
|
||||
int bytesRead = inStream.read(data.array(), 0, length);
|
||||
if (bytesRead > 0) {
|
||||
data.limit(length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void exec(String command) {
|
||||
command = command.trim();
|
||||
if (command.isEmpty()) return;
|
||||
String[] cmdList = command.split(" ", 2);
|
||||
final String filename = cmdList[0];
|
||||
final String parameters = cmdList.length > 1 ? cmdList[1] : "";
|
||||
synchronized (actions) {
|
||||
command = command.trim();
|
||||
if (command.isEmpty()) return;
|
||||
String[] cmdList = command.split(" ", 2);
|
||||
final String filename = cmdList[0];
|
||||
final String parameters = cmdList.length > 1 ? cmdList[1] : "";
|
||||
|
||||
synchronized (lock) {
|
||||
actions.add(() -> {
|
||||
byte[] filenameBytes = filename.getBytes();
|
||||
byte[] parametersBytes = parameters.getBytes();
|
||||
|
||||
data.rewind();
|
||||
data.put(RequestCodes.EXEC);
|
||||
data.putInt(filenameBytes.length + parametersBytes.length + 8);
|
||||
data.putInt(filenameBytes.length);
|
||||
data.putInt(parametersBytes.length);
|
||||
data.put(filenameBytes);
|
||||
data.put(parametersBytes);
|
||||
sendData();
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.EXEC);
|
||||
sendData.putInt(filenameBytes.length + parametersBytes.length + 8);
|
||||
sendData.putInt(filenameBytes.length);
|
||||
sendData.putInt(parametersBytes.length);
|
||||
sendData.put(filenameBytes);
|
||||
sendData.put(parametersBytes);
|
||||
sendPacket(CLIENT_PORT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void killProcess(final String processName) {
|
||||
synchronized (lock) {
|
||||
synchronized (actions) {
|
||||
actions.add(() -> {
|
||||
data.rewind();
|
||||
data.put(RequestCodes.KILL_PROCESS);
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.KILL_PROCESS);
|
||||
byte[] bytes = processName.getBytes();
|
||||
data.putInt(bytes.length);
|
||||
data.put(bytes);
|
||||
sendData();
|
||||
sendData.putInt(bytes.length);
|
||||
sendData.put(bytes);
|
||||
sendPacket(CLIENT_PORT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void getProcesses(final Callback<List<ProcessInfo>> callback) {
|
||||
synchronized (lock) {
|
||||
public void listProcesses() {
|
||||
synchronized (actions) {
|
||||
actions.add(() -> {
|
||||
data.rewind();
|
||||
data.put(RequestCodes.GET_PROCESSES);
|
||||
data.putInt(0);
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.LIST_PROCESSES);
|
||||
sendData.putInt(0);
|
||||
|
||||
if (sendData() && receiveData(5)) {
|
||||
boolean success = data.get() == 1;
|
||||
int responseLength = data.getInt();
|
||||
if (success && receiveData(responseLength)) {
|
||||
ArrayList<ProcessInfo> processes = new ArrayList<>();
|
||||
while (data.position() < data.limit()) {
|
||||
int numBytesBefore = data.position();
|
||||
int pid = data.getInt();
|
||||
long memoryUsage = data.getLong();
|
||||
int affinityMask = data.getInt();
|
||||
|
||||
byte[] bytes = new byte[32];
|
||||
data.get(bytes);
|
||||
String name = StringUtils.fromANSIString(bytes);
|
||||
|
||||
processes.add(new ProcessInfo(pid, name, memoryUsage, affinityMask));
|
||||
|
||||
int bytesRead = data.position() - numBytesBefore;
|
||||
data.position(data.position() + (64 - bytesRead));
|
||||
}
|
||||
callback.call(processes);
|
||||
}
|
||||
else callback.call(Collections.emptyList());
|
||||
if (!sendPacket(CLIENT_PORT) && onGetProcessInfoListener != null) {
|
||||
onGetProcessInfoListener.onGetProcessInfo(0, 0, null);
|
||||
}
|
||||
else callback.call(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setProcessAffinity(final int pid, final int affinityMask) {
|
||||
synchronized (lock) {
|
||||
synchronized (actions) {
|
||||
actions.add(() -> {
|
||||
data.rewind();
|
||||
data.put(RequestCodes.SET_PROCESS_AFFINITY);
|
||||
data.putInt(8);
|
||||
data.putInt(pid);
|
||||
data.putInt(affinityMask);
|
||||
sendData();
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.SET_PROCESS_AFFINITY);
|
||||
sendData.putInt(8);
|
||||
sendData.putInt(pid);
|
||||
sendData.putInt(affinityMask);
|
||||
sendPacket(CLIENT_PORT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEvent(int flags, int dx, int dy, int wheelDelta) {
|
||||
synchronized (lock) {
|
||||
if (!isConnected()) return;
|
||||
synchronized (actions) {
|
||||
if (!initReceived) return;
|
||||
actions.add(() -> {
|
||||
data.rewind();
|
||||
data.put(RequestCodes.MOUSE_EVENT);
|
||||
data.putInt(10);
|
||||
data.putInt(flags);
|
||||
data.putShort((short)dx);
|
||||
data.putShort((short)dy);
|
||||
data.putShort((short)wheelDelta);
|
||||
sendData();
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.MOUSE_EVENT);
|
||||
sendData.putInt(10);
|
||||
sendData.putInt(flags);
|
||||
sendData.putShort((short)dx);
|
||||
sendData.putShort((short)dy);
|
||||
sendData.putShort((short)wheelDelta);
|
||||
sendPacket(CLIENT_PORT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return socket != null && !socket.isClosed() && socket.isConnected();
|
||||
public OnGetProcessInfoListener getOnGetProcessInfoListener() {
|
||||
return onGetProcessInfoListener;
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
InetAddress address = null;
|
||||
try {
|
||||
address = InetAddress.getLocalHost();
|
||||
public void setOnGetProcessInfoListener(OnGetProcessInfoListener onGetProcessInfoListener) {
|
||||
synchronized (actions) {
|
||||
this.onGetProcessInfoListener = onGetProcessInfoListener;
|
||||
}
|
||||
catch (UnknownHostException e) {}
|
||||
|
||||
try {
|
||||
socket = new Socket(address.getHostAddress(), DEFAULT_PORT);
|
||||
}
|
||||
catch (IOException e) {}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
private void startSendThread() {
|
||||
Executors.newSingleThreadExecutor().execute(() -> {
|
||||
while (true) {
|
||||
synchronized (lock) {
|
||||
if (!isConnected()) connect();
|
||||
if (isConnected()) while (!actions.isEmpty()) actions.poll().run();
|
||||
}
|
||||
|
||||
if (!isConnected()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {}
|
||||
while (running) {
|
||||
synchronized (actions) {
|
||||
while (initReceived && !actions.isEmpty()) actions.poll().run();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRequest(byte requestCode, final int port) {
|
||||
switch (requestCode) {
|
||||
case RequestCodes.INIT: {
|
||||
initReceived = true;
|
||||
break;
|
||||
}
|
||||
case RequestCodes.GET_PROCESS: {
|
||||
if (onGetProcessInfoListener == null) return;
|
||||
receiveData.position(receiveData.position() + 4);
|
||||
int numProcesses = receiveData.getShort();
|
||||
int index = receiveData.getShort();
|
||||
int pid = receiveData.getInt();
|
||||
long memoryUsage = receiveData.getLong();
|
||||
int affinityMask = receiveData.getInt();
|
||||
|
||||
byte[] bytes = new byte[32];
|
||||
receiveData.get(bytes);
|
||||
String name = StringUtils.fromANSIString(bytes);
|
||||
|
||||
onGetProcessInfoListener.onGetProcessInfo(index, numProcesses, new ProcessInfo(pid, name, memoryUsage, affinityMask));
|
||||
break;
|
||||
}
|
||||
case RequestCodes.GET_GAMEPAD: {
|
||||
boolean isXInput = receiveData.get() == 1;
|
||||
final ControlsProfile profile = activity.getInputControlsView().getProfile();
|
||||
boolean useVirtualGamepad = profile != null && profile.isVirtualGamepad();
|
||||
|
||||
if (!useVirtualGamepad && (currentController == null || !currentController.isConnected())) {
|
||||
releaseCurrentController();
|
||||
currentController = ExternalController.getController(0);
|
||||
}
|
||||
|
||||
actions.add(() -> {
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.GET_GAMEPAD);
|
||||
|
||||
if (currentController != null || useVirtualGamepad) {
|
||||
sendData.putInt(!useVirtualGamepad ? currentController.getDeviceId() : profile.id);
|
||||
sendData.put(dinputMapperType);
|
||||
byte[] bytes = (useVirtualGamepad ? profile.getName() : currentController.getName()).getBytes();
|
||||
sendData.putInt(bytes.length);
|
||||
sendData.put(bytes);
|
||||
}
|
||||
else sendData.putInt(0);
|
||||
|
||||
sendPacket(port);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case RequestCodes.GET_GAMEPAD_STATE: {
|
||||
int gamepadId = receiveData.getInt();
|
||||
final ControlsProfile profile = activity.getInputControlsView().getProfile();
|
||||
boolean useVirtualGamepad = profile != null && profile.isVirtualGamepad();
|
||||
|
||||
if (currentController != null && currentController.getDeviceId() != gamepadId) currentController = null;
|
||||
|
||||
actions.add(() -> {
|
||||
sendData.rewind();
|
||||
sendData.put(RequestCodes.GET_GAMEPAD_STATE);
|
||||
sendData.put((byte)(currentController != null || useVirtualGamepad ? 1 : 0));
|
||||
|
||||
if (currentController != null || useVirtualGamepad) {
|
||||
sendData.putInt(gamepadId);
|
||||
|
||||
synchronized (gamepadStateQueue) {
|
||||
if (gamepadStateQueue.isEmpty()) {
|
||||
if (useVirtualGamepad) {
|
||||
profile.getGamepadState().writeTo(sendData);
|
||||
}
|
||||
else currentController.state.writeTo(sendData);
|
||||
}
|
||||
else sendData.put(gamepadStateQueue.poll());
|
||||
}
|
||||
}
|
||||
|
||||
sendPacket(port);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case RequestCodes.RELEASE_GAMEPAD:
|
||||
releaseCurrentController();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
localhost = InetAddress.getLocalHost();
|
||||
}
|
||||
catch (UnknownHostException e) {}
|
||||
|
||||
running = true;
|
||||
startSendThread();
|
||||
Executors.newSingleThreadExecutor().execute(() -> {
|
||||
try {
|
||||
socket = new DatagramSocket(SERVER_PORT);
|
||||
|
||||
while (running) {
|
||||
socket.receive(receivePacket);
|
||||
|
||||
synchronized (actions) {
|
||||
receiveData.rewind();
|
||||
byte requestCode = receiveData.get();
|
||||
handleRequest(requestCode, receivePacket.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {}
|
||||
});
|
||||
}
|
||||
|
||||
public void saveGamepadState(GamepadState state) {
|
||||
synchronized (gamepadStateQueue) {
|
||||
if (gamepadStateQueue.size() > 20) gamepadStateQueue.removeLast();
|
||||
gamepadStateQueue.add(state.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseCurrentController() {
|
||||
currentController = null;
|
||||
synchronized (gamepadStateQueue) {
|
||||
gamepadStateQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
boolean handled = false;
|
||||
if (currentController != null && currentController.getDeviceId() == event.getDeviceId()) {
|
||||
handled = currentController.updateStateFromMotionEvent(event);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public boolean onKeyEvent(KeyEvent event) {
|
||||
boolean handled = false;
|
||||
if (currentController != null && currentController.getDeviceId() == event.getDeviceId() && event.getRepeatCount() == 0) {
|
||||
int action = event.getAction();
|
||||
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
handled = currentController.updateStateFromKeyEvent(event);
|
||||
}
|
||||
else if (action == KeyEvent.ACTION_UP) {
|
||||
handled = currentController.updateStateFromKeyEvent(event);
|
||||
}
|
||||
|
||||
if (handled) saveGamepadState(currentController.state);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public byte getDInputMapperType() {
|
||||
return dinputMapperType;
|
||||
}
|
||||
|
||||
public void setDInputMapperType(byte dinputMapperType) {
|
||||
this.dinputMapperType = dinputMapperType;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,10 @@ public class ImageFs {
|
||||
return new File(rootDir, "/opt/installed-wine");
|
||||
}
|
||||
|
||||
public File getTmpDir() {
|
||||
return new File(rootDir, "/tmp");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
+4
@@ -127,11 +127,15 @@ public class GuestProgramLauncherComponent extends EnvironmentComponent {
|
||||
envVars.put("ANDROID_SYSVSHM_SERVER", UnixSocketConfig.SYSVSHM_SERVER_PATH);
|
||||
if (this.envVars != null) envVars.putAll(this.envVars);
|
||||
|
||||
File shmDir = new File(rootDir, "/tmp/shm");
|
||||
shmDir.mkdirs();
|
||||
|
||||
String command = nativeLibraryDir+"/libproot.so";
|
||||
command += " --kill-on-exit";
|
||||
command += " --rootfs="+rootDir;
|
||||
command += " --cwd="+ImageFs.HOME_PATH;
|
||||
command += " --bind=/dev";
|
||||
command += " --bind="+shmDir.getAbsolutePath()+":/dev/shm";
|
||||
command += " --bind=/proc";
|
||||
command += " --bind=/sys";
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.view.KeyEvent;
|
||||
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import com.winlator.inputcontrols.ExternalController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Keyboard {
|
||||
@@ -92,6 +94,8 @@ public class Keyboard {
|
||||
}
|
||||
|
||||
public boolean onKeyEvent(KeyEvent event) {
|
||||
if (ExternalController.isGameController(event.getDevice())) return false;
|
||||
|
||||
int action = event.getAction();
|
||||
if (action == KeyEvent.ACTION_DOWN || action == KeyEvent.ACTION_UP) {
|
||||
int keyCode = event.getKeyCode();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 245 B |
Binary file not shown.
|
After Width: | Height: | Size: 366 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/colorPrimaryDark" />
|
||||
<stroke android:width="1dp" android:color="#303030" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
@@ -100,6 +100,18 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/vertical" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/columns" />
|
||||
|
||||
<com.winlator.widget.NumberPicker
|
||||
android:id="@+id/NPColumns"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:minValue="3"
|
||||
app:maxValue="8" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -178,7 +190,8 @@
|
||||
android:id="@+id/ETCustomText"
|
||||
android:maxLength="8"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="@string/none" />
|
||||
android:hint="@string/none"
|
||||
android:inputType="textCapCharacters" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -33,12 +33,6 @@
|
||||
android:checked="true"
|
||||
android:text="@string/show_touchscreen_controls" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/CBCursorVisible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cursor_visible" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/CBLockCursor"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_margin="1dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/BTDecrement"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/number_picker_background"
|
||||
android:padding="7dp"
|
||||
android:gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/number_picker_decrement"
|
||||
app:tint="#ffffff" />
|
||||
|
||||
<EditText
|
||||
style="@style/EditText"
|
||||
android:id="@+id/EditText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:focusable="false"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:focusableInTouchMode="false"
|
||||
android:inputType="number" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/BTIncrement"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/number_picker_background"
|
||||
android:padding="7dp"
|
||||
android:gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/number_picker_increment"
|
||||
app:tint="#ffffff" />
|
||||
</LinearLayout>
|
||||
@@ -204,11 +204,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:min="10"
|
||||
android:max="200" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/CBUseDRI3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/use_dri3_extension_experimental"
|
||||
android:text="@string/use_dri3_extension"
|
||||
android:layout_marginTop="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -255,6 +255,17 @@
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<LinearLayout style="@style/FieldSet">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/directinput_mapper_type" />
|
||||
|
||||
<Spinner
|
||||
style="@style/ComboBox"
|
||||
android:id="@+id/SDInputMapperType"
|
||||
android:layout_width="match_parent"
|
||||
android:entries="@array/dinput_mapper_type_entries" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -278,13 +289,6 @@
|
||||
android:src="@drawable/icon_menu" />
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/CBSingleCPU"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/use_single_cpu"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/CBForceFullscreen"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<string-array name="binding_type_entries">
|
||||
<item>Keyboard</item>
|
||||
<item>Mouse</item>
|
||||
<item>Gamepad</item>
|
||||
</string-array>
|
||||
<string-array name="dxwrapper_entries">
|
||||
<item>Original WineD3D</item>
|
||||
@@ -49,11 +50,15 @@
|
||||
</string-array>
|
||||
<string-array name="box64_version_entries">
|
||||
<item>0.2.2 (old)</item>
|
||||
<item>0.2.5 (new)</item>
|
||||
<item>0.2.6 (new)</item>
|
||||
</string-array>
|
||||
<string-array name="turnip_version_entries">
|
||||
<item>23.1.6 (a6xx)</item>
|
||||
<item>23.3.0 (a7xx)</item>
|
||||
<item>24.0.0 (a7xx)</item>
|
||||
</string-array>
|
||||
<string-array name="dinput_mapper_type_entries">
|
||||
<item>Standard (Old Gamepads)</item>
|
||||
<item>XInput</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="NumberPicker">
|
||||
<attr name="value" format="integer" />
|
||||
<attr name="minValue" format="integer" />
|
||||
<attr name="maxValue" format="integer" />
|
||||
<attr name="step" format="integer" />
|
||||
<attr name="textSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -62,7 +62,6 @@
|
||||
<string name="value">Value</string>
|
||||
<string name="processor_affinity">Processor Affinity</string>
|
||||
<string name="cursor_speed">Cursor Speed</string>
|
||||
<string name="cursor_visible">Cursor Visible</string>
|
||||
<string name="lock_cursor">Lock Cursor</string>
|
||||
<string name="toggle_switch">Toggle Switch</string>
|
||||
<string name="profile_exported_to">Profile exported to</string>
|
||||
@@ -144,7 +143,7 @@
|
||||
<string name="letter">Letter</string>
|
||||
<string name="target_path">Target Path</string>
|
||||
<string name="xserver">XServer</string>
|
||||
<string name="use_dri3_extension_experimental">Use DRI3 extension (Experimental)</string>
|
||||
<string name="use_dri3_extension">Use DRI3 extension</string>
|
||||
<string name="width">Width</string>
|
||||
<string name="height">Height</string>
|
||||
<string name="old">old</string>
|
||||
@@ -174,7 +173,6 @@
|
||||
<string name="performance_unstable">Performance (Unstable)</string>
|
||||
<string name="preset">Preset</string>
|
||||
<string name="you_cannot_remove_this_preset">You cannot remove this preset</string>
|
||||
<string name="use_single_cpu">Use single CPU</string>
|
||||
<string name="dxwrapper_help_content"><![CDATA[
|
||||
<i><b>WineD3D</b></i> is a translation layer for DirectX 1-11 to OpenGL.<br /><br />
|
||||
<i><b>DXVK</b></i> is a translation layer for Direct3D 9-11 to Vulkan.<br /><br />
|
||||
@@ -207,4 +205,6 @@
|
||||
<string name="box86_64_env_var_help__dynarec_forward">Defines the max allowed forward value when building Block</string>
|
||||
<string name="box86_64_env_var_help__dynarec_callret">Optimization of CALL/RET opcodes</string>
|
||||
<string name="box86_64_env_var_help__dynarec_wait">Defines whether or not Dynarec will wait for the FillBlock to be ready</string>
|
||||
<string name="columns">Columns</string>
|
||||
<string name="directinput_mapper_type">DirectInput Mapper Type</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user