Update app

This commit is contained in:
brunodev85 committed 2024-01-24 11:18:17 -03:00
1 parent 440956199e
commit e6dd7a5976
45 files changed
+1168 -415

No files matched your search

+2 -2
View File
@@ -8,8 +8,8 @@ android {
applicationId 'com.winlator' applicationId 'com.winlator'
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 28 targetSdkVersion 28
versionCode 9 versionCode 10
versionName "3.2" versionName "4.0"
} }
buildTypes { buildTypes {
+2 -1
View File
@@ -44,6 +44,7 @@
<activity android:name="com.winlator.ExternalControllerBindingsActivity" <activity android:name="com.winlator.ExternalControllerBindingsActivity"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:exported="false" /> android:exported="false"
android:screenOrientation="sensor" />
</application> </application>
</manifest> </manifest>
+1
View File
@@ -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_SAFEFLAGS", "values" : ["0", "1", "2"], "defaultValue" : "2"},
{"name" : "BOX64_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, {"name" : "BOX64_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
{"name" : "BOX64_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, {"name" : "BOX64_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
+1
View File
@@ -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_SAFEFLAGS", "values" : ["0", "1", "2"], "defaultValue" : "2"},
{"name" : "BOX86_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, {"name" : "BOX86_DYNAREC_FASTNAN", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
{"name" : "BOX86_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, {"name" : "BOX86_DYNAREC_FASTROUND", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"},
@@ -0,0 +1 @@
{"id":3,"name":"Virtual Gamepad","cursorSpeed":1,"elements":[{"type":"D_PAD","shape":"CIRCLE","bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.85,"x":0.21568627655506134,"y":0.35555556416511536,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.8133170008659363,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.8721405267715454,"y":0.6000000238418579,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.8721405267715454,"y":0.8666666746139526,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.9309640526771545,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1,"x":0.9215686321258545,"y":0.2888889014720917,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1,"x":0.9215686321258545,"y":0.42222222685813904,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1,"x":0.0784313753247261,"y":0.42222222685813904,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"RECT","bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1,"x":0.0784313753247261,"y":0.2888889014720917,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.85,"x":0.538807213306427,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.85,"x":0.46078431606292725,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1,"x":0.10784313827753067,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1,"x":0.7843137383460999,"y":0.35555556416511536,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.85,"x":0.21568627655506134,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.85,"x":0.7843137383460999,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0}]}
Binary file not shown.
@@ -58,7 +58,6 @@ public class ContainerDetailFragment extends Fragment {
private PreloaderDialog preloaderDialog; private PreloaderDialog preloaderDialog;
private JSONArray gpuNames; private JSONArray gpuNames;
private Callback<String> openDirectoryCallback; private Callback<String> openDirectoryCallback;
private final String[] defaultDLLOverrides = {"d3d8", "d3d9", "d3d10", "d3d10_1", "d3d10core", "d3d11", "d3d12", "d3d12core", "ddraw", "dxgi", "wined3d"};
public ContainerDetailFragment() { public ContainerDetailFragment() {
this(0); this(0);
@@ -276,9 +275,6 @@ public class ContainerDetailFragment extends Fragment {
registryEditor.setStringValue("Software\\Wine\\Direct3D", "shader_backend", "glsl"); registryEditor.setStringValue("Software\\Wine\\Direct3D", "shader_backend", "glsl");
registryEditor.setStringValue("Software\\Wine\\Direct3D", "UseGLSL", "enabled"); registryEditor.setStringValue("Software\\Wine\\Direct3D", "UseGLSL", "enabled");
final String dllOverridesKey = "Software\\Wine\\DllOverrides";
for (String name : defaultDLLOverrides) registryEditor.setStringValue(dllOverridesKey, name, "native,builtin");
} }
} }
@@ -28,6 +28,7 @@ import com.winlator.core.AppUtils;
import com.winlator.core.FileUtils; import com.winlator.core.FileUtils;
import com.winlator.core.UnitUtils; import com.winlator.core.UnitUtils;
import com.winlator.widget.InputControlsView; import com.winlator.widget.InputControlsView;
import com.winlator.widget.NumberPicker;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -117,6 +118,14 @@ public class ControlsEditorActivity extends AppCompatActivity implements View.On
inputControlsView.invalidate(); 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); final TextView tvScale = view.findViewById(R.id.TVScale);
SeekBar sbScale = view.findViewById(R.id.SBScale); SeekBar sbScale = view.findViewById(R.id.SBScale);
sbScale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 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); final Spinner sBinding = view.findViewById(R.id.SBinding);
Runnable update = () -> { 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)); sBinding.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, bindingEntries));
AppUtils.setSpinnerSelectionFromValue(sBinding, element.getBindingAt(index).toString()); AppUtils.setSpinnerSelectionFromValue(sBinding, element.getBindingAt(index).toString());
}; };
@@ -243,13 +264,34 @@ public class ControlsEditorActivity extends AppCompatActivity implements View.On
@Override @Override
public void onNothingSelected(AdapterView<?> parent) {} 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() { sBinding.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
boolean isKeyboard = sBindingType.getSelectedItemPosition() == 0; Binding binding = Binding.NONE;
Binding binding = isKeyboard ? Binding.keyboardBindingValues()[position] : Binding.mouseBindingValues()[position]; 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)) { if (binding != element.getBindingAt(index)) {
element.setBindingAt(index, binding); element.setBindingAt(index, binding);
profile.save(); profile.save();
@@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
@@ -76,6 +75,7 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
private void updateControllerBinding(int keyCode, Binding binding) { private void updateControllerBinding(int keyCode, Binding binding) {
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) return; if (keyCode == KeyEvent.KEYCODE_UNKNOWN) return;
ExternalControllerBinding controllerBinding = controller.getControllerBinding(keyCode); ExternalControllerBinding controllerBinding = controller.getControllerBinding(keyCode);
int position; int position;
if (controllerBinding == null) { if (controllerBinding == null) {
@@ -93,28 +93,29 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
recyclerView.scrollToPosition(position); recyclerView.scrollToPosition(position);
} }
private void processJoystickInput(MotionEvent event, int historyPos) { private void processJoystickInput() {
int keyCode = KeyEvent.KEYCODE_UNKNOWN; int keyCode = KeyEvent.KEYCODE_UNKNOWN;
byte sign;
Binding binding = Binding.NONE; 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}; 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) { final float[] values = {controller.state.thumbLX, controller.state.thumbLY, controller.state.thumbRX, controller.state.thumbRY, controller.state.getDPadX(), controller.state.getDPadY()};
if ((sign = Mathf.sign(ExternalController.getCenteredAxis(event, axis, historyPos))) != 0) {
if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Z) { 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; 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; 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; 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; binding = sign > 0 ? Binding.KEY_S : Binding.KEY_W;
} }
keyCode = ExternalControllerBinding.getKeyCodeForAxis(axis, sign); keyCode = ExternalControllerBinding.getKeyCodeForAxis(axes[i], sign);
break; break;
} }
} }
@@ -122,41 +123,24 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
updateControllerBinding(keyCode, binding); 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 @Override
public boolean onGenericMotionEvent(MotionEvent event) { public boolean dispatchGenericMotionEvent(MotionEvent event) {
if (event.getDeviceId() == controller.getDeviceId()) { if (event.getDeviceId() == controller.getDeviceId() && controller.updateStateFromMotionEvent(event)) {
if (ExternalController.isDPadDevice(event)) { if (controller.state.isPressed(ExternalController.IDX_BUTTON_L2)) updateControllerBinding(KeyEvent.KEYCODE_BUTTON_L2, Binding.NONE);
processTriggerButton(event); if (controller.state.isPressed(ExternalController.IDX_BUTTON_R2)) updateControllerBinding(KeyEvent.KEYCODE_BUTTON_R2, Binding.NONE);
processJoystickInput(event, -1); processJoystickInput();
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);
return true; 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 @Override
@@ -208,7 +192,19 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
final Context $this = ExternalControllerBindingsActivity.this; final Context $this = ExternalControllerBindingsActivity.this;
Runnable update = () -> { 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)); holder.binding.setAdapter(new ArrayAdapter<>($this, android.R.layout.simple_spinner_dropdown_item, bindingEntries));
AppUtils.setSpinnerSelectionFromValue(holder.binding, item.getBinding().toString()); AppUtils.setSpinnerSelectionFromValue(holder.binding, item.getBinding().toString());
}; };
@@ -227,8 +223,19 @@ public class ExternalControllerBindingsActivity extends AppCompatActivity {
holder.binding.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { holder.binding.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
boolean isKeyboard = holder.bindingType.getSelectedItemPosition() == 0; Binding binding = Binding.NONE;
Binding binding = isKeyboard ? Binding.keyboardBindingValues()[position] : Binding.mouseBindingValues()[position]; 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()) { if (binding != item.getBinding()) {
item.setBinding(binding); item.setBinding(binding);
profile.save(); profile.save();
@@ -26,10 +26,10 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import com.winlator.contentdialog.ContentDialog;
import com.winlator.core.Callback; import com.winlator.core.Callback;
import com.winlator.core.OBBImageInstaller; import com.winlator.core.OBBImageInstaller;
import com.winlator.core.PreloaderDialog; import com.winlator.core.PreloaderDialog;
import com.winlator.contentdialog.ContentDialog;
import com.winlator.xenvironment.ImageFs; import com.winlator.xenvironment.ImageFs;
import java.util.List; import java.util.List;
@@ -53,7 +53,7 @@ import java.util.concurrent.Executors;
public class SettingsFragment extends Fragment { public class SettingsFragment extends Fragment {
public static final String DEFAULT_BOX86_VERSION = "0.3.0"; public static final String DEFAULT_BOX86_VERSION = "0.3.0";
public static final String DEFAULT_BOX64_VERSION = "0.2.5"; public static final String DEFAULT_BOX64_VERSION = "0.2.6";
private Callback<Uri> selectWineFileCallback; private Callback<Uri> selectWineFileCallback;
private PreloaderDialog preloaderDialog; private PreloaderDialog preloaderDialog;
private SharedPreferences preferences; private SharedPreferences preferences;
@@ -96,17 +96,27 @@ public class SettingsFragment extends Fragment {
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
final Spinner sBox86Version = view.findViewById(R.id.SBox86Version); 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); 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 sBox86Preset = view.findViewById(R.id.SBox86Preset);
final Spinner sBox64Preset = view.findViewById(R.id.SBox64Preset); final Spinner sBox64Preset = view.findViewById(R.id.SBox64Preset);
loadBox86_64PresetSpinners(view, sBox86Preset, sBox64Preset); loadBox86_64PresetSpinners(view, sBox86Preset, sBox64Preset);
final Spinner sTurnipVersion = view.findViewById(R.id.STurnipVersion); 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); final CheckBox cbUseDRI3 = view.findViewById(R.id.CBUseDRI3);
cbUseDRI3.setChecked(preferences.getBoolean("use_dri3", true)); cbUseDRI3.setChecked(preferences.getBoolean("use_dri3", true));
@@ -351,4 +361,14 @@ public class SettingsFragment extends Fragment {
}); });
dialog.show(); dialog.show();
} }
public static void resetBox86_64Version(AppCompatActivity activity) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("box86_version", DEFAULT_BOX86_VERSION);
editor.putString("box64_version", DEFAULT_BOX64_VERSION);
editor.remove("current_box86_version");
editor.remove("current_box64_version");
editor.apply();
}
} }
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CheckBox; import android.widget.CheckBox;
@@ -27,6 +28,7 @@ import com.winlator.core.AppUtils;
import com.winlator.core.CursorLocker; import com.winlator.core.CursorLocker;
import com.winlator.core.EnvVars; import com.winlator.core.EnvVars;
import com.winlator.core.FileUtils; import com.winlator.core.FileUtils;
import com.winlator.core.OBBImageInstaller;
import com.winlator.core.OnExtractFileListener; import com.winlator.core.OnExtractFileListener;
import com.winlator.core.PreloaderDialog; import com.winlator.core.PreloaderDialog;
import com.winlator.core.ProcessHelper; import com.winlator.core.ProcessHelper;
@@ -89,7 +91,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
private boolean firstTimeBoot = false; private boolean firstTimeBoot = false;
private SharedPreferences preferences; private SharedPreferences preferences;
private OnExtractFileListener onExtractFileListener; private OnExtractFileListener onExtractFileListener;
private final WinHandler winHandler = new WinHandler(); private final WinHandler winHandler = new WinHandler(this);
private float globalCursorSpeed = 1.0f; private float globalCursorSpeed = 1.0f;
@Override @Override
@@ -136,6 +138,9 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
graphicsDriver = shortcut.getExtra("graphicsDriver", container.getGraphicsDriver()); graphicsDriver = shortcut.getExtra("graphicsDriver", container.getGraphicsDriver());
audioDriver = shortcut.getExtra("audioDriver", container.getAudioDriver()); audioDriver = shortcut.getExtra("audioDriver", container.getAudioDriver());
screenSize = shortcut.getExtra("screenSize", container.getScreenSize()); screenSize = shortcut.getExtra("screenSize", container.getScreenSize());
String dinputMapperType = shortcut.getExtra("dinputMapperType");
if (!dinputMapperType.isEmpty()) winHandler.setDInputMapperType(Byte.parseByte(dinputMapperType));
} }
if (!wineInfo.isWin64()) { if (!wineInfo.isWin64()) {
@@ -199,6 +204,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
@Override @Override
protected void onDestroy() { protected void onDestroy() {
winHandler.stop();
if (environment != null) environment.stopEnvironmentComponents(); if (environment != null) environment.stopEnvironmentComponents();
super.onDestroy(); super.onDestroy();
} }
@@ -243,6 +249,7 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
} }
private void exit() { private void exit() {
winHandler.stop();
if (environment != null) environment.stopEnvironmentComponents(); if (environment != null) environment.stopEnvironmentComponents();
AppUtils.restartApplication(this); AppUtils.restartApplication(this);
} }
@@ -257,9 +264,10 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
File pulseaudioDir = new File(getFilesDir(), "pulseaudio"); File pulseaudioDir = new File(getFilesDir(), "pulseaudio");
TarZstdUtils.extract(this, "patches.tzst", rootDir, onExtractFileListener); TarZstdUtils.extract(this, "patches.tzst", rootDir, onExtractFileListener);
TarZstdUtils.extract(this, "pulseaudio.tzst", pulseaudioDir); TarZstdUtils.extract(this, "pulseaudio.tzst", pulseaudioDir);
WineUtils.applyRegistryKeyTweaks(this); WineUtils.applyRegistryTweaks(this);
container.putExtra("appVersion", appVersion); container.putExtra("appVersion", appVersion);
container.putExtra("imgVersion", imgVersion); container.putExtra("imgVersion", imgVersion);
SettingsFragment.resetBox86_64Version(this);
containerDataChanged = true; containerDataChanged = true;
} }
@@ -288,9 +296,11 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
envVars.put("MESA_DEBUG", "silent"); envVars.put("MESA_DEBUG", "silent");
envVars.put("MESA_NO_ERROR", "1"); envVars.put("MESA_NO_ERROR", "1");
envVars.put("WINEPREFIX", ImageFs.WINEPREFIX); envVars.put("WINEPREFIX", ImageFs.WINEPREFIX);
envVars.put("WINEESYNC", "1");
if (MainActivity.DEBUG_LEVEL <= 1) envVars.put("WINEDEBUG", "-all"); if (MainActivity.DEBUG_LEVEL <= 1) envVars.put("WINEDEBUG", "-all");
String rootPath = imageFs.getRootDir().getPath(); String rootPath = imageFs.getRootDir().getPath();
FileUtils.clear(imageFs.getTmpDir());
GuestProgramLauncherComponent guestProgramLauncherComponent = new GuestProgramLauncherComponent(); GuestProgramLauncherComponent guestProgramLauncherComponent = new GuestProgramLauncherComponent();
@@ -399,10 +409,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
}; };
loadProfileSpinner.run(); 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); final CheckBox cbLockCursor = dialog.findViewById(R.id.CBLockCursor);
cbLockCursor.setChecked(xServer.cursorLocker.getState() == CursorLocker.State.LOCKED); cbLockCursor.setChecked(xServer.cursorLocker.getState() == CursorLocker.State.LOCKED);
@@ -423,7 +429,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
}); });
dialog.setOnConfirmCallback(() -> { dialog.setOnConfirmCallback(() -> {
renderer.setCursorVisible(cbCursorVisible.isChecked());
xServer.cursorLocker.setState(cbLockCursor.isChecked() ? CursorLocker.State.LOCKED : CursorLocker.State.CONFINED); xServer.cursorLocker.setState(cbLockCursor.isChecked() ? CursorLocker.State.LOCKED : CursorLocker.State.CONFINED);
inputControlsView.setShowTouchscreenControls(cbShowTouchscreenControls.isChecked()); inputControlsView.setShowTouchscreenControls(cbShowTouchscreenControls.isChecked());
int position = sProfile.getSelectedItemPosition(); int position = sProfile.getSelectedItemPosition();
@@ -510,12 +515,21 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
dialog.show(); dialog.show();
} }
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
return !winHandler.onGenericMotionEvent(event) && super.dispatchGenericMotionEvent(event);
}
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { 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)); (!ExternalController.isGameController(event.getDevice()) && super.dispatchKeyEvent(event));
} }
public InputControlsView getInputControlsView() {
return inputControlsView;
}
private void generateWineprefix() { private void generateWineprefix() {
Intent intent = getIntent(); Intent intent = getIntent();
@@ -731,7 +745,6 @@ public class XServerDisplayActivity extends AppCompatActivity implements Navigat
String args = ""; String args = "";
if (shortcut != null) { if (shortcut != null) {
if (shortcut.getExtra("singleCPU", "0").equals("1")) args += "/affinity "+ProcessHelper.getSingleCPUAffinityMask()+" ";
String execArgs = shortcut.getExtra("execArgs"); String execArgs = shortcut.getExtra("execArgs");
execArgs = !execArgs.isEmpty() ? " "+execArgs : ""; execArgs = !execArgs.isEmpty() ? " "+execArgs : "";
@@ -19,7 +19,7 @@ public class Container {
public static final String DEFAULT_GRAPHICS_DRIVER = "turnip-zink"; public static final String DEFAULT_GRAPHICS_DRIVER = "turnip-zink";
public static final String DEFAULT_AUDIO_DRIVER = "alsa"; public static final String DEFAULT_AUDIO_DRIVER = "alsa";
public static final String DEFAULT_DXWRAPPER = "original-wined3d"; 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 String DEFAULT_DRIVES = "D:"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
public static final byte MAX_DRIVE_LETTERS = 8; public static final byte MAX_DRIVE_LETTERS = 8;
public final int id; public final int id;
@@ -1,6 +1,5 @@
package com.winlator.contentdialog; package com.winlator.contentdialog;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -24,6 +23,7 @@ import com.winlator.core.AppUtils;
import com.winlator.core.ArrayUtils; import com.winlator.core.ArrayUtils;
import com.winlator.core.EnvVars; import com.winlator.core.EnvVars;
import com.winlator.core.StringUtils; import com.winlator.core.StringUtils;
import com.winlator.winhandler.WinHandler;
import java.io.File; 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)); sAudioDriver.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_spinner_dropdown_item, audioDriverEntries));
AppUtils.setSpinnerSelectionFromIdentifier(sAudioDriver, shortcut.getExtra("audioDriver")); 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); final CheckBox cbForceFullscreen = findViewById(R.id.CBForceFullscreen);
cbForceFullscreen.setChecked(shortcut.getExtra("forceFullscreen", "0").equals("1")); cbForceFullscreen.setChecked(shortcut.getExtra("forceFullscreen", "0").equals("1"));
@@ -87,6 +84,9 @@ public class ShortcutSettingsDialog extends ContentDialog {
final Spinner sBox64Preset = findViewById(R.id.SBox64Preset); final Spinner sBox64Preset = findViewById(R.id.SBox64Preset);
Box86_64PresetManager.loadSpinner("box64", sBox64Preset, shortcut.getExtra("box64Preset", shortcut.container.getBox64Preset())); 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(); createDXComponentsTab();
createEnvVarsTab(); createEnvVarsTab();
@@ -118,7 +118,6 @@ public class ShortcutSettingsDialog extends ContentDialog {
shortcut.putExtra("dxwrapper", sDXWrapper.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sDXWrapper.getSelectedItem()) : null); shortcut.putExtra("dxwrapper", sDXWrapper.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sDXWrapper.getSelectedItem()) : null);
shortcut.putExtra("audioDriver", sAudioDriver.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sAudioDriver.getSelectedItem()) : null); shortcut.putExtra("audioDriver", sAudioDriver.getSelectedItemPosition() > 0 ? StringUtils.parseIdentifier(sAudioDriver.getSelectedItem()) : null);
shortcut.putExtra("forceFullscreen", cbForceFullscreen.isChecked() ? "1" : null); shortcut.putExtra("forceFullscreen", cbForceFullscreen.isChecked() ? "1" : null);
shortcut.putExtra("singleCPU", cbSingleCPU.isChecked() ? "1" : null);
shortcut.putExtra("dxcomponents", getDXComponents()); shortcut.putExtra("dxcomponents", getDXComponents());
shortcut.putExtra("envVars", getEnvVars()); 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("box86Preset", !box86Preset.equals(shortcut.container.getBox86Preset()) ? box86Preset : null);
shortcut.putExtra("box64Preset", !box64Preset.equals(shortcut.container.getBox64Preset()) ? box64Preset : 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(); shortcut.saveData();
} }
}); });
@@ -2,14 +2,12 @@ package com.winlator.core;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import androidx.preference.PreferenceManager;
import com.winlator.MainActivity; import com.winlator.MainActivity;
import com.winlator.R; import com.winlator.R;
import com.winlator.SettingsFragment;
import com.winlator.contentdialog.ContentDialog; import com.winlator.contentdialog.ContentDialog;
import com.winlator.xenvironment.ImageFs; import com.winlator.xenvironment.ImageFs;
@@ -39,11 +37,7 @@ public abstract class OBBImageInstaller {
ImageFs imageFs = ImageFs.find(activity); ImageFs imageFs = ImageFs.find(activity);
final File rootDir = imageFs.getRootDir(); final File rootDir = imageFs.getRootDir();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); SettingsFragment.resetBox86_64Version(activity);
SharedPreferences.Editor editor = preferences.edit();
editor.remove("current_box86_version");
editor.remove("current_box64_version");
editor.apply();
if (foundVersion <= 0) foundVersion = imageFs.isValid() ? imageFs.getVersion() + 1 : 1; if (foundVersion <= 0) foundVersion = imageFs.isValid() ? imageFs.getVersion() + 1 : 1;
final int finalVersion = foundVersion; final int finalVersion = foundVersion;
@@ -131,12 +131,6 @@ public abstract class ProcessHelper {
return result.toArray(new String[0]); 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) { public static String getAffinityMask(String cpuList) {
String[] values = cpuList.split(","); String[] values = cpuList.split(",");
int affinityMask = 0; int affinityMask = 0;
@@ -11,14 +11,45 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
public abstract class TarXZUtils { 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) { public static boolean extract(Context context, Uri source, File destination) {
if (source == null) return false; 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)) { ArchiveInputStream tar = new TarArchiveInputStream(inStream)) {
TarArchiveEntry entry; TarArchiveEntry entry;
while ((entry = (TarArchiveEntry)tar.getNextEntry()) != null) { while ((entry = (TarArchiveEntry)tar.getNextEntry()) != null) {
@@ -83,17 +83,20 @@ public abstract class WineUtils {
return; return;
} }
File wineBin = new File(binDir, "wine"); File wineBin32 = new File(binDir, "wine");
if (!wineBin.isFile()) { File wineBin64 = new File(binDir, "wine64");
if (!wineBin32.isFile()) {
callback.call(null); callback.call(null);
return; 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); ImageFs imageFs = ImageFs.find(context);
File rootDir = ImageFs.find(context).getRootDir(); 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(); final String winePath = wineDir.getPath();
try { try {
@@ -140,14 +143,26 @@ public abstract class WineUtils {
return wineInfos; return wineInfos;
} }
public static void applyRegistryKeyTweaks(Context context) { public static void applyRegistryTweaks(Context context) {
File rootDir = ImageFs.find(context).getRootDir(); File rootDir = ImageFs.find(context).getRootDir();
File systemRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/system.reg"); File systemRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/system.reg");
File userRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/user.reg");
try (WineRegistryEditor registryEditor = new WineRegistryEditor(systemRegFile)) { try (WineRegistryEditor registryEditor = new WineRegistryEditor(systemRegFile)) {
registryEditor.setStringValue("Software\\Classes\\.reg", null, "REGfile"); registryEditor.setStringValue("Software\\Classes\\.reg", null, "REGfile");
registryEditor.setStringValue("Software\\Classes\\.reg", "Content Type", "application/reg"); 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.setStringValue("Software\\Classes\\REGfile\\Shell\\Open\\command", null, "C:\\windows\\regedit.exe /C \"%1\"");
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "DisableHidraw", 1);
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "DisableInput", 1);
registryEditor.setDwordValue("System\\CurrentControlSet\\Services\\winebus", "Enable SDL", 0);
}
final String[] dllOverrides = {"d3d8", "d3d9", "d3d10", "d3d10_1", "d3d10core", "d3d11", "d3d12", "d3d12core", "ddraw", "dxgi", "wined3d", "dinput", "dinput8", "xinput1_1", "xinput1_2", "xinput1_3", "xinput1_4", "xinput9_1_0", "xinputuap"};
final String dllOverridesKey = "Software\\Wine\\DllOverrides";
try (WineRegistryEditor registryEditor = new WineRegistryEditor(userRegFile)) {
for (String name : dllOverrides) registryEditor.setStringValue(dllOverridesKey, name, "native,builtin");
} }
} }
@@ -8,7 +8,7 @@ import com.winlator.xserver.XKeycode;
import java.util.ArrayList; import java.util.ArrayList;
public enum Binding { 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; public final XKeycode keycode;
Binding() { Binding() {
@@ -59,7 +59,7 @@ public enum Binding {
case KEY_KP_ADD: case KEY_KP_ADD:
return "+"; return "+";
default: 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; return name().startsWith("KEY_") || this == NONE;
} }
public boolean isGamepad() {
return name().startsWith("GAMEPAD_");
}
public static String[] mouseBindingLabels() { public static String[] mouseBindingLabels() {
ArrayList<String> names = new ArrayList<>(); ArrayList<String> names = new ArrayList<>();
for (Binding binding : values()) if (binding.isMouse()) names.add(binding.toString()); for (Binding binding : values()) if (binding.isMouse()) names.add(binding.toString());
@@ -113,6 +117,12 @@ public enum Binding {
return labels.toArray(new String[0]); return labels.toArray(new String[0]);
} }
public static String[] gamepadBindingLabels() {
ArrayList<String> names = new ArrayList<>();
for (Binding binding : values()) if (binding.isGamepad()) names.add(binding.toString());
return names.toArray(new String[0]);
}
public static Binding[] mouseBindingValues() { public static Binding[] mouseBindingValues() {
ArrayList<Binding> labels = new ArrayList<>(); ArrayList<Binding> labels = new ArrayList<>();
for (Binding binding : values()) if (binding.isMouse()) labels.add(binding); 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); for (Binding binding : values()) if (binding.isKeyboard()) values.add(binding);
return values.toArray(new Binding[0]); return values.toArray(new Binding[0]);
} }
public static Binding[] gamepadBindingValues() {
ArrayList<Binding> labels = new ArrayList<>();
for (Binding binding : values()) if (binding.isGamepad()) labels.add(binding);
return labels.toArray(new Binding[0]);
}
} }
@@ -7,7 +7,6 @@ import android.graphics.Path;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import androidx.annotation.IntRange;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
import com.winlator.math.Mathf; import com.winlator.math.Mathf;
@@ -17,7 +16,12 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.Arrays;
public class ControlElement { 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 { public enum Type {
BUTTON, D_PAD, RANGE_BUTTON, STICK; BUTTON, D_PAD, RANGE_BUTTON, STICK;
@@ -56,7 +60,7 @@ public class ControlElement {
private final InputControlsView inputControlsView; private final InputControlsView inputControlsView;
private Type type = Type.BUTTON; private Type type = Type.BUTTON;
private Shape shape = Shape.CIRCLE; 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 float scale = 1.0f;
private short x; private short x;
private short y; private short y;
@@ -64,7 +68,7 @@ public class ControlElement {
private boolean toggleSwitch = false; private boolean toggleSwitch = false;
private int currentPointerId = -1; private int currentPointerId = -1;
private final Rect boundingBox = new Rect(); private final Rect boundingBox = new Rect();
private final boolean[] states = new boolean[4]; private boolean[] states = new boolean[4];
private boolean boundingBoxNeedsUpdate = true; private boolean boundingBoxNeedsUpdate = true;
private String text = ""; private String text = "";
private byte iconId; private byte iconId;
@@ -79,7 +83,7 @@ public class ControlElement {
} }
private void reset() { 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) { if (type == Type.D_PAD || type == Type.STICK) {
bindings[0] = Binding.KEY_W; bindings[0] = Binding.KEY_W;
bindings[1] = Binding.KEY_D; bindings[1] = Binding.KEY_D;
@@ -102,6 +106,17 @@ public class ControlElement {
reset(); 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() { public Shape getShape() {
return shape; return shape;
} }
@@ -136,12 +151,19 @@ public class ControlElement {
this.toggleSwitch = toggleSwitch; this.toggleSwitch = toggleSwitch;
} }
public Binding getBindingAt(@IntRange(from = 0, to = 4) int index) { public Binding getBindingAt(int index) {
return bindings[index]; return index < bindings.length ? bindings[index] : Binding.NONE;
} }
public void setBindingAt(@IntRange(from = 0, to = 4) int index, Binding binding) { public void setBindingAt(int index, Binding binding) {
this.bindings[index] = 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() { public float getScale() {
@@ -223,24 +245,27 @@ public class ControlElement {
break; break;
} }
break; break;
case D_PAD: case D_PAD: {
halfWidth = snappingSize * 7; halfWidth = snappingSize * 7;
halfHeight = snappingSize * 7; halfHeight = snappingSize * 7;
break; break;
case STICK: }
case STICK: {
halfWidth = snappingSize * 6; halfWidth = snappingSize * 6;
halfHeight = snappingSize * 6; halfHeight = snappingSize * 6;
break; break;
case RANGE_BUTTON: }
if (orientation == 0) { case RANGE_BUTTON: {
halfWidth = snappingSize * 12; halfWidth = snappingSize * ((bindings.length * 4 + 8) / 2);
halfHeight = (int)(snappingSize * 1.5f); halfHeight = snappingSize * 2;
}
else { if (orientation == 1) {
halfWidth = (int)(snappingSize * 1.5f); int tmp = halfWidth;
halfHeight = snappingSize * 12; halfWidth = halfHeight;
halfHeight = tmp;
} }
break; break;
}
} }
halfWidth *= scale; halfWidth *= scale;
@@ -256,7 +281,7 @@ public class ControlElement {
} }
else { else {
Binding binding = getBindingAt(0); Binding binding = getBindingAt(0);
String text = binding.toString().replace("NUMPAD ", "NP"); String text = binding.toString().replace("NUMPAD ", "NP").replace("BUTTON ", "");
if (text.length() > 7) { if (text.length() > 7) {
String[] parts = text.split(" "); String[] parts = text.split(" ");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -326,7 +351,8 @@ public class ControlElement {
if (iconId > 0) { if (iconId > 0) {
drawIcon(canvas, cx, cy, boundingBox.width(), boundingBox.height(), iconId); drawIcon(canvas, cx, cy, boundingBox.width(), boundingBox.height(), iconId);
} else { }
else {
String text = getDisplayText(); String text = getDisplayText();
paint.setTextSize(Math.min(getTextSizeForWidth(paint, text, boundingBox.width() - strokeWidth * 2), snappingSize * 2 * scale)); paint.setTextSize(Math.min(getTextSizeForWidth(paint, text, boundingBox.width() - strokeWidth * 2), snappingSize * 2 * scale));
paint.setTextAlign(Paint.Align.CENTER); paint.setTextAlign(Paint.Align.CENTER);
@@ -379,9 +405,9 @@ public class ControlElement {
case RANGE_BUTTON: { case RANGE_BUTTON: {
Range range = getRange(); Range range = getRange();
int oldColor = paint.getColor(); 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 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; float minTextSize = snappingSize * 2 * scale;
if (orientation == 0) { if (orientation == 0) {
@@ -391,7 +417,7 @@ public class ControlElement {
float startX = boundingBox.left; float startX = boundingBox.left;
canvas.drawRoundRect(startX, boundingBox.top, boundingBox.right, boundingBox.bottom, radius, radius, paint); 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; startX += elementSize;
for (byte i = 0; i < bindings.length; i++) { for (byte i = 0; i < bindings.length; i++) {
@@ -412,7 +438,7 @@ public class ControlElement {
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setColor(oldColor); paint.setColor(oldColor);
canvas.drawLine(startX, lineTop, startX, lineBottom, paint); 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 { else {
float lineLeft = boundingBox.left + strokeWidth * 0.5f; float lineLeft = boundingBox.left + strokeWidth * 0.5f;
@@ -421,7 +447,7 @@ public class ControlElement {
float startY = boundingBox.top; float startY = boundingBox.top;
canvas.drawRoundRect(boundingBox.left, startY, boundingBox.right, boundingBox.bottom, radius, radius, paint); 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; startY += elementSize;
for (byte i = 0; i < bindings.length; i++) { for (byte i = 0; i < bindings.length; i++) {
@@ -442,7 +468,7 @@ public class ControlElement {
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setColor(oldColor); paint.setColor(oldColor);
canvas.drawLine(lineLeft, startY, lineRight, startY, paint); 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; break;
} }
@@ -536,7 +562,7 @@ public class ControlElement {
break; break;
} }
bindings[0] = bindings[1] = bindings[2] = bindings[3] = Binding.NONE; Arrays.fill(bindings, Binding.NONE);
bindings[currentElementIndex-1] = binding; bindings[currentElementIndex-1] = binding;
states[currentElementIndex-1] = true; states[currentElementIndex-1] = true;
inputControlsView.handleInputEvent(binding, true); inputControlsView.handleInputEvent(binding, true);
@@ -551,62 +577,51 @@ public class ControlElement {
public boolean handleTouchMove(int pointerId, float x, float y) { public boolean handleTouchMove(int pointerId, float x, float y) {
if (pointerId == currentPointerId && (type == ControlElement.Type.D_PAD || type == Type.STICK)) { if (pointerId == currentPointerId && (type == ControlElement.Type.D_PAD || type == Type.STICK)) {
Rect boundingBox = getBoundingBox(); Rect boundingBox = getBoundingBox();
x -= boundingBox.left;
y -= boundingBox.top;
float radius = boundingBox.width() * 0.5f; 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) { if (distance > radius * radius) {
float angle = (float)Math.atan2(y - radius, x - radius); float angle = (float)Math.atan2(offsetY, offsetX);
x = (float)((Math.cos(angle) * radius) + radius); offsetX = (float)(Math.cos(angle) * radius);
y = (float)((Math.sin(angle) * radius) + radius); offsetY = (float)(Math.sin(angle) * radius);
} }
float invWidth = 1.0f / boundingBox.width(); float deltaX = Mathf.clamp(offsetX / radius, -1, 1);
float deltaX = Mathf.clamp(-1 + 2 * ((radius - x) + radius) * invWidth, -1, 1) * -1; float deltaY = Mathf.clamp(offsetY / radius, -1, 1);
float deltaY = Mathf.clamp(-1 + 2 * ((radius - y) + radius) * invWidth, -1, 1) * -1;
if (type == Type.STICK) { if (type == Type.STICK) {
if (thumbstickPosition == null) thumbstickPosition = new PointF(); if (thumbstickPosition == null) thumbstickPosition = new PointF();
thumbstickPosition.x = boundingBox.left + deltaX * radius + radius; thumbstickPosition.x = boundingBox.left + deltaX * radius + radius;
thumbstickPosition.y = boundingBox.top + deltaY * 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++) { for (byte i = 0; i < 4; i++) {
float value = i == 1 || i == 3 ? deltaX : deltaY; float value = i == 1 || i == 3 ? deltaX : deltaY;
if (states[i]) { 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); inputControlsView.handleInputEvent(getBindingAt(i), true, value);
this.states[i] = true; this.states[i] = true;
}
else {
inputControlsView.handleInputEvent(getBindingAt(i), false, value);
this.states[i] = false;
}
} }
inputControlsView.invalidate(); inputControlsView.invalidate();
} }
else { else {
boolean[] states = { final boolean[] states = {deltaY <= -DPAD_DEAD_ZONE, deltaX >= DPAD_DEAD_ZONE, deltaY >= DPAD_DEAD_ZONE, deltaX <= -DPAD_DEAD_ZONE};
deltaY <= -InputControlsView.DPAD_DEAD_ZONE,
deltaX >= InputControlsView.DPAD_DEAD_ZONE,
deltaY >= InputControlsView.DPAD_DEAD_ZONE,
deltaX <= -InputControlsView.DPAD_DEAD_ZONE
};
for (byte i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
if (states[i] && !this.states[i]) { Binding binding = getBindingAt(i);
inputControlsView.handleInputEvent(getBindingAt(i), true); 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; this.states[i] = true;
} }
else if (!states[i] && this.states[i]) { else if (!states[i] && this.states[i]) {
inputControlsView.handleInputEvent(getBindingAt(i), false); inputControlsView.handleInputEvent(binding, false);
this.states[i] = 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) { 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); if (states[i]) inputControlsView.handleInputEvent(getBindingAt(i), false);
states[i] = false; states[i] = false;
} }
@@ -26,7 +26,9 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
private final List<ControlElement> immutableElements = Collections.unmodifiableList(elements); private final List<ControlElement> immutableElements = Collections.unmodifiableList(elements);
private boolean elementsLoaded = false; private boolean elementsLoaded = false;
private boolean controllersLoaded = false; private boolean controllersLoaded = false;
private boolean virtualGamepad = false;
private final Context context; private final Context context;
private GamepadState gamepadState;
public ControlsProfile(Context context, int id) { public ControlsProfile(Context context, int id) {
this.context = context; this.context = context;
@@ -49,9 +51,19 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
this.cursorSpeed = cursorSpeed; this.cursorSpeed = cursorSpeed;
} }
public boolean isVirtualGamepad() {
return virtualGamepad;
}
public GamepadState getGamepadState() {
if (gamepadState == null) gamepadState = new GamepadState();
return gamepadState;
}
public ExternalController addController(String id) { public ExternalController addController(String id) {
ExternalController controller = getController(id); ExternalController controller = getController(id);
if (controller == null) controllers.add(controller = ExternalController.getController(id)); if (controller == null) controllers.add(controller = ExternalController.getController(id));
controllersLoaded = true;
return controller; return controller;
} }
@@ -95,6 +107,7 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
data.put("id", id); data.put("id", id);
data.put("name", name); data.put("name", name);
data.put("cursorSpeed", Float.valueOf(cursorSpeed)); data.put("cursorSpeed", Float.valueOf(cursorSpeed));
if (virtualGamepad) data.put("virtualGamepad", true);
JSONArray elementsJSONArray = new JSONArray(); JSONArray elementsJSONArray = new JSONArray();
if (!elementsLoaded && file.isFile()) { if (!elementsLoaded && file.isFile()) {
@@ -183,8 +196,9 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
public void loadElements(InputControlsView inputControlsView) { public void loadElements(InputControlsView inputControlsView) {
elements.clear(); elements.clear();
elementsLoaded = false; elementsLoaded = false;
virtualGamepad = false;
File file = getProfileFile(inputControlsView.getContext(), id); File file = getProfileFile(context, id);
if (!file.isFile()) return; if (!file.isFile()) return;
try { try {
@@ -204,10 +218,15 @@ public class ControlsProfile implements Comparable<ControlsProfile> {
if (elementJSONObject.has("range")) element.setRange(ControlElement.Range.valueOf(elementJSONObject.getString("range"))); if (elementJSONObject.has("range")) element.setRange(ControlElement.Range.valueOf(elementJSONObject.getString("range")));
if (elementJSONObject.has("orientation")) element.setOrientation((byte)elementJSONObject.getInt("orientation")); if (elementJSONObject.has("orientation")) element.setOrientation((byte)elementJSONObject.getInt("orientation"));
boolean hasGamepadBinding = true;
JSONArray bindingsJSONArray = elementJSONObject.getJSONArray("bindings"); JSONArray bindingsJSONArray = elementJSONObject.getJSONArray("bindings");
for (int j = 0; j < bindingsJSONArray.length(); j++) { for (int j = 0; j < bindingsJSONArray.length(); j++) {
Binding binding = Binding.fromString(bindingsJSONArray.getString(j));
element.setBindingAt(j, 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); elements.add(element);
} }
elementsLoaded = true; elementsLoaded = true;
@@ -1,6 +1,7 @@
package com.winlator.inputcontrols; package com.winlator.inputcontrols;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -12,10 +13,23 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
public class ExternalController { 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 name;
private String id; private String id;
private int deviceId = -1; private int deviceId = -1;
private final ArrayList<ExternalControllerBinding> controllerBindings = new ArrayList<>(); private final ArrayList<ExternalControllerBinding> controllerBindings = new ArrayList<>();
public final GamepadState state = new GamepadState();
public String getName() { public String getName() {
return name; return name;
@@ -104,11 +118,70 @@ public class ExternalController {
return obj instanceof ExternalController ? ((ExternalController)obj).id.equals(this.id) : super.equals(obj); return obj instanceof ExternalController ? ((ExternalController)obj).id.equals(this.id) : super.equals(obj);
} }
private void processJoystickInput(MotionEvent event, int historyPos) {
state.thumbLX = getCenteredAxis(event, MotionEvent.AXIS_X, historyPos);
state.thumbLY = getCenteredAxis(event, MotionEvent.AXIS_Y, historyPos);
state.thumbRX = getCenteredAxis(event, MotionEvent.AXIS_Z, historyPos);
state.thumbRY = getCenteredAxis(event, MotionEvent.AXIS_RZ, historyPos);
if (historyPos == -1) {
float axisX = getCenteredAxis(event, MotionEvent.AXIS_HAT_X, historyPos);
float axisY = getCenteredAxis(event, MotionEvent.AXIS_HAT_Y, historyPos);
state.dpad[0] = axisY == -1.0f && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
state.dpad[1] = axisX == 1.0f && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
state.dpad[2] = axisY == 1.0f && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
state.dpad[3] = axisX == -1.0f && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
}
}
private void processTriggerButton(MotionEvent event) {
state.setPressed(IDX_BUTTON_L2, event.getAxisValue(MotionEvent.AXIS_LTRIGGER) == 1.0f || event.getAxisValue(MotionEvent.AXIS_BRAKE) == 1.0f);
state.setPressed(IDX_BUTTON_R2, event.getAxisValue(MotionEvent.AXIS_RTRIGGER) == 1.0f || event.getAxisValue(MotionEvent.AXIS_GAS) == 1.0f);
}
public boolean updateStateFromMotionEvent(MotionEvent event) {
if (isJoystickDevice(event)) {
processTriggerButton(event);
int historySize = event.getHistorySize();
for (int i = 0; i < historySize; i++) processJoystickInput(event, i);
processJoystickInput(event, -1);
return true;
}
return false;
}
public boolean updateStateFromKeyEvent(KeyEvent event) {
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
int keyCode = event.getKeyCode();
int buttonIdx = getButtonIdxByKeyCode(keyCode);
if (buttonIdx != -1) {
state.setPressed(buttonIdx, pressed);
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
state.dpad[0] = pressed && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
state.dpad[1] = pressed && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
state.dpad[2] = pressed && Math.abs(state.thumbLY) < ControlElement.STICK_DEAD_ZONE;
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
state.dpad[3] = pressed && Math.abs(state.thumbLX) < ControlElement.STICK_DEAD_ZONE;
return true;
}
return false;
}
public static ArrayList<ExternalController> getControllers() { public static ArrayList<ExternalController> getControllers() {
ArrayList<ExternalController> controllers = new ArrayList<>(); ArrayList<ExternalController> controllers = new ArrayList<>();
for (int deviceId : InputDevice.getDeviceIds()) { for (int deviceId : InputDevice.getDeviceIds()) {
InputDevice device = InputDevice.getDevice(deviceId); InputDevice device = InputDevice.getDevice(deviceId);
if (device != null && !device.isVirtual() && isGameController(device)) { if (isGameController(device)) {
ExternalController controller = new ExternalController(); ExternalController controller = new ExternalController();
controller.setId(device.getDescriptor()); controller.setId(device.getDescriptor());
controller.setName(device.getName()); controller.setName(device.getName());
@@ -123,19 +196,42 @@ public class ExternalController {
return null; 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) { public static boolean isGameController(InputDevice device) {
if (device == null) return false;
int sources = device.getSources(); int sources = device.getSources();
return (sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD || return !device.isVirtual() && ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD ||
(sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK; (sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
} }
public static float getCenteredAxis(MotionEvent event, int axis, int historyPos) { public static float getCenteredAxis(MotionEvent event, int axis, int historyPos) {
InputDevice device = event.getDevice(); if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) {
InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource()); float value = event.getAxisValue(axis);
if (range != null) { if (Math.abs(value) == 1.0f) return value;
float flat = range.getFlat(); }
float value = historyPos < 0 ? event.getAxisValue(axis) : event.getHistoricalAxisValue(axis, historyPos); else {
if (Math.abs(value) > flat) return value; 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; return 0;
} }
@@ -144,7 +240,65 @@ public class ExternalController {
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE; return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE;
} }
public static boolean isDPadDevice(MotionEvent event) { public static int getButtonIdxByKeyCode(int keyCode) {
return (event.getSource() & InputDevice.SOURCE_DPAD) != InputDevice.SOURCE_DPAD && event.getAction() == MotionEvent.ACTION_MOVE; switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_A:
return IDX_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_B:
return IDX_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_X:
return IDX_BUTTON_X;
case KeyEvent.KEYCODE_BUTTON_Y:
return IDX_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_L1:
return IDX_BUTTON_L1;
case KeyEvent.KEYCODE_BUTTON_R1:
return IDX_BUTTON_R1;
case KeyEvent.KEYCODE_BUTTON_SELECT:
return IDX_BUTTON_SELECT;
case KeyEvent.KEYCODE_BUTTON_START:
return IDX_BUTTON_START;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
return IDX_BUTTON_L3;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
return IDX_BUTTON_R3;
case KeyEvent.KEYCODE_BUTTON_L2:
return IDX_BUTTON_L2;
case KeyEvent.KEYCODE_BUTTON_R2:
return IDX_BUTTON_R2;
default:
return -1;
}
}
public static int getButtonIdxByName(String name) {
switch (name) {
case "A":
return IDX_BUTTON_A;
case "B":
return IDX_BUTTON_B;
case "X":
return IDX_BUTTON_X;
case "Y":
return IDX_BUTTON_Y;
case "L1":
return IDX_BUTTON_L1;
case "R1":
return IDX_BUTTON_R1;
case "SELECT":
return IDX_BUTTON_SELECT;
case "START":
return IDX_BUTTON_START;
case "L3":
return IDX_BUTTON_L3;
case "R3":
return IDX_BUTTON_R3;
case "L2":
return IDX_BUTTON_L2;
case "R2":
return IDX_BUTTON_R2;
default:
return -1;
}
} }
} }
@@ -17,8 +17,6 @@ public class ExternalControllerBinding {
public static final byte AXIS_Z_POSITIVE = -6; public static final byte AXIS_Z_POSITIVE = -6;
public static final byte AXIS_RZ_NEGATIVE = -7; public static final byte AXIS_RZ_NEGATIVE = -7;
public static final byte AXIS_RZ_POSITIVE = -8; 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 short keyCode;
private Binding binding = Binding.NONE; private Binding binding = Binding.NONE;
@@ -70,10 +68,6 @@ public class ExternalControllerBinding {
return "AXIS RZ-"; return "AXIS RZ-";
case AXIS_RZ_POSITIVE: case AXIS_RZ_POSITIVE:
return "AXIS RZ+"; return "AXIS RZ+";
case AXIS_LTRIGGER:
return "L TRIGGER";
case AXIS_RTRIGGER:
return "R TRIGGER";
default: default:
return KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", "").replace("_", " "); return KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", "").replace("_", " ");
} }
@@ -0,0 +1,61 @@
package com.winlator.inputcontrols;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class GamepadState {
public float thumbLX = 0;
public float thumbLY = 0;
public float thumbRX = 0;
public float thumbRY = 0;
public final boolean[] dpad = new boolean[4];
public short buttons = 0;
public byte getPovHat() {
byte povHat = -1;
if (dpad[0] && dpad[1]) povHat = 1;
else if (dpad[1] && dpad[2]) povHat = 3;
else if (dpad[2] && dpad[3]) povHat = 5;
else if (dpad[3] && dpad[0]) povHat = 7;
else if (dpad[0]) povHat = 0;
else if (dpad[1]) povHat = 2;
else if (dpad[2]) povHat = 4;
else if (dpad[3]) povHat = 6;
return povHat;
}
public void writeTo(ByteBuffer buffer) {
buffer.putShort(buttons);
buffer.put(getPovHat());
buffer.putShort((short)(thumbLX * Short.MAX_VALUE));
buffer.putShort((short)(thumbLY * Short.MAX_VALUE));
buffer.putShort((short)(thumbRX * Short.MAX_VALUE));
buffer.putShort((short)(thumbRY * Short.MAX_VALUE));
}
public byte[] toByteArray() {
ByteBuffer buffer = ByteBuffer.allocate(11).order(ByteOrder.LITTLE_ENDIAN);
writeTo(buffer);
return buffer.array();
}
public void setPressed(int buttonIdx, boolean pressed) {
int flag = 1<<buttonIdx;
if (pressed) {
buttons |= flag;
}
else buttons &= ~flag;
}
public boolean isPressed(int buttonIdx) {
return (buttons & (1<<buttonIdx)) != 0;
}
public byte getDPadX() {
return (byte)(dpad[1] ? 1 : (dpad[3] ? -1 : 0));
}
public byte getDPadY() {
return (byte)(dpad[0] ? -1 : (dpad[2] ? 1 : 0));
}
}
@@ -23,6 +23,7 @@ import com.winlator.inputcontrols.ControlElement;
import com.winlator.inputcontrols.ControlsProfile; import com.winlator.inputcontrols.ControlsProfile;
import com.winlator.inputcontrols.ExternalController; import com.winlator.inputcontrols.ExternalController;
import com.winlator.inputcontrols.ExternalControllerBinding; import com.winlator.inputcontrols.ExternalControllerBinding;
import com.winlator.inputcontrols.GamepadState;
import com.winlator.math.Mathf; import com.winlator.math.Mathf;
import com.winlator.xserver.Pointer; import com.winlator.xserver.Pointer;
import com.winlator.xserver.XServer; import com.winlator.xserver.XServer;
@@ -34,8 +35,6 @@ import java.util.TimerTask;
public class InputControlsView extends View { public class InputControlsView extends View {
public static final float DEFAULT_OVERLAY_OPACITY = 0.4f; public static final float DEFAULT_OVERLAY_OPACITY = 0.4f;
public static final float JOYSTICK_DEAD_ZONE = 0.15f;
public static final float DPAD_DEAD_ZONE = 0.3f;
private boolean editMode = false; private boolean editMode = false;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path path = new Path(); private final Path path = new Path();
@@ -241,6 +240,10 @@ public class InputControlsView extends View {
this.touchpadView = touchpadView; this.touchpadView = touchpadView;
} }
public XServer getXServer() {
return xServer;
}
public void setXServer(XServer xServer) { public void setXServer(XServer xServer) {
this.xServer = xServer; this.xServer = xServer;
createMouseMoveTimer(); createMouseMoveTimer();
@@ -267,58 +270,41 @@ public class InputControlsView extends View {
} }
} }
private void processJoystickInput(MotionEvent event, ExternalController controller, int historyPos) { private void processJoystickInput(ExternalController controller) {
ExternalControllerBinding controllerBinding; ExternalControllerBinding controllerBinding;
final int[] axes = {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y}; 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) { final float[] values = {controller.state.thumbLX, controller.state.thumbLY, controller.state.thumbRX, controller.state.thumbRY, controller.state.getDPadX(), controller.state.getDPadY()};
float value = ExternalController.getCenteredAxis(event, axis, historyPos);
if (Math.abs(value) > JOYSTICK_DEAD_ZONE) { for (byte i = 0; i < axes.length; i++) {
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, Mathf.sign(value))); if (Math.abs(values[i]) > ControlElement.STICK_DEAD_ZONE) {
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), true, value); controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], Mathf.sign(values[i])));
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), true, values[i]);
} }
else { else {
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, (byte) 1)); controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], (byte) 1));
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, value); if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, values[i]);
controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axis, (byte)-1)); controllerBinding = controller.getControllerBinding(ExternalControllerBinding.getKeyCodeForAxis(axes[i], (byte)-1));
if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), false, value); 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 @Override
public boolean onGenericMotionEvent(MotionEvent event) { public boolean onGenericMotionEvent(MotionEvent event) {
if (!editMode && profile != null) { if (!editMode && profile != null) {
ExternalController controller = profile.getController(event.getDeviceId()); ExternalController controller = profile.getController(event.getDeviceId());
if (controller != null) { if (controller != null && controller.updateStateFromMotionEvent(event)) {
if (ExternalController.isDPadDevice(event)) { ExternalControllerBinding controllerBinding;
processTriggerButton(event, controller); controllerBinding = controller.getControllerBinding(KeyEvent.KEYCODE_BUTTON_L2);
processJoystickInput(event, controller, -1); if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), controller.state.isPressed(ExternalController.IDX_BUTTON_L2));
return true;
} controllerBinding = controller.getControllerBinding(KeyEvent.KEYCODE_BUTTON_R2);
else if (ExternalController.isJoystickDevice(event)) { if (controllerBinding != null) handleInputEvent(controllerBinding.getBinding(), controller.state.isPressed(ExternalController.IDX_BUTTON_R2));
int historySize = event.getHistorySize();
for (int i = 0; i < historySize; i++) processJoystickInput(event, controller, i); processJoystickInput(controller);
processJoystickInput(event, controller, -1); return true;
return true;
}
} }
} }
return super.onGenericMotionEvent(event); return super.onGenericMotionEvent(event);
} }
@@ -395,9 +381,6 @@ public class InputControlsView extends View {
} }
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_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: case MotionEvent.ACTION_CANCEL:
for (ControlElement element : profile.getElements()) if (element.handleTouchUp(pointerId)) handled = true; for (ControlElement element : profile.getElements()) if (element.handleTouchUp(pointerId)) handled = true;
if (!handled) touchpadView.onTouchEvent(event); if (!handled) touchpadView.onTouchEvent(event);
@@ -433,36 +416,54 @@ public class InputControlsView extends View {
} }
public void handleInputEvent(Binding binding, boolean isActionDown, float offset) { public void handleInputEvent(Binding binding, boolean isActionDown, float offset) {
if (isActionDown) { if (binding.isGamepad()) {
if (binding == Binding.MOUSE_MOVE_LEFT || binding == Binding.MOUSE_MOVE_RIGHT) { GamepadState state = profile.getGamepadState();
mouseMoveOffset.x = offset != 0 ? offset : (binding == Binding.MOUSE_MOVE_LEFT ? -1 : 1);
createMouseMoveTimer(); 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) { else if (binding == Binding.GAMEPAD_LEFT_THUMB_UP || binding == Binding.GAMEPAD_LEFT_THUMB_DOWN) {
mouseMoveOffset.y = offset != 0 ? offset : (binding == Binding.MOUSE_MOVE_UP ? -1 : 1); state.thumbLY = isActionDown ? offset : 0;
createMouseMoveTimer();
} }
else { else if (binding == Binding.GAMEPAD_LEFT_THUMB_LEFT || binding == Binding.GAMEPAD_LEFT_THUMB_RIGHT) {
Pointer.Button pointerButton = binding.getPointerButton(); state.thumbLX = isActionDown ? offset : 0;
if (pointerButton != null) { }
xServer.injectPointerButtonPress(pointerButton); else if (binding == Binding.GAMEPAD_RIGHT_THUMB_UP || binding == Binding.GAMEPAD_RIGHT_THUMB_DOWN) {
} state.thumbRY = isActionDown ? offset : 0;
else xServer.injectKeyPress(binding.keycode); }
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 { else {
if (binding == Binding.MOUSE_MOVE_LEFT || binding == Binding.MOUSE_MOVE_RIGHT) { 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) { else if (binding == Binding.MOUSE_MOVE_DOWN || binding == Binding.MOUSE_MOVE_UP) {
if (Math.abs(offset) < InputControlsView.JOYSTICK_DEAD_ZONE) mouseMoveOffset.y = 0; mouseMoveOffset.y = isActionDown ? offset : 0;
createMouseMoveTimer();
} }
else { else {
Pointer.Button pointerButton = binding.getPointerButton(); Pointer.Button pointerButton = binding.getPointerButton();
if (pointerButton != null) { if (isActionDown) {
xServer.injectPointerButtonRelease(pointerButton); if (pointerButton != null) {
xServer.injectPointerButtonPress(pointerButton);
}
else xServer.injectKeyPress(binding.keycode);
}
else {
if (pointerButton != null) {
xServer.injectPointerButtonRelease(pointerButton);
}
else xServer.injectKeyRelease(binding.keycode);
} }
else xServer.injectKeyRelease(binding.keycode);
} }
} }
} }
@@ -0,0 +1,127 @@
package com.winlator.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
import com.winlator.R;
import com.winlator.math.Mathf;
public class NumberPicker extends FrameLayout implements View.OnTouchListener {
private int value = 0;
private int minValue = 0;
private int maxValue = 100;
private int step = 1;
private final EditText editText;
private OnValueChangeListener onValueChangeListener;
public interface OnValueChangeListener {
void onValueChange(NumberPicker numberPicker, int value);
}
public NumberPicker(Context context) {
this(context, null);
}
public NumberPicker(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.number_picker, this, true);
editText = findViewById(R.id.EditText);
findViewById(R.id.BTDecrement).setOnTouchListener(this);
findViewById(R.id.BTIncrement).setOnTouchListener(this);
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumberPicker);
minValue = ta.getInt(R.styleable.NumberPicker_minValue, minValue);
maxValue = ta.getInt(R.styleable.NumberPicker_maxValue, maxValue);
setStep(ta.getInt(R.styleable.NumberPicker_step, step));
int value = ta.getInt(R.styleable.NumberPicker_value, 0);
ta.recycle();
setValue(value);
}
else setStep(step);
}
public void setValue(int value) {
this.value = Mathf.clamp(value, minValue, maxValue);
editText.setText(String.valueOf(this.value));
}
public int getValue() {
return value;
}
public int getMinValue() {
return minValue;
}
public void setMinValue(int minValue) {
this.minValue = minValue;
}
public int getMaxValue() {
return maxValue;
}
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
public void increment() {
setValue(value+step);
}
public void decrement() {
setValue(value-step);
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
public OnValueChangeListener getOnValueChangeListener() {
return onValueChangeListener;
}
public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener) {
this.onValueChangeListener = onValueChangeListener;
}
private void onButtonClick(View v) {
if (!isEnabled()) return;
int id = v.getId();
if (id == R.id.BTIncrement) {
increment();
}
else if (id == R.id.BTDecrement) {
decrement();
}
if (onValueChangeListener != null) {
onValueChangeListener.onValueChange(NumberPicker.this, value);
}
}
@Override
public boolean onTouch(final View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
onButtonClick(v);
}
return true;
}
}
@@ -0,0 +1,5 @@
package com.winlator.winhandler;
public interface OnGetProcessInfoListener {
void onGetProcessInfo(int index, int count, ProcessInfo processInfo);
}
@@ -2,9 +2,14 @@ package com.winlator.winhandler;
abstract class RequestCodes { abstract class RequestCodes {
public static final byte EXIT = 0; public static final byte EXIT = 0;
public static final byte EXEC = 1; public static final byte INIT = 1;
public static final byte KILL_PROCESS = 2; public static final byte EXEC = 2;
public static final byte GET_PROCESSES = 3; public static final byte KILL_PROCESS = 3;
public static final byte SET_PROCESS_AFFINITY = 4; public static final byte LIST_PROCESSES = 4;
public static final byte MOUSE_EVENT = 5; public static final byte GET_PROCESS = 5;
} public static final byte SET_PROCESS_AFFINITY = 6;
public static final byte MOUSE_EVENT = 7;
public static final byte GET_GAMEPAD = 8;
public static final byte GET_GAMEPAD_STATE = 9;
public static final byte RELEASE_GAMEPAD = 10;
}
@@ -19,10 +19,11 @@ import com.winlator.contentdialog.ContentDialog;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
public class TaskManagerDialog extends ContentDialog { public class TaskManagerDialog extends ContentDialog implements OnGetProcessInfoListener {
private final XServerDisplayActivity activity; private final XServerDisplayActivity activity;
private final LayoutInflater inflater; private final LayoutInflater inflater;
private Timer timer; private Timer timer;
private final Object lock = new Object();
public TaskManagerDialog(XServerDisplayActivity activity) { public TaskManagerDialog(XServerDisplayActivity activity) {
super(activity, R.layout.task_manager_dialog); super(activity, R.layout.task_manager_dialog);
@@ -43,32 +44,17 @@ public class TaskManagerDialog extends ContentDialog {
timer.cancel(); timer.cancel();
timer = null; timer = null;
} }
activity.getWinHandler().setOnGetProcessInfoListener(null);
}); });
inflater = LayoutInflater.from(activity); inflater = LayoutInflater.from(activity);
} }
private void update() { private void update() {
WinHandler winHandler = activity.getWinHandler(); synchronized (lock) {
winHandler.getProcesses((processes) -> activity.runOnUiThread(() -> { activity.getWinHandler().listProcesses();
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);
}));
LinearLayout tableHead = findViewById(R.id.LLTableHead); LinearLayout tableHead = findViewById(R.id.LLTableHead);
TextView tvMemoryInfo = (TextView)tableHead.getChildAt(2); TextView tvMemoryInfo = (TextView)tableHead.getChildAt(2);
@@ -120,13 +106,45 @@ public class TaskManagerDialog extends ContentDialog {
@Override @Override
public void show() { public void show() {
update(); update();
activity.getWinHandler().setOnGetProcessInfoListener(this);
timer = new Timer(); timer = new Timer();
timer.schedule(new TimerTask() { timer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
update(); activity.runOnUiThread(TaskManagerDialog.this::update);
} }
}, 0, 1000); }, 0, 1000);
super.show(); super.show();
} }
@Override
public void onGetProcessInfo(int index, int numProcesses, ProcessInfo processInfo) {
activity.runOnUiThread(() -> {
synchronized (lock) {
final LinearLayout container = findViewById(R.id.LLProcessList);
setBottomBarText(activity.getString(R.string.processes)+": " + numProcesses);
if (numProcesses == 0) {
container.removeAllViews();
findViewById(R.id.TVEmptyText).setVisibility(View.VISIBLE);
return;
}
findViewById(R.id.TVEmptyText).setVisibility(View.GONE);
int childCount = container.getChildCount();
View itemView = index < childCount ? container.getChildAt(index) : inflater.inflate(R.layout.process_info_list_item, container, false);
((TextView)itemView.findViewById(R.id.TVName)).setText(processInfo.name);
((TextView)itemView.findViewById(R.id.TVPID)).setText(String.valueOf(processInfo.pid));
((TextView)itemView.findViewById(R.id.TVMemoryUsage)).setText(processInfo.getFormattedMemoryUsage());
itemView.findViewById(R.id.BTMenu).setOnClickListener((v) -> showListItemMenu(v, processInfo));
if (index >= childCount) container.addView(itemView);
if (index == numProcesses-1 && childCount > numProcesses) {
for (int i = numProcesses; i < childCount; i++) container.removeViewAt(i);
}
}
});
}
} }
@@ -1,35 +1,55 @@
package com.winlator.winhandler; 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.core.StringUtils;
import com.winlator.inputcontrols.ControlsProfile;
import com.winlator.inputcontrols.ExternalController;
import com.winlator.inputcontrols.GamepadState;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.DatagramPacket;
import java.io.OutputStream; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class WinHandler { public class WinHandler {
private static final short DEFAULT_PORT = 7946; private static final short SERVER_PORT = 7947;
private Socket socket; private static final short CLIENT_PORT = 7946;
private final Object lock = new Object(); public static final byte DINPUT_MAPPER_TYPE_STANDARD = 0;
private ByteBuffer data = ByteBuffer.allocate(128).order(ByteOrder.LITTLE_ENDIAN); public static final byte DINPUT_MAPPER_TYPE_XINPUT = 1;
private DatagramSocket socket;
private final ByteBuffer sendData = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
private final ByteBuffer receiveData = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
private final DatagramPacket sendPacket = new DatagramPacket(sendData.array(), 64);
private final DatagramPacket receivePacket = new DatagramPacket(receiveData.array(), 64);
private final ArrayDeque<Runnable> actions = new ArrayDeque<>(); private final ArrayDeque<Runnable> actions = new ArrayDeque<>();
private boolean initReceived = false;
private boolean running = false;
private OnGetProcessInfoListener onGetProcessInfoListener;
private ExternalController currentController;
private final ArrayDeque<byte[]> gamepadStateQueue = new ArrayDeque<>();
private InetAddress localhost;
private byte dinputMapperType = DINPUT_MAPPER_TYPE_XINPUT;
private final XServerDisplayActivity activity;
private boolean sendData() { public WinHandler(XServerDisplayActivity activity) {
this.activity = activity;
}
private boolean sendPacket(int port) {
try { try {
int size = data.position(); int size = sendData.position();
if (size == 0) return true; if (size == 0) return false;
OutputStream outStream = socket.getOutputStream(); sendPacket.setAddress(localhost);
outStream.write(data.array(), 0, size); sendPacket.setPort(port);
socket.send(sendPacket);
return true; return true;
} }
catch (IOException e) { 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) { public void exec(String command) {
command = command.trim(); synchronized (actions) {
if (command.isEmpty()) return; command = command.trim();
String[] cmdList = command.split(" ", 2); if (command.isEmpty()) return;
final String filename = cmdList[0]; String[] cmdList = command.split(" ", 2);
final String parameters = cmdList.length > 1 ? cmdList[1] : ""; final String filename = cmdList[0];
final String parameters = cmdList.length > 1 ? cmdList[1] : "";
synchronized (lock) {
actions.add(() -> { actions.add(() -> {
byte[] filenameBytes = filename.getBytes(); byte[] filenameBytes = filename.getBytes();
byte[] parametersBytes = parameters.getBytes(); byte[] parametersBytes = parameters.getBytes();
data.rewind(); sendData.rewind();
data.put(RequestCodes.EXEC); sendData.put(RequestCodes.EXEC);
data.putInt(filenameBytes.length + parametersBytes.length + 8); sendData.putInt(filenameBytes.length + parametersBytes.length + 8);
data.putInt(filenameBytes.length); sendData.putInt(filenameBytes.length);
data.putInt(parametersBytes.length); sendData.putInt(parametersBytes.length);
data.put(filenameBytes); sendData.put(filenameBytes);
data.put(parametersBytes); sendData.put(parametersBytes);
sendData(); sendPacket(CLIENT_PORT);
}); });
} }
} }
public void killProcess(final String processName) { public void killProcess(final String processName) {
synchronized (lock) { synchronized (actions) {
actions.add(() -> { actions.add(() -> {
data.rewind(); sendData.rewind();
data.put(RequestCodes.KILL_PROCESS); sendData.put(RequestCodes.KILL_PROCESS);
byte[] bytes = processName.getBytes(); byte[] bytes = processName.getBytes();
data.putInt(bytes.length); sendData.putInt(bytes.length);
data.put(bytes); sendData.put(bytes);
sendData(); sendPacket(CLIENT_PORT);
}); });
} }
} }
public void getProcesses(final Callback<List<ProcessInfo>> callback) { public void listProcesses() {
synchronized (lock) { synchronized (actions) {
actions.add(() -> { actions.add(() -> {
data.rewind(); sendData.rewind();
data.put(RequestCodes.GET_PROCESSES); sendData.put(RequestCodes.LIST_PROCESSES);
data.putInt(0); sendData.putInt(0);
if (sendData() && receiveData(5)) { if (!sendPacket(CLIENT_PORT) && onGetProcessInfoListener != null) {
boolean success = data.get() == 1; onGetProcessInfoListener.onGetProcessInfo(0, 0, null);
int responseLength = data.getInt();
if (success && receiveData(responseLength)) {
ArrayList<ProcessInfo> processes = new ArrayList<>();
while (data.position() < data.limit()) {
int numBytesBefore = data.position();
int pid = data.getInt();
long memoryUsage = data.getLong();
int affinityMask = data.getInt();
byte[] bytes = new byte[32];
data.get(bytes);
String name = StringUtils.fromANSIString(bytes);
processes.add(new ProcessInfo(pid, name, memoryUsage, affinityMask));
int bytesRead = data.position() - numBytesBefore;
data.position(data.position() + (64 - bytesRead));
}
callback.call(processes);
}
else callback.call(Collections.emptyList());
} }
else callback.call(Collections.emptyList());
}); });
} }
} }
public void setProcessAffinity(final int pid, final int affinityMask) { public void setProcessAffinity(final int pid, final int affinityMask) {
synchronized (lock) { synchronized (actions) {
actions.add(() -> { actions.add(() -> {
data.rewind(); sendData.rewind();
data.put(RequestCodes.SET_PROCESS_AFFINITY); sendData.put(RequestCodes.SET_PROCESS_AFFINITY);
data.putInt(8); sendData.putInt(8);
data.putInt(pid); sendData.putInt(pid);
data.putInt(affinityMask); sendData.putInt(affinityMask);
sendData(); sendPacket(CLIENT_PORT);
}); });
} }
} }
public void mouseEvent(int flags, int dx, int dy, int wheelDelta) { public void mouseEvent(int flags, int dx, int dy, int wheelDelta) {
synchronized (lock) { synchronized (actions) {
if (!isConnected()) return; if (!initReceived) return;
actions.add(() -> { actions.add(() -> {
data.rewind(); sendData.rewind();
data.put(RequestCodes.MOUSE_EVENT); sendData.put(RequestCodes.MOUSE_EVENT);
data.putInt(10); sendData.putInt(10);
data.putInt(flags); sendData.putInt(flags);
data.putShort((short)dx); sendData.putShort((short)dx);
data.putShort((short)dy); sendData.putShort((short)dy);
data.putShort((short)wheelDelta); sendData.putShort((short)wheelDelta);
sendData(); sendPacket(CLIENT_PORT);
}); });
} }
} }
public boolean isConnected() { public OnGetProcessInfoListener getOnGetProcessInfoListener() {
return socket != null && !socket.isClosed() && socket.isConnected(); return onGetProcessInfoListener;
} }
private void connect() { public void setOnGetProcessInfoListener(OnGetProcessInfoListener onGetProcessInfoListener) {
InetAddress address = null; synchronized (actions) {
try { this.onGetProcessInfoListener = onGetProcessInfoListener;
address = InetAddress.getLocalHost();
} }
catch (UnknownHostException e) {}
try {
socket = new Socket(address.getHostAddress(), DEFAULT_PORT);
}
catch (IOException e) {}
} }
public void start() { private void startSendThread() {
Executors.newSingleThreadExecutor().execute(() -> { Executors.newSingleThreadExecutor().execute(() -> {
while (true) { while (running) {
synchronized (lock) { synchronized (actions) {
if (!isConnected()) connect(); while (initReceived && !actions.isEmpty()) actions.poll().run();
if (isConnected()) while (!actions.isEmpty()) actions.poll().run();
}
if (!isConnected()) {
try {
Thread.sleep(100);
}
catch (InterruptedException e) {}
} }
} }
}); });
} }
public void stop() {
running = false;
if (socket != null) {
socket.close();
socket = null;
}
}
private void handleRequest(byte requestCode, final int port) {
switch (requestCode) {
case RequestCodes.INIT: {
initReceived = true;
break;
}
case RequestCodes.GET_PROCESS: {
if (onGetProcessInfoListener == null) return;
receiveData.position(receiveData.position() + 4);
int numProcesses = receiveData.getShort();
int index = receiveData.getShort();
int pid = receiveData.getInt();
long memoryUsage = receiveData.getLong();
int affinityMask = receiveData.getInt();
byte[] bytes = new byte[32];
receiveData.get(bytes);
String name = StringUtils.fromANSIString(bytes);
onGetProcessInfoListener.onGetProcessInfo(index, numProcesses, new ProcessInfo(pid, name, memoryUsage, affinityMask));
break;
}
case RequestCodes.GET_GAMEPAD: {
boolean isXInput = receiveData.get() == 1;
final ControlsProfile profile = activity.getInputControlsView().getProfile();
boolean useVirtualGamepad = profile != null && profile.isVirtualGamepad();
if (!useVirtualGamepad && (currentController == null || !currentController.isConnected())) {
releaseCurrentController();
currentController = ExternalController.getController(0);
}
actions.add(() -> {
sendData.rewind();
sendData.put(RequestCodes.GET_GAMEPAD);
if (currentController != null || useVirtualGamepad) {
sendData.putInt(!useVirtualGamepad ? currentController.getDeviceId() : profile.id);
sendData.put(dinputMapperType);
byte[] bytes = (useVirtualGamepad ? profile.getName() : currentController.getName()).getBytes();
sendData.putInt(bytes.length);
sendData.put(bytes);
}
else sendData.putInt(0);
sendPacket(port);
});
break;
}
case RequestCodes.GET_GAMEPAD_STATE: {
int gamepadId = receiveData.getInt();
final ControlsProfile profile = activity.getInputControlsView().getProfile();
boolean useVirtualGamepad = profile != null && profile.isVirtualGamepad();
if (currentController != null && currentController.getDeviceId() != gamepadId) currentController = null;
actions.add(() -> {
sendData.rewind();
sendData.put(RequestCodes.GET_GAMEPAD_STATE);
sendData.put((byte)(currentController != null || useVirtualGamepad ? 1 : 0));
if (currentController != null || useVirtualGamepad) {
sendData.putInt(gamepadId);
synchronized (gamepadStateQueue) {
if (gamepadStateQueue.isEmpty()) {
if (useVirtualGamepad) {
profile.getGamepadState().writeTo(sendData);
}
else currentController.state.writeTo(sendData);
}
else sendData.put(gamepadStateQueue.poll());
}
}
sendPacket(port);
});
break;
}
case RequestCodes.RELEASE_GAMEPAD:
releaseCurrentController();
break;
}
}
public void start() {
try {
localhost = InetAddress.getLocalHost();
}
catch (UnknownHostException e) {}
running = true;
startSendThread();
Executors.newSingleThreadExecutor().execute(() -> {
try {
socket = new DatagramSocket(SERVER_PORT);
while (running) {
socket.receive(receivePacket);
synchronized (actions) {
receiveData.rewind();
byte requestCode = receiveData.get();
handleRequest(requestCode, receivePacket.getPort());
}
}
}
catch (IOException e) {}
});
}
public void saveGamepadState(GamepadState state) {
synchronized (gamepadStateQueue) {
if (gamepadStateQueue.size() > 20) gamepadStateQueue.removeLast();
gamepadStateQueue.add(state.toByteArray());
}
}
private void releaseCurrentController() {
currentController = null;
synchronized (gamepadStateQueue) {
gamepadStateQueue.clear();
}
}
public boolean onGenericMotionEvent(MotionEvent event) {
boolean handled = false;
if (currentController != null && currentController.getDeviceId() == event.getDeviceId()) {
handled = currentController.updateStateFromMotionEvent(event);
}
return handled;
}
public boolean onKeyEvent(KeyEvent event) {
boolean handled = false;
if (currentController != null && currentController.getDeviceId() == event.getDeviceId() && event.getRepeatCount() == 0) {
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
handled = currentController.updateStateFromKeyEvent(event);
}
else if (action == KeyEvent.ACTION_UP) {
handled = currentController.updateStateFromKeyEvent(event);
}
if (handled) saveGamepadState(currentController.state);
}
return handled;
}
public byte getDInputMapperType() {
return dinputMapperType;
}
public void setDInputMapperType(byte dinputMapperType) {
this.dinputMapperType = dinputMapperType;
}
} }
@@ -75,6 +75,10 @@ public class ImageFs {
return new File(rootDir, "/opt/installed-wine"); return new File(rootDir, "/opt/installed-wine");
} }
public File getTmpDir() {
return new File(rootDir, "/tmp");
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
@@ -127,11 +127,15 @@ public class GuestProgramLauncherComponent extends EnvironmentComponent {
envVars.put("ANDROID_SYSVSHM_SERVER", UnixSocketConfig.SYSVSHM_SERVER_PATH); envVars.put("ANDROID_SYSVSHM_SERVER", UnixSocketConfig.SYSVSHM_SERVER_PATH);
if (this.envVars != null) envVars.putAll(this.envVars); if (this.envVars != null) envVars.putAll(this.envVars);
File shmDir = new File(rootDir, "/tmp/shm");
shmDir.mkdirs();
String command = nativeLibraryDir+"/libproot.so"; String command = nativeLibraryDir+"/libproot.so";
command += " --kill-on-exit"; command += " --kill-on-exit";
command += " --rootfs="+rootDir; command += " --rootfs="+rootDir;
command += " --cwd="+ImageFs.HOME_PATH; command += " --cwd="+ImageFs.HOME_PATH;
command += " --bind=/dev"; command += " --bind=/dev";
command += " --bind="+shmDir.getAbsolutePath()+":/dev/shm";
command += " --bind=/proc"; command += " --bind=/proc";
command += " --bind=/sys"; command += " --bind=/sys";
@@ -4,6 +4,8 @@ import android.view.KeyEvent;
import androidx.collection.ArraySet; import androidx.collection.ArraySet;
import com.winlator.inputcontrols.ExternalController;
import java.util.ArrayList; import java.util.ArrayList;
public class Keyboard { public class Keyboard {
@@ -92,6 +94,8 @@ public class Keyboard {
} }
public boolean onKeyEvent(KeyEvent event) { public boolean onKeyEvent(KeyEvent event) {
if (ExternalController.isGameController(event.getDevice())) return false;
int action = event.getAction(); int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN || action == KeyEvent.ACTION_UP) { if (action == KeyEvent.ACTION_DOWN || action == KeyEvent.ACTION_UP) {
int keyCode = event.getKeyCode(); int keyCode = event.getKeyCode();
Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorPrimaryDark" />
<stroke android:width="1dp" android:color="#303030" />
<corners android:radius="4dp" />
</shape>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
@@ -100,6 +100,18 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/vertical" /> android:text="@string/vertical" />
</RadioGroup> </RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/columns" />
<com.winlator.widget.NumberPicker
android:id="@+id/NPColumns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:minValue="3"
app:maxValue="8" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -178,7 +190,8 @@
android:id="@+id/ETCustomText" android:id="@+id/ETCustomText"
android:maxLength="8" android:maxLength="8"
android:layout_width="match_parent" android:layout_width="match_parent"
android:hint="@string/none" /> android:hint="@string/none"
android:inputType="textCapCharacters" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -33,12 +33,6 @@
android:checked="true" android:checked="true"
android:text="@string/show_touchscreen_controls" /> android:text="@string/show_touchscreen_controls" />
<CheckBox
android:id="@+id/CBCursorVisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cursor_visible" />
<CheckBox <CheckBox
android:id="@+id/CBLockCursor" android:id="@+id/CBLockCursor"
android:layout_width="wrap_content" android:layout_width="wrap_content"
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="42dp"
android:gravity="center_vertical"
android:layout_margin="1dp">
<ImageButton
android:id="@+id/BTDecrement"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/number_picker_background"
android:padding="7dp"
android:gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/number_picker_decrement"
app:tint="#ffffff" />
<EditText
style="@style/EditText"
android:id="@+id/EditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:focusable="false"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:focusableInTouchMode="false"
android:inputType="number" />
<ImageButton
android:id="@+id/BTIncrement"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/number_picker_background"
android:padding="7dp"
android:gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/number_picker_increment"
app:tint="#ffffff" />
</LinearLayout>
@@ -204,11 +204,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:min="10" android:min="10"
android:max="200" /> android:max="200" />
<CheckBox <CheckBox
android:id="@+id/CBUseDRI3" android:id="@+id/CBUseDRI3"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/use_dri3_extension_experimental" android:text="@string/use_dri3_extension"
android:layout_marginTop="8dp" /> android:layout_marginTop="8dp" />
</LinearLayout> </LinearLayout>
@@ -255,6 +255,17 @@
android:layout_marginTop="8dp"> android:layout_marginTop="8dp">
<LinearLayout style="@style/FieldSet"> <LinearLayout style="@style/FieldSet">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/directinput_mapper_type" />
<Spinner
style="@style/ComboBox"
android:id="@+id/SDInputMapperType"
android:layout_width="match_parent"
android:entries="@array/dinput_mapper_type_entries" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -278,13 +289,6 @@
android:src="@drawable/icon_menu" /> android:src="@drawable/icon_menu" />
</LinearLayout> </LinearLayout>
<CheckBox
android:id="@+id/CBSingleCPU"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_single_cpu"
android:layout_marginTop="4dp" />
<CheckBox <CheckBox
android:id="@+id/CBForceFullscreen" android:id="@+id/CBForceFullscreen"
android:layout_width="wrap_content" android:layout_width="wrap_content"
+6 -1
View File
@@ -27,6 +27,7 @@
<string-array name="binding_type_entries"> <string-array name="binding_type_entries">
<item>Keyboard</item> <item>Keyboard</item>
<item>Mouse</item> <item>Mouse</item>
<item>Gamepad</item>
</string-array> </string-array>
<string-array name="dxwrapper_entries"> <string-array name="dxwrapper_entries">
<item>Original WineD3D</item> <item>Original WineD3D</item>
@@ -49,11 +50,15 @@
</string-array> </string-array>
<string-array name="box64_version_entries"> <string-array name="box64_version_entries">
<item>0.2.2 (old)</item> <item>0.2.2 (old)</item>
<item>0.2.5 (new)</item> <item>0.2.6 (new)</item>
</string-array> </string-array>
<string-array name="turnip_version_entries"> <string-array name="turnip_version_entries">
<item>23.1.6 (a6xx)</item> <item>23.1.6 (a6xx)</item>
<item>23.3.0 (a7xx)</item> <item>23.3.0 (a7xx)</item>
<item>24.0.0 (a7xx)</item> <item>24.0.0 (a7xx)</item>
</string-array> </string-array>
<string-array name="dinput_mapper_type_entries">
<item>Standard (Old Gamepads)</item>
<item>XInput</item>
</string-array>
</resources> </resources>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberPicker">
<attr name="value" format="integer" />
<attr name="minValue" format="integer" />
<attr name="maxValue" format="integer" />
<attr name="step" format="integer" />
<attr name="textSize" format="dimension" />
</declare-styleable>
</resources>
+3 -3
View File
@@ -62,7 +62,6 @@
<string name="value">Value</string> <string name="value">Value</string>
<string name="processor_affinity">Processor Affinity</string> <string name="processor_affinity">Processor Affinity</string>
<string name="cursor_speed">Cursor Speed</string> <string name="cursor_speed">Cursor Speed</string>
<string name="cursor_visible">Cursor Visible</string>
<string name="lock_cursor">Lock Cursor</string> <string name="lock_cursor">Lock Cursor</string>
<string name="toggle_switch">Toggle Switch</string> <string name="toggle_switch">Toggle Switch</string>
<string name="profile_exported_to">Profile exported to</string> <string name="profile_exported_to">Profile exported to</string>
@@ -144,7 +143,7 @@
<string name="letter">Letter</string> <string name="letter">Letter</string>
<string name="target_path">Target Path</string> <string name="target_path">Target Path</string>
<string name="xserver">XServer</string> <string name="xserver">XServer</string>
<string name="use_dri3_extension_experimental">Use DRI3 extension (Experimental)</string> <string name="use_dri3_extension">Use DRI3 extension</string>
<string name="width">Width</string> <string name="width">Width</string>
<string name="height">Height</string> <string name="height">Height</string>
<string name="old">old</string> <string name="old">old</string>
@@ -174,7 +173,6 @@
<string name="performance_unstable">Performance (Unstable)</string> <string name="performance_unstable">Performance (Unstable)</string>
<string name="preset">Preset</string> <string name="preset">Preset</string>
<string name="you_cannot_remove_this_preset">You cannot remove this preset</string> <string name="you_cannot_remove_this_preset">You cannot remove this preset</string>
<string name="use_single_cpu">Use single CPU</string>
<string name="dxwrapper_help_content"><![CDATA[ <string name="dxwrapper_help_content"><![CDATA[
<i><b>WineD3D</b></i> is a translation layer for DirectX 1-11 to OpenGL.<br /><br /> <i><b>WineD3D</b></i> is a translation layer for DirectX 1-11 to OpenGL.<br /><br />
<i><b>DXVK</b></i> is a translation layer for Direct3D 9-11 to Vulkan.<br /><br /> <i><b>DXVK</b></i> is a translation layer for Direct3D 9-11 to Vulkan.<br /><br />
@@ -207,4 +205,6 @@
<string name="box86_64_env_var_help__dynarec_forward">Defines the max allowed forward value when building Block</string> <string name="box86_64_env_var_help__dynarec_forward">Defines the max allowed forward value when building Block</string>
<string name="box86_64_env_var_help__dynarec_callret">Optimization of CALL/RET opcodes</string> <string name="box86_64_env_var_help__dynarec_callret">Optimization of CALL/RET opcodes</string>
<string name="box86_64_env_var_help__dynarec_wait">Defines whether or not Dynarec will wait for the FillBlock to be ready</string> <string name="box86_64_env_var_help__dynarec_wait">Defines whether or not Dynarec will wait for the FillBlock to be ready</string>
<string name="columns">Columns</string>
<string name="directinput_mapper_type">DirectInput Mapper Type</string>
</resources> </resources>