diff --git a/app/build.gradle b/app/build.gradle
index 05c3a5f..f415597 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId 'com.winlator'
minSdkVersion 26
targetSdkVersion 28
- versionCode 9
- versionName "3.2"
+ versionCode 10
+ versionName "4.0"
}
buildTypes {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a75b95b..64e4e5f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,7 @@
+ android:exported="false"
+ android:screenOrientation="sensor" />
diff --git a/app/src/main/assets/box64_env_vars.json b/app/src/main/assets/box64_env_vars.json
index 046ca8f..1f8d492 100644
--- a/app/src/main/assets/box64_env_vars.json
+++ b/app/src/main/assets/box64_env_vars.json
@@ -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"},
diff --git a/app/src/main/assets/box86_64/box64-0.2.5.tzst b/app/src/main/assets/box86_64/box64-0.2.6.tzst
similarity index 51%
rename from app/src/main/assets/box86_64/box64-0.2.5.tzst
rename to app/src/main/assets/box86_64/box64-0.2.6.tzst
index fcbdac4..261dde1 100644
Binary files a/app/src/main/assets/box86_64/box64-0.2.5.tzst and b/app/src/main/assets/box86_64/box64-0.2.6.tzst differ
diff --git a/app/src/main/assets/box86_env_vars.json b/app/src/main/assets/box86_env_vars.json
index 1c988e1..579cb9d 100644
--- a/app/src/main/assets/box86_env_vars.json
+++ b/app/src/main/assets/box86_env_vars.json
@@ -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"},
diff --git a/app/src/main/assets/inputcontrols/profiles/controls-3.icp b/app/src/main/assets/inputcontrols/profiles/controls-3.icp
new file mode 100644
index 0000000..75a2497
--- /dev/null
+++ b/app/src/main/assets/inputcontrols/profiles/controls-3.icp
@@ -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}]}
\ No newline at end of file
diff --git a/app/src/main/assets/patches.tzst b/app/src/main/assets/patches.tzst
index 81f2596..f6bfd8a 100644
Binary files a/app/src/main/assets/patches.tzst and b/app/src/main/assets/patches.tzst differ
diff --git a/app/src/main/java/com/winlator/ContainerDetailFragment.java b/app/src/main/java/com/winlator/ContainerDetailFragment.java
index 4ea1bdd..fdd173a 100644
--- a/app/src/main/java/com/winlator/ContainerDetailFragment.java
+++ b/app/src/main/java/com/winlator/ContainerDetailFragment.java
@@ -58,7 +58,6 @@ public class ContainerDetailFragment extends Fragment {
private PreloaderDialog preloaderDialog;
private JSONArray gpuNames;
private Callback 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");
}
}
diff --git a/app/src/main/java/com/winlator/ControlsEditorActivity.java b/app/src/main/java/com/winlator/ControlsEditorActivity.java
index 760e6ea..94bdd8f 100644
--- a/app/src/main/java/com/winlator/ControlsEditorActivity.java
+++ b/app/src/main/java/com/winlator/ControlsEditorActivity.java
@@ -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();
diff --git a/app/src/main/java/com/winlator/ExternalControllerBindingsActivity.java b/app/src/main/java/com/winlator/ExternalControllerBindingsActivity.java
index 9e35fca..633cf96 100644
--- a/app/src/main/java/com/winlator/ExternalControllerBindingsActivity.java
+++ b/app/src/main/java/com/winlator/ExternalControllerBindingsActivity.java
@@ -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();
diff --git a/app/src/main/java/com/winlator/MainActivity.java b/app/src/main/java/com/winlator/MainActivity.java
index 786bfd6..25645b1 100644
--- a/app/src/main/java/com/winlator/MainActivity.java
+++ b/app/src/main/java/com/winlator/MainActivity.java
@@ -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;
diff --git a/app/src/main/java/com/winlator/SettingsFragment.java b/app/src/main/java/com/winlator/SettingsFragment.java
index ad0cb2b..6c48286 100644
--- a/app/src/main/java/com/winlator/SettingsFragment.java
+++ b/app/src/main/java/com/winlator/SettingsFragment.java
@@ -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 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();
+ }
}
diff --git a/app/src/main/java/com/winlator/XServerDisplayActivity.java b/app/src/main/java/com/winlator/XServerDisplayActivity.java
index 4a19be7..212718e 100644
--- a/app/src/main/java/com/winlator/XServerDisplayActivity.java
+++ b/app/src/main/java/com/winlator/XServerDisplayActivity.java
@@ -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 : "";
diff --git a/app/src/main/java/com/winlator/container/Container.java b/app/src/main/java/com/winlator/container/Container.java
index c21c50e..0b30a7b 100644
--- a/app/src/main/java/com/winlator/container/Container.java
+++ b/app/src/main/java/com/winlator/container/Container.java
@@ -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;
diff --git a/app/src/main/java/com/winlator/contentdialog/ShortcutSettingsDialog.java b/app/src/main/java/com/winlator/contentdialog/ShortcutSettingsDialog.java
index 73715ca..ebbb4aa 100644
--- a/app/src/main/java/com/winlator/contentdialog/ShortcutSettingsDialog.java
+++ b/app/src/main/java/com/winlator/contentdialog/ShortcutSettingsDialog.java
@@ -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();
}
});
diff --git a/app/src/main/java/com/winlator/core/OBBImageInstaller.java b/app/src/main/java/com/winlator/core/OBBImageInstaller.java
index 5c8fba7..50d996e 100644
--- a/app/src/main/java/com/winlator/core/OBBImageInstaller.java
+++ b/app/src/main/java/com/winlator/core/OBBImageInstaller.java
@@ -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;
diff --git a/app/src/main/java/com/winlator/core/ProcessHelper.java b/app/src/main/java/com/winlator/core/ProcessHelper.java
index 6978521..fb6474d 100644
--- a/app/src/main/java/com/winlator/core/ProcessHelper.java
+++ b/app/src/main/java/com/winlator/core/ProcessHelper.java
@@ -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;
diff --git a/app/src/main/java/com/winlator/core/TarXZUtils.java b/app/src/main/java/com/winlator/core/TarXZUtils.java
index 038a135..75a8e8e 100644
--- a/app/src/main/java/com/winlator/core/TarXZUtils.java
+++ b/app/src/main/java/com/winlator/core/TarXZUtils.java
@@ -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) {
diff --git a/app/src/main/java/com/winlator/core/WineUtils.java b/app/src/main/java/com/winlator/core/WineUtils.java
index 188c3fa..2c0e98d 100644
--- a/app/src/main/java/com/winlator/core/WineUtils.java
+++ b/app/src/main/java/com/winlator/core/WineUtils.java
@@ -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");
}
}
diff --git a/app/src/main/java/com/winlator/inputcontrols/Binding.java b/app/src/main/java/com/winlator/inputcontrols/Binding.java
index 73f0960..472959c 100644
--- a/app/src/main/java/com/winlator/inputcontrols/Binding.java
+++ b/app/src/main/java/com/winlator/inputcontrols/Binding.java
@@ -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 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 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 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 labels = new ArrayList<>();
+ for (Binding binding : values()) if (binding.isGamepad()) labels.add(binding);
+ return labels.toArray(new Binding[0]);
+ }
}
diff --git a/app/src/main/java/com/winlator/inputcontrols/ControlElement.java b/app/src/main/java/com/winlator/inputcontrols/ControlElement.java
index 9cf3ad0..a637a06 100644
--- a/app/src/main/java/com/winlator/inputcontrols/ControlElement.java
+++ b/app/src/main/java/com/winlator/inputcontrols/ControlElement.java
@@ -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;
}
diff --git a/app/src/main/java/com/winlator/inputcontrols/ControlsProfile.java b/app/src/main/java/com/winlator/inputcontrols/ControlsProfile.java
index b450fd6..18a8950 100644
--- a/app/src/main/java/com/winlator/inputcontrols/ControlsProfile.java
+++ b/app/src/main/java/com/winlator/inputcontrols/ControlsProfile.java
@@ -26,7 +26,9 @@ public class ControlsProfile implements Comparable {
private final List 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 {
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 {
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 {
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 {
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;
diff --git a/app/src/main/java/com/winlator/inputcontrols/ExternalController.java b/app/src/main/java/com/winlator/inputcontrols/ExternalController.java
index 51c465d..e776cee 100644
--- a/app/src/main/java/com/winlator/inputcontrols/ExternalController.java
+++ b/app/src/main/java/com/winlator/inputcontrols/ExternalController.java
@@ -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 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 getControllers() {
ArrayList 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;
+ }
}
}
diff --git a/app/src/main/java/com/winlator/inputcontrols/ExternalControllerBinding.java b/app/src/main/java/com/winlator/inputcontrols/ExternalControllerBinding.java
index d111ef3..8e46fe1 100644
--- a/app/src/main/java/com/winlator/inputcontrols/ExternalControllerBinding.java
+++ b/app/src/main/java/com/winlator/inputcontrols/ExternalControllerBinding.java
@@ -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("_", " ");
}
diff --git a/app/src/main/java/com/winlator/inputcontrols/GamepadState.java b/app/src/main/java/com/winlator/inputcontrols/GamepadState.java
new file mode 100644
index 0000000..057bc85
--- /dev/null
+++ b/app/src/main/java/com/winlator/inputcontrols/GamepadState.java
@@ -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< 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);
}
}
}
diff --git a/app/src/main/java/com/winlator/widget/NumberPicker.java b/app/src/main/java/com/winlator/widget/NumberPicker.java
new file mode 100644
index 0000000..b4067d6
--- /dev/null
+++ b/app/src/main/java/com/winlator/widget/NumberPicker.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/winlator/winhandler/OnGetProcessInfoListener.java b/app/src/main/java/com/winlator/winhandler/OnGetProcessInfoListener.java
new file mode 100644
index 0000000..f668f3f
--- /dev/null
+++ b/app/src/main/java/com/winlator/winhandler/OnGetProcessInfoListener.java
@@ -0,0 +1,5 @@
+package com.winlator.winhandler;
+
+public interface OnGetProcessInfoListener {
+ void onGetProcessInfo(int index, int count, ProcessInfo processInfo);
+}
diff --git a/app/src/main/java/com/winlator/winhandler/RequestCodes.java b/app/src/main/java/com/winlator/winhandler/RequestCodes.java
index 5dabb48..78ce604 100644
--- a/app/src/main/java/com/winlator/winhandler/RequestCodes.java
+++ b/app/src/main/java/com/winlator/winhandler/RequestCodes.java
@@ -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;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/winlator/winhandler/TaskManagerDialog.java b/app/src/main/java/com/winlator/winhandler/TaskManagerDialog.java
index bc21432..2028e17 100644
--- a/app/src/main/java/com/winlator/winhandler/TaskManagerDialog.java
+++ b/app/src/main/java/com/winlator/winhandler/TaskManagerDialog.java
@@ -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);
+ }
+ }
+ });
+ }
}
diff --git a/app/src/main/java/com/winlator/winhandler/WinHandler.java b/app/src/main/java/com/winlator/winhandler/WinHandler.java
index db1c234..e9caf02 100644
--- a/app/src/main/java/com/winlator/winhandler/WinHandler.java
+++ b/app/src/main/java/com/winlator/winhandler/WinHandler.java
@@ -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 actions = new ArrayDeque<>();
+ private boolean initReceived = false;
+ private boolean running = false;
+ private OnGetProcessInfoListener onGetProcessInfoListener;
+ private ExternalController currentController;
+ private final ArrayDeque 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> 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 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;
+ }
}
diff --git a/app/src/main/java/com/winlator/xenvironment/ImageFs.java b/app/src/main/java/com/winlator/xenvironment/ImageFs.java
index 5626a2f..3163f28 100644
--- a/app/src/main/java/com/winlator/xenvironment/ImageFs.java
+++ b/app/src/main/java/com/winlator/xenvironment/ImageFs.java
@@ -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() {
diff --git a/app/src/main/java/com/winlator/xenvironment/components/GuestProgramLauncherComponent.java b/app/src/main/java/com/winlator/xenvironment/components/GuestProgramLauncherComponent.java
index f22e2f7..b42a646 100644
--- a/app/src/main/java/com/winlator/xenvironment/components/GuestProgramLauncherComponent.java
+++ b/app/src/main/java/com/winlator/xenvironment/components/GuestProgramLauncherComponent.java
@@ -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";
diff --git a/app/src/main/java/com/winlator/xserver/Keyboard.java b/app/src/main/java/com/winlator/xserver/Keyboard.java
index 5c0b2df..a021991 100644
--- a/app/src/main/java/com/winlator/xserver/Keyboard.java
+++ b/app/src/main/java/com/winlator/xserver/Keyboard.java
@@ -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();
diff --git a/app/src/main/res/drawable-hdpi/number_picker_decrement.png b/app/src/main/res/drawable-hdpi/number_picker_decrement.png
new file mode 100644
index 0000000..78f1e21
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/number_picker_decrement.png differ
diff --git a/app/src/main/res/drawable-hdpi/number_picker_increment.png b/app/src/main/res/drawable-hdpi/number_picker_increment.png
new file mode 100644
index 0000000..13bbe36
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/number_picker_increment.png differ
diff --git a/app/src/main/res/drawable/number_picker_background.xml b/app/src/main/res/drawable/number_picker_background.xml
new file mode 100644
index 0000000..bb69356
--- /dev/null
+++ b/app/src/main/res/drawable/number_picker_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/control_element_settings.xml b/app/src/main/res/layout/control_element_settings.xml
index 473caf7..d5e29d4 100644
--- a/app/src/main/res/layout/control_element_settings.xml
+++ b/app/src/main/res/layout/control_element_settings.xml
@@ -1,8 +1,8 @@
-
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+
+
+
+ android:hint="@string/none"
+ android:inputType="textCapCharacters" />
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml
index 01a600d..5177801 100644
--- a/app/src/main/res/layout/settings_fragment.xml
+++ b/app/src/main/res/layout/settings_fragment.xml
@@ -204,11 +204,12 @@
android:layout_height="wrap_content"
android:min="10"
android:max="200" />
+
diff --git a/app/src/main/res/layout/shortcut_settings_dialog.xml b/app/src/main/res/layout/shortcut_settings_dialog.xml
index 85afa57..ff0adea 100644
--- a/app/src/main/res/layout/shortcut_settings_dialog.xml
+++ b/app/src/main/res/layout/shortcut_settings_dialog.xml
@@ -255,6 +255,17 @@
android:layout_marginTop="8dp">
+
+
+
+
-
-
- Keyboard
- Mouse
+ - Gamepad
- Original WineD3D
@@ -49,11 +50,15 @@
- 0.2.2 (old)
- - 0.2.5 (new)
+ - 0.2.6 (new)
- 23.1.6 (a6xx)
- 23.3.0 (a7xx)
- 24.0.0 (a7xx)
+
+ - Standard (Old Gamepads)
+ - XInput
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..d69ea4a
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e033e63..26443c4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -62,7 +62,6 @@
Value
Processor Affinity
Cursor Speed
- Cursor Visible
Lock Cursor
Toggle Switch
Profile exported to
@@ -144,7 +143,7 @@
Letter
Target Path
XServer
- Use DRI3 extension (Experimental)
+ Use DRI3 extension
Width
Height
old
@@ -174,7 +173,6 @@
Performance (Unstable)
Preset
You cannot remove this preset
- Use single CPU
WineD3D is a translation layer for DirectX 1-11 to OpenGL.
DXVK is a translation layer for Direct3D 9-11 to Vulkan.
@@ -207,4 +205,6 @@
Defines the max allowed forward value when building Block
Optimization of CALL/RET opcodes
Defines whether or not Dynarec will wait for the FillBlock to be ready
+ Columns
+ DirectInput Mapper Type
\ No newline at end of file