mirror of https://github.com/minetest/minetest.git
Merge branch 'master' into Eyecandy-Effects
This commit is contained in:
commit
65d9fcdd49
|
@ -1,2 +1,5 @@
|
||||||
|
# Forces all files which git considers text files to use LF line endings
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
*.cpp diff=cpp
|
*.cpp diff=cpp
|
||||||
*.h diff=cpp
|
*.h diff=cpp
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||||
|
index fd5a056e3..83e3cf657 100644
|
||||||
|
--- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||||
|
+++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||||
|
@@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
|
||||||
|
+ if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
|
||||||
|
+ /*
|
||||||
|
+ * CUSTOM ADDITION FOR MINETEST
|
||||||
|
+ * should be upstreamed
|
||||||
|
+ */
|
||||||
|
+ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
|
||||||
|
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
|
||||||
|
// they are ignored here because sending them as mouse input to SDL is messy
|
||||||
|
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
|
|
@ -20,7 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
package net.minetest.minetest;
|
package net.minetest.minetest;
|
||||||
|
|
||||||
import android.app.NativeActivity;
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -32,6 +33,7 @@ import android.view.WindowManager;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
import androidx.annotation.Keep;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
@ -45,12 +47,29 @@ import java.util.Objects;
|
||||||
// This annotation prevents the minifier/Proguard from mangling them.
|
// This annotation prevents the minifier/Proguard from mangling them.
|
||||||
@Keep
|
@Keep
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class GameActivity extends NativeActivity {
|
public class GameActivity extends SDLActivity {
|
||||||
static {
|
@Override
|
||||||
System.loadLibrary("c++_shared");
|
protected String getMainSharedObject() {
|
||||||
System.loadLibrary("minetest");
|
return getContext().getApplicationInfo().nativeLibraryDir + "/libminetest.so";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getMainFunction() {
|
||||||
|
return "SDL_Main";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] getLibraries() {
|
||||||
|
return new String[] {
|
||||||
|
"minetest"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent SDL from changing orientation settings since we already set the
|
||||||
|
// correct orientation in our AndroidManifest.xml
|
||||||
|
@Override
|
||||||
|
public void setOrientationBis(int w, int h, boolean resizable, String hint) {}
|
||||||
|
|
||||||
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
|
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
|
||||||
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
|
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
|
||||||
|
|
||||||
|
@ -59,32 +78,6 @@ public class GameActivity extends NativeActivity {
|
||||||
private String messageReturnValue = "";
|
private String messageReturnValue = "";
|
||||||
private int selectionReturnValue = 0;
|
private int selectionReturnValue = 0;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeFullScreen() {
|
|
||||||
this.getWindow().getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
|
||||||
super.onWindowFocusChanged(hasFocus);
|
|
||||||
if (hasFocus)
|
|
||||||
makeFullScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
makeFullScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
private native void saveSettings();
|
private native void saveSettings();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,11 +89,6 @@ public class GameActivity extends NativeActivity {
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
// Ignore the back press so Minetest can handle it
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showTextInputDialog(String hint, String current, int editType) {
|
public void showTextInputDialog(String hint, String current, int editType) {
|
||||||
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
|
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
|
||||||
}
|
}
|
||||||
|
@ -265,4 +253,8 @@ public class GameActivity extends NativeActivity {
|
||||||
|
|
||||||
return langCode;
|
return langCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasPhysicalKeyboard() {
|
||||||
|
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
|
||||||
|
interface HIDDevice
|
||||||
|
{
|
||||||
|
public int getId();
|
||||||
|
public int getVendorId();
|
||||||
|
public int getProductId();
|
||||||
|
public String getSerialNumber();
|
||||||
|
public int getVersion();
|
||||||
|
public String getManufacturerName();
|
||||||
|
public String getProductName();
|
||||||
|
public UsbDevice getDevice();
|
||||||
|
public boolean open();
|
||||||
|
public int sendFeatureReport(byte[] report);
|
||||||
|
public int sendOutputReport(byte[] report);
|
||||||
|
public boolean getFeatureReport(byte[] report);
|
||||||
|
public void setFrozen(boolean frozen);
|
||||||
|
public void close();
|
||||||
|
public void shutdown();
|
||||||
|
}
|
|
@ -0,0 +1,650 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCallback;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.bluetooth.BluetoothGattService;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.os.*;
|
||||||
|
|
||||||
|
//import com.android.internal.util.HexDump;
|
||||||
|
|
||||||
|
import java.lang.Runnable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||||
|
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
private HIDDeviceManager mManager;
|
||||||
|
private BluetoothDevice mDevice;
|
||||||
|
private int mDeviceId;
|
||||||
|
private BluetoothGatt mGatt;
|
||||||
|
private boolean mIsRegistered = false;
|
||||||
|
private boolean mIsConnected = false;
|
||||||
|
private boolean mIsChromebook = false;
|
||||||
|
private boolean mIsReconnecting = false;
|
||||||
|
private boolean mFrozen = false;
|
||||||
|
private LinkedList<GattOperation> mOperations;
|
||||||
|
GattOperation mCurrentOperation = null;
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
private static final int TRANSPORT_AUTO = 0;
|
||||||
|
private static final int TRANSPORT_BREDR = 1;
|
||||||
|
private static final int TRANSPORT_LE = 2;
|
||||||
|
|
||||||
|
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||||
|
|
||||||
|
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||||
|
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||||
|
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||||
|
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||||
|
|
||||||
|
static class GattOperation {
|
||||||
|
private enum Operation {
|
||||||
|
CHR_READ,
|
||||||
|
CHR_WRITE,
|
||||||
|
ENABLE_NOTIFICATION
|
||||||
|
}
|
||||||
|
|
||||||
|
Operation mOp;
|
||||||
|
UUID mUuid;
|
||||||
|
byte[] mValue;
|
||||||
|
BluetoothGatt mGatt;
|
||||||
|
boolean mResult = true;
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
// This is executed in main thread
|
||||||
|
BluetoothGattCharacteristic chr;
|
||||||
|
|
||||||
|
switch (mOp) {
|
||||||
|
case CHR_READ:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||||
|
if (!mGatt.readCharacteristic(chr)) {
|
||||||
|
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
break;
|
||||||
|
case CHR_WRITE:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||||
|
chr.setValue(mValue);
|
||||||
|
if (!mGatt.writeCharacteristic(chr)) {
|
||||||
|
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
break;
|
||||||
|
case ENABLE_NOTIFICATION:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||||
|
if (chr != null) {
|
||||||
|
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||||
|
if (cccd != null) {
|
||||||
|
int properties = chr.getProperties();
|
||||||
|
byte[] value;
|
||||||
|
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||||
|
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||||
|
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||||
|
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||||
|
mResult = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mGatt.setCharacteristicNotification(chr, true);
|
||||||
|
cccd.setValue(value);
|
||||||
|
if (!mGatt.writeDescriptor(cccd)) {
|
||||||
|
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean finish() {
|
||||||
|
return mResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||||
|
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||||
|
if (valveService == null)
|
||||||
|
return null;
|
||||||
|
return valveService.getCharacteristic(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||||
|
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||||
|
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||||
|
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||||
|
mManager = manager;
|
||||||
|
mDevice = device;
|
||||||
|
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||||
|
mIsRegistered = false;
|
||||||
|
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||||
|
mOperations = new LinkedList<GattOperation>();
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
mGatt = connectGatt();
|
||||||
|
// final HIDDeviceBLESteamController finalThis = this;
|
||||||
|
// mHandler.postDelayed(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// finalThis.checkConnectionForChromebookIssue();
|
||||||
|
// }
|
||||||
|
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentifier() {
|
||||||
|
return String.format("SteamController.%s", mDevice.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothGatt getGatt() {
|
||||||
|
return mGatt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||||
|
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||||
|
private BluetoothGatt connectGatt(boolean managed) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||||
|
try {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothGatt connectGatt() {
|
||||||
|
return connectGatt(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getConnectionState() {
|
||||||
|
|
||||||
|
Context context = mManager.getContext();
|
||||||
|
if (context == null) {
|
||||||
|
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||||
|
return BluetoothProfile.STATE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||||
|
if (btManager == null) {
|
||||||
|
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||||
|
// we instantiate a device to start with?
|
||||||
|
return BluetoothProfile.STATE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reconnect() {
|
||||||
|
|
||||||
|
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkConnectionForChromebookIssue() {
|
||||||
|
if (!mIsChromebook) {
|
||||||
|
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||||
|
// over and over.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connectionState = getConnectionState();
|
||||||
|
|
||||||
|
switch (connectionState) {
|
||||||
|
case BluetoothProfile.STATE_CONNECTED:
|
||||||
|
if (!mIsConnected) {
|
||||||
|
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||||
|
// to try to recover.
|
||||||
|
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!isRegistered()) {
|
||||||
|
if (mGatt.getServices().size() > 0) {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BluetoothProfile.STATE_DISCONNECTED:
|
||||||
|
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||||
|
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BluetoothProfile.STATE_CONNECTING:
|
||||||
|
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final HIDDeviceBLESteamController finalThis = this;
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finalThis.checkConnectionForChromebookIssue();
|
||||||
|
}
|
||||||
|
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRegistered() {
|
||||||
|
return mIsRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegistered() {
|
||||||
|
mIsRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||||
|
|
||||||
|
if (isRegistered()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsConnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "probeService controller=" + controller);
|
||||||
|
|
||||||
|
for (BluetoothGattService service : mGatt.getServices()) {
|
||||||
|
if (service.getUuid().equals(steamControllerService)) {
|
||||||
|
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||||
|
|
||||||
|
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||||
|
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||||
|
Log.v(TAG, "Found input characteristic");
|
||||||
|
// Start notifications
|
||||||
|
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||||
|
if (cccd != null) {
|
||||||
|
enableNotification(chr.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||||
|
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||||
|
mIsConnected = false;
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void finishCurrentGattOperation() {
|
||||||
|
GattOperation op = null;
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation != null) {
|
||||||
|
op = mCurrentOperation;
|
||||||
|
mCurrentOperation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (op != null) {
|
||||||
|
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||||
|
|
||||||
|
// Our operation failed, let's add it back to the beginning of our queue.
|
||||||
|
if (!result) {
|
||||||
|
mOperations.addFirst(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executeNextGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeNextGattOperation() {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mOperations.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
mCurrentOperation = mOperations.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run in main thread
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation == null) {
|
||||||
|
Log.e(TAG, "Current operation null in executor?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentOperation.run();
|
||||||
|
// now wait for the GATT callback and when it comes, finish this operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueGattOperation(GattOperation op) {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
mOperations.add(op);
|
||||||
|
}
|
||||||
|
executeNextGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableNotification(UUID chrUuid) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readCharacteristic(UUID uuid) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////// BluetoothGattCallback overridden methods
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||||
|
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||||
|
mIsReconnecting = false;
|
||||||
|
if (newState == 2) {
|
||||||
|
mIsConnected = true;
|
||||||
|
// Run directly, without GattOperation
|
||||||
|
if (!isRegistered()) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mGatt.discoverServices();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (newState == 0) {
|
||||||
|
mIsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||||
|
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||||
|
if (status == 0) {
|
||||||
|
if (gatt.getServices().size() == 0) {
|
||||||
|
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mIsConnected = false;
|
||||||
|
gatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||||
|
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||||
|
// Only register controller with the native side once it has been fully configured
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||||
|
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||||
|
setRegistered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
|
// Enable this for verbose logging of controller input reports
|
||||||
|
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||||
|
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||||
|
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||||
|
|
||||||
|
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||||
|
boolean hasWrittenInputDescriptor = true;
|
||||||
|
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||||
|
if (reportChr != null) {
|
||||||
|
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||||
|
reportChr.setValue(enterValveMode);
|
||||||
|
gatt.writeCharacteristic(reportChr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||||
|
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||||
|
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////// Public API
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return mDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId() {
|
||||||
|
// Valve Corporation
|
||||||
|
final int VALVE_USB_VID = 0x28DE;
|
||||||
|
return VALVE_USB_VID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId() {
|
||||||
|
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||||
|
final int D0G_BLE2_PID = 0x1106;
|
||||||
|
return D0G_BLE2_PID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerialNumber() {
|
||||||
|
// This will be read later via feature report by Steam
|
||||||
|
return "12345";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturerName() {
|
||||||
|
return "Valve Corporation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProductName() {
|
||||||
|
return "Steam Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UsbDevice getDevice() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean open() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendFeatureReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to skip the first byte, as that doesn't go over the air
|
||||||
|
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||||
|
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||||
|
writeCharacteristic(reportCharacteristic, actual_report);
|
||||||
|
return report.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendOutputReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||||
|
writeCharacteristic(reportCharacteristic, report);
|
||||||
|
return report.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFeatureReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.v(TAG, "getFeatureReport");
|
||||||
|
readCharacteristic(reportCharacteristic);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
mFrozen = frozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
close();
|
||||||
|
|
||||||
|
BluetoothGatt g = mGatt;
|
||||||
|
if (g != null) {
|
||||||
|
g.disconnect();
|
||||||
|
g.close();
|
||||||
|
mGatt = null;
|
||||||
|
}
|
||||||
|
mManager = null;
|
||||||
|
mIsRegistered = false;
|
||||||
|
mIsConnected = false;
|
||||||
|
mOperations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,691 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.hardware.usb.*;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HIDDeviceManager {
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||||
|
|
||||||
|
private static HIDDeviceManager sManager;
|
||||||
|
private static int sManagerRefCount = 0;
|
||||||
|
|
||||||
|
public static HIDDeviceManager acquire(Context context) {
|
||||||
|
if (sManagerRefCount == 0) {
|
||||||
|
sManager = new HIDDeviceManager(context);
|
||||||
|
}
|
||||||
|
++sManagerRefCount;
|
||||||
|
return sManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void release(HIDDeviceManager manager) {
|
||||||
|
if (manager == sManager) {
|
||||||
|
--sManagerRefCount;
|
||||||
|
if (sManagerRefCount == 0) {
|
||||||
|
sManager.close();
|
||||||
|
sManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||||
|
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||||
|
private int mNextDeviceId = 0;
|
||||||
|
private SharedPreferences mSharedPreferences = null;
|
||||||
|
private boolean mIsChromebook = false;
|
||||||
|
private UsbManager mUsbManager;
|
||||||
|
private Handler mHandler;
|
||||||
|
private BluetoothManager mBluetoothManager;
|
||||||
|
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||||
|
|
||||||
|
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDeviceAttached(usbDevice);
|
||||||
|
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDeviceDetached(usbDevice);
|
||||||
|
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||||
|
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||||
|
|
||||||
|
if (isSteamController(device)) {
|
||||||
|
connectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||||
|
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||||
|
|
||||||
|
disconnectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private HIDDeviceManager(final Context context) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
HIDDeviceRegisterCallback();
|
||||||
|
|
||||||
|
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||||
|
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||||
|
|
||||||
|
// if (shouldClear) {
|
||||||
|
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||||
|
// spedit.clear();
|
||||||
|
// spedit.commit();
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
{
|
||||||
|
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeviceIDForIdentifier(String identifier) {
|
||||||
|
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||||
|
|
||||||
|
int result = mSharedPreferences.getInt(identifier, 0);
|
||||||
|
if (result == 0) {
|
||||||
|
result = mNextDeviceId++;
|
||||||
|
spedit.putInt("next_device_id", mNextDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
spedit.putInt(identifier, result);
|
||||||
|
spedit.commit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeUSB() {
|
||||||
|
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||||
|
if (mUsbManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Logging
|
||||||
|
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||||
|
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||||
|
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||||
|
Log.i(TAG,"Product: " + device.getProductName());
|
||||||
|
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||||
|
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||||
|
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||||
|
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||||
|
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||||
|
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||||
|
Log.i(TAG,"---------------------------------------");
|
||||||
|
|
||||||
|
// Get interface details
|
||||||
|
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||||
|
UsbInterface mUsbInterface = device.getInterface(index);
|
||||||
|
Log.i(TAG," ***** *****");
|
||||||
|
Log.i(TAG," Interface index: " + index);
|
||||||
|
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||||
|
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||||
|
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||||
|
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||||
|
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||||
|
|
||||||
|
// Get endpoint details
|
||||||
|
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||||
|
{
|
||||||
|
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||||
|
Log.i(TAG," ++++ ++++ ++++");
|
||||||
|
Log.i(TAG," Endpoint index: " + epi);
|
||||||
|
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||||
|
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||||
|
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||||
|
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||||
|
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||||
|
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG," No more devices connected.");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Register for USB broadcasts and permission completions
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||||
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||||
|
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||||
|
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||||
|
|
||||||
|
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||||
|
handleUsbDeviceAttached(usbDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UsbManager getUSBManager() {
|
||||||
|
return mUsbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownUSB() {
|
||||||
|
try {
|
||||||
|
mContext.unregisterReceiver(mUsbBroadcast);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We may not have registered, that's okay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
final int XB360_IFACE_SUBCLASS = 93;
|
||||||
|
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||||
|
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||||
|
final int[] SUPPORTED_VENDORS = {
|
||||||
|
0x0079, // GPD Win 2
|
||||||
|
0x044f, // Thrustmaster
|
||||||
|
0x045e, // Microsoft
|
||||||
|
0x046d, // Logitech
|
||||||
|
0x056e, // Elecom
|
||||||
|
0x06a3, // Saitek
|
||||||
|
0x0738, // Mad Catz
|
||||||
|
0x07ff, // Mad Catz
|
||||||
|
0x0e6f, // PDP
|
||||||
|
0x0f0d, // Hori
|
||||||
|
0x1038, // SteelSeries
|
||||||
|
0x11c9, // Nacon
|
||||||
|
0x12ab, // Unknown
|
||||||
|
0x1430, // RedOctane
|
||||||
|
0x146b, // BigBen
|
||||||
|
0x1532, // Razer Sabertooth
|
||||||
|
0x15e4, // Numark
|
||||||
|
0x162e, // Joytech
|
||||||
|
0x1689, // Razer Onza
|
||||||
|
0x1949, // Lab126, Inc.
|
||||||
|
0x1bad, // Harmonix
|
||||||
|
0x20d6, // PowerA
|
||||||
|
0x24c6, // PowerA
|
||||||
|
0x2c22, // Qanba
|
||||||
|
0x2dc8, // 8BitDo
|
||||||
|
0x9886, // ASTRO Gaming
|
||||||
|
};
|
||||||
|
|
||||||
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||||
|
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||||
|
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||||
|
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||||
|
int vendor_id = usbDevice.getVendorId();
|
||||||
|
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||||
|
if (vendor_id == supportedVid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
final int XB1_IFACE_SUBCLASS = 71;
|
||||||
|
final int XB1_IFACE_PROTOCOL = 208;
|
||||||
|
final int[] SUPPORTED_VENDORS = {
|
||||||
|
0x03f0, // HP
|
||||||
|
0x044f, // Thrustmaster
|
||||||
|
0x045e, // Microsoft
|
||||||
|
0x0738, // Mad Catz
|
||||||
|
0x0e6f, // PDP
|
||||||
|
0x0f0d, // Hori
|
||||||
|
0x10f5, // Turtle Beach
|
||||||
|
0x1532, // Razer Wildcat
|
||||||
|
0x20d6, // PowerA
|
||||||
|
0x24c6, // PowerA
|
||||||
|
0x2dc8, // 8BitDo
|
||||||
|
0x2e24, // Hyperkin
|
||||||
|
0x3537, // GameSir
|
||||||
|
};
|
||||||
|
|
||||||
|
if (usbInterface.getId() == 0 &&
|
||||||
|
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||||
|
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||||
|
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||||
|
int vendor_id = usbDevice.getVendorId();
|
||||||
|
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||||
|
if (vendor_id == supportedVid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||||
|
connectHIDDeviceUSB(usbDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||||
|
List<Integer> devices = new ArrayList<Integer>();
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
if (usbDevice.equals(device.getDevice())) {
|
||||||
|
devices.add(device.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int id : devices) {
|
||||||
|
HIDDevice device = mDevicesById.get(id);
|
||||||
|
mDevicesById.remove(id);
|
||||||
|
device.shutdown();
|
||||||
|
HIDDeviceDisconnected(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
if (usbDevice.equals(device.getDevice())) {
|
||||||
|
boolean opened = false;
|
||||||
|
if (permission_granted) {
|
||||||
|
opened = device.open();
|
||||||
|
}
|
||||||
|
HIDDeviceOpenResult(device.getId(), opened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||||
|
synchronized (this) {
|
||||||
|
int interface_mask = 0;
|
||||||
|
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||||
|
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||||
|
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||||
|
// Check to see if we've already added this interface
|
||||||
|
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||||
|
int interface_id = usbInterface.getId();
|
||||||
|
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
interface_mask |= (1 << interface_id);
|
||||||
|
|
||||||
|
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||||
|
int id = device.getId();
|
||||||
|
mDevicesById.put(id, device);
|
||||||
|
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeBluetooth() {
|
||||||
|
Log.d(TAG, "Initializing Bluetooth");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||||
|
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||||
|
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||||
|
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||||
|
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||||
|
if (mBluetoothManager == null) {
|
||||||
|
// This device doesn't support Bluetooth.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||||
|
if (btAdapter == null) {
|
||||||
|
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our bonded devices.
|
||||||
|
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||||
|
|
||||||
|
Log.d(TAG, "Bluetooth device available: " + device);
|
||||||
|
if (isSteamController(device)) {
|
||||||
|
connectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||||
|
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||||
|
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||||
|
|
||||||
|
if (mIsChromebook) {
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||||
|
|
||||||
|
// final HIDDeviceManager finalThis = this;
|
||||||
|
// mHandler.postDelayed(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// finalThis.chromebookConnectionHandler();
|
||||||
|
// }
|
||||||
|
// }, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownBluetooth() {
|
||||||
|
try {
|
||||||
|
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We may not have registered, that's okay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||||
|
// This function provides a sort of dummy version of that, watching for changes in the
|
||||||
|
// connected devices and attempting to add controllers as things change.
|
||||||
|
public void chromebookConnectionHandler() {
|
||||||
|
if (!mIsChromebook) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||||
|
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||||
|
|
||||||
|
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||||
|
|
||||||
|
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||||
|
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||||
|
connected.add(bluetoothDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||||
|
if (!currentConnected.contains(bluetoothDevice)) {
|
||||||
|
disconnected.add(bluetoothDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastBluetoothDevices = currentConnected;
|
||||||
|
|
||||||
|
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||||
|
disconnectBluetoothDevice(bluetoothDevice);
|
||||||
|
}
|
||||||
|
for (BluetoothDevice bluetoothDevice : connected) {
|
||||||
|
connectBluetoothDevice(bluetoothDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
final HIDDeviceManager finalThis = this;
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finalThis.chromebookConnectionHandler();
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||||
|
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||||
|
synchronized (this) {
|
||||||
|
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||||
|
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||||
|
|
||||||
|
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||||
|
device.reconnect();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||||
|
int id = device.getId();
|
||||||
|
mBluetoothDevices.put(bluetoothDevice, device);
|
||||||
|
mDevicesById.put(id, device);
|
||||||
|
|
||||||
|
// The Steam Controller will mark itself connected once initialization is complete
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||||
|
synchronized (this) {
|
||||||
|
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||||
|
if (device == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int id = device.getId();
|
||||||
|
mBluetoothDevices.remove(bluetoothDevice);
|
||||||
|
mDevicesById.remove(id);
|
||||||
|
device.shutdown();
|
||||||
|
HIDDeviceDisconnected(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||||
|
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||||
|
if (bluetoothDevice == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the device has no local name, we really don't want to try an equality check against it.
|
||||||
|
if (bluetoothDevice.getName() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
shutdownUSB();
|
||||||
|
shutdownBluetooth();
|
||||||
|
synchronized (this) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
device.shutdown();
|
||||||
|
}
|
||||||
|
mDevicesById.clear();
|
||||||
|
mBluetoothDevices.clear();
|
||||||
|
HIDDeviceReleaseCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
synchronized (this) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
device.setFrozen(frozen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private HIDDevice getDevice(int id) {
|
||||||
|
synchronized (this) {
|
||||||
|
HIDDevice result = mDevicesById.get(id);
|
||||||
|
if (result == null) {
|
||||||
|
Log.v(TAG, "No device for id: " + id);
|
||||||
|
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////// JNI interface functions
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||||
|
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||||
|
|
||||||
|
if (usb) {
|
||||||
|
initializeUSB();
|
||||||
|
}
|
||||||
|
if (bluetooth) {
|
||||||
|
initializeBluetooth();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean openDevice(int deviceID) {
|
||||||
|
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||||
|
HIDDevice device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look to see if this is a USB device and we have permission to access it
|
||||||
|
UsbDevice usbDevice = device.getDevice();
|
||||||
|
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||||
|
HIDDeviceOpenPending(deviceID);
|
||||||
|
try {
|
||||||
|
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||||
|
int flags;
|
||||||
|
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||||
|
flags = FLAG_MUTABLE;
|
||||||
|
} else {
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||||
|
HIDDeviceOpenResult(deviceID, false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return device.open();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sendOutputReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.sendOutputReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.sendFeatureReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.getFeatureReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeDevice(int deviceID) {
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// Native methods
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private native void HIDDeviceRegisterCallback();
|
||||||
|
private native void HIDDeviceReleaseCallback();
|
||||||
|
|
||||||
|
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||||
|
native void HIDDeviceOpenPending(int deviceID);
|
||||||
|
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||||
|
native void HIDDeviceDisconnected(int deviceID);
|
||||||
|
|
||||||
|
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||||
|
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||||
|
}
|
|
@ -0,0 +1,309 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.hardware.usb.*;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
class HIDDeviceUSB implements HIDDevice {
|
||||||
|
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
|
||||||
|
protected HIDDeviceManager mManager;
|
||||||
|
protected UsbDevice mDevice;
|
||||||
|
protected int mInterfaceIndex;
|
||||||
|
protected int mInterface;
|
||||||
|
protected int mDeviceId;
|
||||||
|
protected UsbDeviceConnection mConnection;
|
||||||
|
protected UsbEndpoint mInputEndpoint;
|
||||||
|
protected UsbEndpoint mOutputEndpoint;
|
||||||
|
protected InputThread mInputThread;
|
||||||
|
protected boolean mRunning;
|
||||||
|
protected boolean mFrozen;
|
||||||
|
|
||||||
|
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||||
|
mManager = manager;
|
||||||
|
mDevice = usbDevice;
|
||||||
|
mInterfaceIndex = interface_index;
|
||||||
|
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||||
|
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentifier() {
|
||||||
|
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return mDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId() {
|
||||||
|
return mDevice.getVendorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId() {
|
||||||
|
return mDevice.getProductId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerialNumber() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||||
|
try {
|
||||||
|
result = mDevice.getSerialNumber();
|
||||||
|
}
|
||||||
|
catch (SecurityException exception) {
|
||||||
|
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturerName() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||||
|
result = mDevice.getManufacturerName();
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = String.format("%x", getVendorId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProductName() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||||
|
result = mDevice.getProductName();
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = String.format("%x", getProductId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UsbDevice getDevice() {
|
||||||
|
return mDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean open() {
|
||||||
|
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||||
|
if (mConnection == null) {
|
||||||
|
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force claim our interface
|
||||||
|
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||||
|
if (!mConnection.claimInterface(iface, true)) {
|
||||||
|
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the endpoints
|
||||||
|
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||||
|
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||||
|
switch (endpt.getDirection()) {
|
||||||
|
case UsbConstants.USB_DIR_IN:
|
||||||
|
if (mInputEndpoint == null) {
|
||||||
|
mInputEndpoint = endpt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UsbConstants.USB_DIR_OUT:
|
||||||
|
if (mOutputEndpoint == null) {
|
||||||
|
mOutputEndpoint = endpt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the required endpoints were present
|
||||||
|
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||||
|
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for input
|
||||||
|
mRunning = true;
|
||||||
|
mInputThread = new InputThread();
|
||||||
|
mInputThread.start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendFeatureReport(byte[] report) {
|
||||||
|
int res = -1;
|
||||||
|
int offset = 0;
|
||||||
|
int length = report.length;
|
||||||
|
boolean skipped_report_id = false;
|
||||||
|
byte report_number = report[0];
|
||||||
|
|
||||||
|
if (report_number == 0x0) {
|
||||||
|
++offset;
|
||||||
|
--length;
|
||||||
|
skipped_report_id = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = mConnection.controlTransfer(
|
||||||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||||
|
0x09/*HID set_report*/,
|
||||||
|
(3/*HID feature*/ << 8) | report_number,
|
||||||
|
mInterface,
|
||||||
|
report, offset, length,
|
||||||
|
1000/*timeout millis*/);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped_report_id) {
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendOutputReport(byte[] report) {
|
||||||
|
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||||
|
if (r != report.length) {
|
||||||
|
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFeatureReport(byte[] report) {
|
||||||
|
int res = -1;
|
||||||
|
int offset = 0;
|
||||||
|
int length = report.length;
|
||||||
|
boolean skipped_report_id = false;
|
||||||
|
byte report_number = report[0];
|
||||||
|
|
||||||
|
if (report_number == 0x0) {
|
||||||
|
/* Offset the return buffer by 1, so that the report ID
|
||||||
|
will remain in byte 0. */
|
||||||
|
++offset;
|
||||||
|
--length;
|
||||||
|
skipped_report_id = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = mConnection.controlTransfer(
|
||||||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||||
|
0x01/*HID get_report*/,
|
||||||
|
(3/*HID feature*/ << 8) | report_number,
|
||||||
|
mInterface,
|
||||||
|
report, offset, length,
|
||||||
|
1000/*timeout millis*/);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped_report_id) {
|
||||||
|
++res;
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
if (res == length) {
|
||||||
|
data = report;
|
||||||
|
} else {
|
||||||
|
data = Arrays.copyOfRange(report, 0, res);
|
||||||
|
}
|
||||||
|
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
mRunning = false;
|
||||||
|
if (mInputThread != null) {
|
||||||
|
while (mInputThread.isAlive()) {
|
||||||
|
mInputThread.interrupt();
|
||||||
|
try {
|
||||||
|
mInputThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Keep trying until we're done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mInputThread = null;
|
||||||
|
}
|
||||||
|
if (mConnection != null) {
|
||||||
|
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||||
|
mConnection.releaseInterface(iface);
|
||||||
|
mConnection.close();
|
||||||
|
mConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
close();
|
||||||
|
mManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
mFrozen = frozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class InputThread extends Thread {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||||
|
byte[] packet = new byte[packetSize];
|
||||||
|
while (mRunning) {
|
||||||
|
int r;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r < 0) {
|
||||||
|
// Could be a timeout or an I/O error
|
||||||
|
}
|
||||||
|
if (r > 0) {
|
||||||
|
byte[] data;
|
||||||
|
if (r == packetSize) {
|
||||||
|
data = packet;
|
||||||
|
} else {
|
||||||
|
data = Arrays.copyOfRange(packet, 0, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mFrozen) {
|
||||||
|
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.lang.Class;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
SDL library initialization
|
||||||
|
*/
|
||||||
|
public class SDL {
|
||||||
|
|
||||||
|
// This function should be called first and sets up the native code
|
||||||
|
// so it can call into the Java classes
|
||||||
|
public static void setupJNI() {
|
||||||
|
SDLActivity.nativeSetupJNI();
|
||||||
|
SDLAudioManager.nativeSetupJNI();
|
||||||
|
SDLControllerManager.nativeSetupJNI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function should be called each time the activity is started
|
||||||
|
public static void initialize() {
|
||||||
|
setContext(null);
|
||||||
|
|
||||||
|
SDLActivity.initialize();
|
||||||
|
SDLAudioManager.initialize();
|
||||||
|
SDLControllerManager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function stores the current activity (SDL or not)
|
||||||
|
public static void setContext(Context context) {
|
||||||
|
SDLAudioManager.setContext(context);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||||
|
|
||||||
|
if (libraryName == null) {
|
||||||
|
throw new NullPointerException("No library name provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||||
|
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||||
|
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||||
|
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||||
|
// internally.)
|
||||||
|
//
|
||||||
|
// To use ReLinker, just add it as a dependency. For more information, see
|
||||||
|
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||||
|
//
|
||||||
|
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||||
|
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||||
|
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||||
|
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||||
|
|
||||||
|
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||||
|
// they've changed during updates.
|
||||||
|
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||||
|
Object relinkInstance = forceMethod.invoke(null);
|
||||||
|
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||||
|
|
||||||
|
// Actually load the library!
|
||||||
|
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||||
|
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||||
|
}
|
||||||
|
catch (final Throwable e) {
|
||||||
|
// Fall back
|
||||||
|
try {
|
||||||
|
System.loadLibrary(libraryName);
|
||||||
|
}
|
||||||
|
catch (final UnsatisfiedLinkError ule) {
|
||||||
|
throw ule;
|
||||||
|
}
|
||||||
|
catch (final SecurityException se) {
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Context mContext;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,514 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioDeviceCallback;
|
||||||
|
import android.media.AudioDeviceInfo;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.AudioRecord;
|
||||||
|
import android.media.AudioTrack;
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class SDLAudioManager {
|
||||||
|
protected static final String TAG = "SDLAudio";
|
||||||
|
|
||||||
|
protected static AudioTrack mAudioTrack;
|
||||||
|
protected static AudioRecord mAudioRecord;
|
||||||
|
protected static Context mContext;
|
||||||
|
|
||||||
|
private static final int[] NO_DEVICES = {};
|
||||||
|
|
||||||
|
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||||
|
|
||||||
|
public static void initialize() {
|
||||||
|
mAudioTrack = null;
|
||||||
|
mAudioRecord = null;
|
||||||
|
mAudioDeviceCallback = null;
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||||
|
{
|
||||||
|
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||||
|
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||||
|
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setContext(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
if (context != null) {
|
||||||
|
registerAudioDeviceCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void release(Context context) {
|
||||||
|
unregisterAudioDeviceCallback(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
|
||||||
|
protected static String getAudioFormatString(int audioFormat) {
|
||||||
|
switch (audioFormat) {
|
||||||
|
case AudioFormat.ENCODING_PCM_8BIT:
|
||||||
|
return "8-bit";
|
||||||
|
case AudioFormat.ENCODING_PCM_16BIT:
|
||||||
|
return "16-bit";
|
||||||
|
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||||
|
return "float";
|
||||||
|
default:
|
||||||
|
return Integer.toString(audioFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||||
|
int channelConfig;
|
||||||
|
int sampleSize;
|
||||||
|
int frameSize;
|
||||||
|
|
||||||
|
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||||
|
|
||||||
|
/* On older devices let's use known good settings */
|
||||||
|
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||||
|
if (desiredChannels > 2) {
|
||||||
|
desiredChannels = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||||
|
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||||
|
if (sampleRate < 8000) {
|
||||||
|
sampleRate = 8000;
|
||||||
|
} else if (sampleRate > 48000) {
|
||||||
|
sampleRate = 48000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||||
|
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||||
|
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||||
|
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (audioFormat)
|
||||||
|
{
|
||||||
|
case AudioFormat.ENCODING_PCM_8BIT:
|
||||||
|
sampleSize = 1;
|
||||||
|
break;
|
||||||
|
case AudioFormat.ENCODING_PCM_16BIT:
|
||||||
|
sampleSize = 2;
|
||||||
|
break;
|
||||||
|
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||||
|
sampleSize = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||||
|
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
sampleSize = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCapture) {
|
||||||
|
switch (desiredChannels) {
|
||||||
|
case 1:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||||
|
desiredChannels = 2;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (desiredChannels) {
|
||||||
|
case 1:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||||
|
desiredChannels = 6;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||||
|
desiredChannels = 2;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||||
|
|
||||||
|
if ((channelConfig & 0x00000004) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000008) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000010) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000020) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000040) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000080) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000100) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000200) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000400) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000800) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00001000) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
frameSize = (sampleSize * desiredChannels);
|
||||||
|
|
||||||
|
// Let the user pick a larger buffer if they really want -- but ye
|
||||||
|
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||||
|
// latency already
|
||||||
|
int minBufferSize;
|
||||||
|
if (isCapture) {
|
||||||
|
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||||
|
} else {
|
||||||
|
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||||
|
}
|
||||||
|
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||||
|
|
||||||
|
int[] results = new int[4];
|
||||||
|
|
||||||
|
if (isCapture) {
|
||||||
|
if (mAudioRecord == null) {
|
||||||
|
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||||
|
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||||
|
|
||||||
|
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||||
|
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||||
|
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||||
|
mAudioRecord.release();
|
||||||
|
mAudioRecord = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||||
|
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioRecord.startRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
results[0] = mAudioRecord.getSampleRate();
|
||||||
|
results[1] = mAudioRecord.getAudioFormat();
|
||||||
|
results[2] = mAudioRecord.getChannelCount();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||||
|
|
||||||
|
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||||
|
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||||
|
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||||
|
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||||
|
/* Try again, with safer values */
|
||||||
|
|
||||||
|
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||||
|
mAudioTrack.release();
|
||||||
|
mAudioTrack = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||||
|
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioTrack.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
results[0] = mAudioTrack.getSampleRate();
|
||||||
|
results[1] = mAudioTrack.getAudioFormat();
|
||||||
|
results[2] = mAudioTrack.getChannelCount();
|
||||||
|
}
|
||||||
|
results[3] = desiredFrames;
|
||||||
|
|
||||||
|
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||||
|
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||||
|
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerAudioDeviceCallback() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void unregisterAudioDeviceCallback(Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] getAudioOutputDevices() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||||
|
} else {
|
||||||
|
return NO_DEVICES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] getAudioInputDevices() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||||
|
} else {
|
||||||
|
return NO_DEVICES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||||
|
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||||
|
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length;) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteShortBuffer(short[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length;) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length; ) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||||
|
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||||
|
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||||
|
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||||
|
} else {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||||
|
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||||
|
} else {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void audioClose() {
|
||||||
|
if (mAudioTrack != null) {
|
||||||
|
mAudioTrack.stop();
|
||||||
|
mAudioTrack.release();
|
||||||
|
mAudioTrack = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void captureClose() {
|
||||||
|
if (mAudioRecord != null) {
|
||||||
|
mAudioRecord.stop();
|
||||||
|
mAudioRecord.release();
|
||||||
|
mAudioRecord = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
/* Set thread name */
|
||||||
|
if (iscapture) {
|
||||||
|
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||||
|
} else {
|
||||||
|
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set thread priority */
|
||||||
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static native int nativeSetupJNI();
|
||||||
|
|
||||||
|
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||||
|
|
||||||
|
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,854 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
|
||||||
|
public class SDLControllerManager
|
||||||
|
{
|
||||||
|
|
||||||
|
public static native int nativeSetupJNI();
|
||||||
|
|
||||||
|
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||||
|
int vendor_id, int product_id,
|
||||||
|
boolean is_accelerometer, int button_mask,
|
||||||
|
int naxes, int axis_mask, int nhats, int nballs);
|
||||||
|
public static native int nativeRemoveJoystick(int device_id);
|
||||||
|
public static native int nativeAddHaptic(int device_id, String name);
|
||||||
|
public static native int nativeRemoveHaptic(int device_id);
|
||||||
|
public static native int onNativePadDown(int device_id, int keycode);
|
||||||
|
public static native int onNativePadUp(int device_id, int keycode);
|
||||||
|
public static native void onNativeJoy(int device_id, int axis,
|
||||||
|
float value);
|
||||||
|
public static native void onNativeHat(int device_id, int hat_id,
|
||||||
|
int x, int y);
|
||||||
|
|
||||||
|
protected static SDLJoystickHandler mJoystickHandler;
|
||||||
|
protected static SDLHapticHandler mHapticHandler;
|
||||||
|
|
||||||
|
private static final String TAG = "SDLControllerManager";
|
||||||
|
|
||||||
|
public static void initialize() {
|
||||||
|
if (mJoystickHandler == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||||
|
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||||
|
} else {
|
||||||
|
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHapticHandler == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||||
|
mHapticHandler = new SDLHapticHandler_API26();
|
||||||
|
} else {
|
||||||
|
mHapticHandler = new SDLHapticHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||||
|
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||||
|
return mJoystickHandler.handleMotionEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void pollInputDevices() {
|
||||||
|
mJoystickHandler.pollInputDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void pollHapticDevices() {
|
||||||
|
mHapticHandler.pollHapticDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void hapticRun(int device_id, float intensity, int length) {
|
||||||
|
mHapticHandler.run(device_id, intensity, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void hapticStop(int device_id)
|
||||||
|
{
|
||||||
|
mHapticHandler.stop(device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a given device is considered a possible SDL joystick
|
||||||
|
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
|
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||||
|
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||||
|
if ((device == null) || (deviceId < 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int sources = device.getSources();
|
||||||
|
|
||||||
|
/* This is called for every button press, so let's not spam the logs */
|
||||||
|
/*
|
||||||
|
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||||
|
}
|
||||||
|
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||||
|
}
|
||||||
|
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||||
|
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||||
|
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLJoystickHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles given MotionEvent.
|
||||||
|
* @param event the event to be handled.
|
||||||
|
* @return if given event was processed.
|
||||||
|
*/
|
||||||
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles adding and removing of input devices.
|
||||||
|
*/
|
||||||
|
public void pollInputDevices() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual joystick functionality available for API >= 12 devices */
|
||||||
|
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||||
|
|
||||||
|
static class SDLJoystick {
|
||||||
|
public int device_id;
|
||||||
|
public String name;
|
||||||
|
public String desc;
|
||||||
|
public ArrayList<InputDevice.MotionRange> axes;
|
||||||
|
public ArrayList<InputDevice.MotionRange> hats;
|
||||||
|
}
|
||||||
|
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||||
|
@Override
|
||||||
|
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||||
|
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||||
|
int arg0Axis = arg0.getAxis();
|
||||||
|
int arg1Axis = arg1.getAxis();
|
||||||
|
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||||
|
// This is because the usual pairing are:
|
||||||
|
// - AXIS_X + AXIS_Y (left stick).
|
||||||
|
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||||
|
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||||
|
// This sorts the axes in the above order, which tends to be correct
|
||||||
|
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||||
|
// triggers on Z/RZ.
|
||||||
|
//
|
||||||
|
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||||
|
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||||
|
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||||
|
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||||
|
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||||
|
--arg0Axis;
|
||||||
|
}
|
||||||
|
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||||
|
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||||
|
--arg1Axis;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg0Axis - arg1Axis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<SDLJoystick> mJoysticks;
|
||||||
|
|
||||||
|
public SDLJoystickHandler_API16() {
|
||||||
|
|
||||||
|
mJoysticks = new ArrayList<SDLJoystick>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pollInputDevices() {
|
||||||
|
int[] deviceIds = InputDevice.getDeviceIds();
|
||||||
|
|
||||||
|
for (int device_id : deviceIds) {
|
||||||
|
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||||
|
SDLJoystick joystick = getJoystick(device_id);
|
||||||
|
if (joystick == null) {
|
||||||
|
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||||
|
joystick = new SDLJoystick();
|
||||||
|
joystick.device_id = device_id;
|
||||||
|
joystick.name = joystickDevice.getName();
|
||||||
|
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||||
|
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
|
||||||
|
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||||
|
Collections.sort(ranges, new RangeComparator());
|
||||||
|
for (InputDevice.MotionRange range : ranges) {
|
||||||
|
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
|
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||||
|
joystick.hats.add(range);
|
||||||
|
} else {
|
||||||
|
joystick.axes.add(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mJoysticks.add(joystick);
|
||||||
|
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||||
|
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||||
|
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check removed devices */
|
||||||
|
ArrayList<Integer> removedDevices = null;
|
||||||
|
for (SDLJoystick joystick : mJoysticks) {
|
||||||
|
int device_id = joystick.device_id;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < deviceIds.length; i++) {
|
||||||
|
if (device_id == deviceIds[i]) break;
|
||||||
|
}
|
||||||
|
if (i == deviceIds.length) {
|
||||||
|
if (removedDevices == null) {
|
||||||
|
removedDevices = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
removedDevices.add(device_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedDevices != null) {
|
||||||
|
for (int device_id : removedDevices) {
|
||||||
|
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||||
|
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||||
|
if (mJoysticks.get(i).device_id == device_id) {
|
||||||
|
mJoysticks.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SDLJoystick getJoystick(int device_id) {
|
||||||
|
for (SDLJoystick joystick : mJoysticks) {
|
||||||
|
if (joystick.device_id == device_id) {
|
||||||
|
return joystick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
|
int actionPointerIndex = event.getActionIndex();
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == MotionEvent.ACTION_MOVE) {
|
||||||
|
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||||
|
if (joystick != null) {
|
||||||
|
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||||
|
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||||
|
/* Normalize the value to -1...1 */
|
||||||
|
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||||
|
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||||
|
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||||
|
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||||
|
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||||
|
String desc = joystickDevice.getDescriptor();
|
||||||
|
|
||||||
|
if (desc != null && !desc.isEmpty()) {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return joystickDevice.getName();
|
||||||
|
}
|
||||||
|
public int getProductId(InputDevice joystickDevice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int getVendorId(InputDevice joystickDevice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
public int getButtonMask(InputDevice joystickDevice) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId(InputDevice joystickDevice) {
|
||||||
|
return joystickDevice.getProductId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId(InputDevice joystickDevice) {
|
||||||
|
return joystickDevice.getVendorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||||
|
// For compatibility, keep computing the axis mask like before,
|
||||||
|
// only really distinguishing 2, 4 and 6 axes.
|
||||||
|
int axis_mask = 0;
|
||||||
|
if (ranges.size() >= 2) {
|
||||||
|
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||||
|
axis_mask |= 0x0003;
|
||||||
|
}
|
||||||
|
if (ranges.size() >= 4) {
|
||||||
|
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||||
|
axis_mask |= 0x000c;
|
||||||
|
}
|
||||||
|
if (ranges.size() >= 6) {
|
||||||
|
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||||
|
axis_mask |= 0x0030;
|
||||||
|
}
|
||||||
|
// Also add an indicator bit for whether the sorting order has changed.
|
||||||
|
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||||
|
boolean have_z = false;
|
||||||
|
boolean have_past_z_before_rz = false;
|
||||||
|
for (InputDevice.MotionRange range : ranges) {
|
||||||
|
int axis = range.getAxis();
|
||||||
|
if (axis == MotionEvent.AXIS_Z) {
|
||||||
|
have_z = true;
|
||||||
|
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||||
|
have_past_z_before_rz = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (have_z && have_past_z_before_rz) {
|
||||||
|
// If both these exist, the compare() function changed sorting order.
|
||||||
|
// Set a bit to indicate this fact.
|
||||||
|
axis_mask |= 0x8000;
|
||||||
|
}
|
||||||
|
return axis_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getButtonMask(InputDevice joystickDevice) {
|
||||||
|
int button_mask = 0;
|
||||||
|
int[] keys = new int[] {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
|
KeyEvent.KEYCODE_BACK,
|
||||||
|
KeyEvent.KEYCODE_MENU,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
|
|
||||||
|
// These don't map into any SDL controller buttons directly
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_C,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Z,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_3,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_4,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_5,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_6,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_7,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_8,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_9,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_10,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_11,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_12,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_13,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_14,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_15,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_16,
|
||||||
|
};
|
||||||
|
int[] masks = new int[] {
|
||||||
|
(1 << 0), // A -> A
|
||||||
|
(1 << 1), // B -> B
|
||||||
|
(1 << 2), // X -> X
|
||||||
|
(1 << 3), // Y -> Y
|
||||||
|
(1 << 4), // BACK -> BACK
|
||||||
|
(1 << 6), // MENU -> START
|
||||||
|
(1 << 5), // MODE -> GUIDE
|
||||||
|
(1 << 6), // START -> START
|
||||||
|
(1 << 7), // THUMBL -> LEFTSTICK
|
||||||
|
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||||
|
(1 << 9), // L1 -> LEFTSHOULDER
|
||||||
|
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||||
|
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||||
|
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||||
|
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||||
|
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||||
|
(1 << 4), // SELECT -> BACK
|
||||||
|
(1 << 0), // DPAD_CENTER -> A
|
||||||
|
(1 << 15), // L2 -> ??
|
||||||
|
(1 << 16), // R2 -> ??
|
||||||
|
(1 << 17), // C -> ??
|
||||||
|
(1 << 18), // Z -> ??
|
||||||
|
(1 << 20), // 1 -> ??
|
||||||
|
(1 << 21), // 2 -> ??
|
||||||
|
(1 << 22), // 3 -> ??
|
||||||
|
(1 << 23), // 4 -> ??
|
||||||
|
(1 << 24), // 5 -> ??
|
||||||
|
(1 << 25), // 6 -> ??
|
||||||
|
(1 << 26), // 7 -> ??
|
||||||
|
(1 << 27), // 8 -> ??
|
||||||
|
(1 << 28), // 9 -> ??
|
||||||
|
(1 << 29), // 10 -> ??
|
||||||
|
(1 << 30), // 11 -> ??
|
||||||
|
(1 << 31), // 12 -> ??
|
||||||
|
// We're out of room...
|
||||||
|
0xFFFFFFFF, // 13 -> ??
|
||||||
|
0xFFFFFFFF, // 14 -> ??
|
||||||
|
0xFFFFFFFF, // 15 -> ??
|
||||||
|
0xFFFFFFFF, // 16 -> ??
|
||||||
|
};
|
||||||
|
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||||
|
for (int i = 0; i < keys.length; ++i) {
|
||||||
|
if (has_keys[i]) {
|
||||||
|
button_mask |= masks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return button_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||||
|
@Override
|
||||||
|
public void run(int device_id, float intensity, int length) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||||
|
if (intensity == 0.0f) {
|
||||||
|
stop(device_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vibeValue = Math.round(intensity * 255);
|
||||||
|
|
||||||
|
if (vibeValue > 255) {
|
||||||
|
vibeValue = 255;
|
||||||
|
}
|
||||||
|
if (vibeValue < 1) {
|
||||||
|
stop(device_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||||
|
// something went horribly wrong with the Android 8.0 APIs.
|
||||||
|
haptic.vib.vibrate(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLHapticHandler {
|
||||||
|
|
||||||
|
static class SDLHaptic {
|
||||||
|
public int device_id;
|
||||||
|
public String name;
|
||||||
|
public Vibrator vib;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<SDLHaptic> mHaptics;
|
||||||
|
|
||||||
|
public SDLHapticHandler() {
|
||||||
|
mHaptics = new ArrayList<SDLHaptic>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(int device_id, float intensity, int length) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
haptic.vib.vibrate(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(int device_id) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
haptic.vib.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pollHapticDevices() {
|
||||||
|
|
||||||
|
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||||
|
boolean hasVibratorService = false;
|
||||||
|
|
||||||
|
int[] deviceIds = InputDevice.getDeviceIds();
|
||||||
|
// It helps processing the device ids in reverse order
|
||||||
|
// For example, in the case of the XBox 360 wireless dongle,
|
||||||
|
// so the first controller seen by SDL matches what the receiver
|
||||||
|
// considers to be the first controller
|
||||||
|
|
||||||
|
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||||
|
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||||
|
if (haptic == null) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||||
|
Vibrator vib = device.getVibrator();
|
||||||
|
if (vib.hasVibrator()) {
|
||||||
|
haptic = new SDLHaptic();
|
||||||
|
haptic.device_id = deviceIds[i];
|
||||||
|
haptic.name = device.getName();
|
||||||
|
haptic.vib = vib;
|
||||||
|
mHaptics.add(haptic);
|
||||||
|
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check VIBRATOR_SERVICE */
|
||||||
|
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
if (vib != null) {
|
||||||
|
hasVibratorService = vib.hasVibrator();
|
||||||
|
|
||||||
|
if (hasVibratorService) {
|
||||||
|
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||||
|
if (haptic == null) {
|
||||||
|
haptic = new SDLHaptic();
|
||||||
|
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||||
|
haptic.name = "VIBRATOR_SERVICE";
|
||||||
|
haptic.vib = vib;
|
||||||
|
mHaptics.add(haptic);
|
||||||
|
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check removed devices */
|
||||||
|
ArrayList<Integer> removedDevices = null;
|
||||||
|
for (SDLHaptic haptic : mHaptics) {
|
||||||
|
int device_id = haptic.device_id;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < deviceIds.length; i++) {
|
||||||
|
if (device_id == deviceIds[i]) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||||
|
if (i == deviceIds.length) {
|
||||||
|
if (removedDevices == null) {
|
||||||
|
removedDevices = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
removedDevices.add(device_id);
|
||||||
|
}
|
||||||
|
} // else: don't remove the vibrator if it is still present
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedDevices != null) {
|
||||||
|
for (int device_id : removedDevices) {
|
||||||
|
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||||
|
for (int i = 0; i < mHaptics.size(); i++) {
|
||||||
|
if (mHaptics.get(i).device_id == device_id) {
|
||||||
|
mHaptics.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SDLHaptic getHaptic(int device_id) {
|
||||||
|
for (SDLHaptic haptic : mHaptics) {
|
||||||
|
if (haptic.device_id == device_id) {
|
||||||
|
return haptic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
float x, y;
|
||||||
|
int action;
|
||||||
|
|
||||||
|
switch ( event.getSource() ) {
|
||||||
|
case InputDevice.SOURCE_JOYSTICK:
|
||||||
|
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reclaimRelativeMouseModeIfNeeded()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
|
||||||
|
private boolean mRelativeModeEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
|
||||||
|
// Handle relative mouse mode
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||||
|
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||||
|
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||||
|
return super.onGenericMotion(v, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return mRelativeModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
mRelativeModeEnabled = enabled;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||||
|
} else {
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||||
|
} else {
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
private boolean mRelativeModeEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
float x, y;
|
||||||
|
int action;
|
||||||
|
|
||||||
|
switch ( event.getSource() ) {
|
||||||
|
case InputDevice.SOURCE_JOYSTICK:
|
||||||
|
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE:
|
||||||
|
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||||
|
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return mRelativeModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||||
|
if (enabled) {
|
||||||
|
SDLActivity.getContentView().requestPointerCapture();
|
||||||
|
} else {
|
||||||
|
SDLActivity.getContentView().releasePointerCapture();
|
||||||
|
}
|
||||||
|
mRelativeModeEnabled = enabled;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reclaimRelativeMouseModeIfNeeded()
|
||||||
|
{
|
||||||
|
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||||
|
SDLActivity.getContentView().requestPointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
// Relative mouse in capture mode will only have relative for X/Y
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
// Relative mouse in capture mode will only have relative for X/Y
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,405 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||||
|
in order to do anything useful.
|
||||||
|
|
||||||
|
Because of this, that's where we set up the SDL thread
|
||||||
|
*/
|
||||||
|
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||||
|
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||||
|
|
||||||
|
// Sensors
|
||||||
|
protected SensorManager mSensorManager;
|
||||||
|
protected Display mDisplay;
|
||||||
|
|
||||||
|
// Keep track of the surface size to normalize touch events
|
||||||
|
protected float mWidth, mHeight;
|
||||||
|
|
||||||
|
// Is SurfaceView ready for rendering
|
||||||
|
public boolean mIsSurfaceReady;
|
||||||
|
|
||||||
|
// Startup
|
||||||
|
public SDLSurface(Context context) {
|
||||||
|
super(context);
|
||||||
|
getHolder().addCallback(this);
|
||||||
|
|
||||||
|
setFocusable(true);
|
||||||
|
setFocusableInTouchMode(true);
|
||||||
|
requestFocus();
|
||||||
|
setOnKeyListener(this);
|
||||||
|
setOnTouchListener(this);
|
||||||
|
|
||||||
|
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||||
|
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
|
||||||
|
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||||
|
|
||||||
|
// Some arbitrary defaults to avoid a potential division by zero
|
||||||
|
mWidth = 1.0f;
|
||||||
|
mHeight = 1.0f;
|
||||||
|
|
||||||
|
mIsSurfaceReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePause() {
|
||||||
|
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleResume() {
|
||||||
|
setFocusable(true);
|
||||||
|
setFocusableInTouchMode(true);
|
||||||
|
requestFocus();
|
||||||
|
setOnKeyListener(this);
|
||||||
|
setOnTouchListener(this);
|
||||||
|
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Surface getNativeSurface() {
|
||||||
|
return getHolder().getSurface();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when we have a valid drawing surface
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
Log.v("SDL", "surfaceCreated()");
|
||||||
|
SDLActivity.onNativeSurfaceCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when we lose the surface
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
Log.v("SDL", "surfaceDestroyed()");
|
||||||
|
|
||||||
|
// Transition to pause, if needed
|
||||||
|
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||||
|
SDLActivity.handleNativeState();
|
||||||
|
|
||||||
|
mIsSurfaceReady = false;
|
||||||
|
SDLActivity.onNativeSurfaceDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the surface is resized
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder holder,
|
||||||
|
int format, int width, int height) {
|
||||||
|
Log.v("SDL", "surfaceChanged()");
|
||||||
|
|
||||||
|
if (SDLActivity.mSingleton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
int nDeviceWidth = width;
|
||||||
|
int nDeviceHeight = height;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||||
|
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||||
|
mDisplay.getRealMetrics( realMetrics );
|
||||||
|
nDeviceWidth = realMetrics.widthPixels;
|
||||||
|
nDeviceHeight = realMetrics.heightPixels;
|
||||||
|
}
|
||||||
|
} catch(Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(SDLActivity.getContext()) {
|
||||||
|
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||||
|
SDLActivity.getContext().notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||||
|
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||||
|
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||||
|
SDLActivity.onNativeResize();
|
||||||
|
|
||||||
|
// Prevent a screen distortion glitch,
|
||||||
|
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||||
|
boolean skip = false;
|
||||||
|
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||||
|
|
||||||
|
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||||
|
if (mWidth > mHeight) {
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||||
|
if (mWidth < mHeight) {
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special Patch for Square Resolution: Black Berry Passport
|
||||||
|
if (skip) {
|
||||||
|
double min = Math.min(mWidth, mHeight);
|
||||||
|
double max = Math.max(mWidth, mHeight);
|
||||||
|
|
||||||
|
if (max / min < 1.20) {
|
||||||
|
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||||
|
skip = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't skip in MultiWindow.
|
||||||
|
if (skip) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||||
|
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||||
|
Log.v("SDL", "Don't skip in Multi-Window");
|
||||||
|
skip = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip) {
|
||||||
|
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||||
|
mIsSurfaceReady = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||||
|
SDLActivity.onNativeSurfaceChanged();
|
||||||
|
|
||||||
|
/* Surface is ready */
|
||||||
|
mIsSurfaceReady = true;
|
||||||
|
|
||||||
|
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||||
|
SDLActivity.handleNativeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key events
|
||||||
|
@Override
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch events
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||||
|
int touchDevId = event.getDeviceId();
|
||||||
|
final int pointerCount = event.getPointerCount();
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
int pointerFingerId;
|
||||||
|
int i = -1;
|
||||||
|
float x,y,p;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||||
|
* Appears when using Android emulator, eg:
|
||||||
|
* adb shell input mouse tap 100 100
|
||||||
|
* adb shell input touchscreen tap 100 100
|
||||||
|
*/
|
||||||
|
if (touchDevId < 0) {
|
||||||
|
touchDevId -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12290 = Samsung DeX mode desktop mouse
|
||||||
|
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||||
|
// 0x2 = SOURCE_CLASS_POINTER
|
||||||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||||
|
int mouseButton = 1;
|
||||||
|
try {
|
||||||
|
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||||
|
if (object != null) {
|
||||||
|
mouseButton = (Integer) object;
|
||||||
|
}
|
||||||
|
} catch(Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||||
|
// if we are. We'll leverage our existing mouse motion listener
|
||||||
|
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||||
|
x = motionListener.getEventX(event);
|
||||||
|
y = motionListener.getEventY(event);
|
||||||
|
|
||||||
|
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||||
|
} else {
|
||||||
|
switch(action) {
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
for (i = 0; i < pointerCount; i++) {
|
||||||
|
pointerFingerId = event.getPointerId(i);
|
||||||
|
x = event.getX(i) / mWidth;
|
||||||
|
y = event.getY(i) / mHeight;
|
||||||
|
p = event.getPressure(i);
|
||||||
|
if (p > 1.0f) {
|
||||||
|
// may be larger than 1.0f on some devices
|
||||||
|
// see the documentation of getPressure(i)
|
||||||
|
p = 1.0f;
|
||||||
|
}
|
||||||
|
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
// Primary pointer up/down, the index is always zero
|
||||||
|
i = 0;
|
||||||
|
/* fallthrough */
|
||||||
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
|
// Non primary pointer up/down
|
||||||
|
if (i == -1) {
|
||||||
|
i = event.getActionIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerFingerId = event.getPointerId(i);
|
||||||
|
x = event.getX(i) / mWidth;
|
||||||
|
y = event.getY(i) / mHeight;
|
||||||
|
p = event.getPressure(i);
|
||||||
|
if (p > 1.0f) {
|
||||||
|
// may be larger than 1.0f on some devices
|
||||||
|
// see the documentation of getPressure(i)
|
||||||
|
p = 1.0f;
|
||||||
|
}
|
||||||
|
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
for (i = 0; i < pointerCount; i++) {
|
||||||
|
pointerFingerId = event.getPointerId(i);
|
||||||
|
x = event.getX(i) / mWidth;
|
||||||
|
y = event.getY(i) / mHeight;
|
||||||
|
p = event.getPressure(i);
|
||||||
|
if (p > 1.0f) {
|
||||||
|
// may be larger than 1.0f on some devices
|
||||||
|
// see the documentation of getPressure(i)
|
||||||
|
p = 1.0f;
|
||||||
|
}
|
||||||
|
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sensor events
|
||||||
|
public void enableSensor(int sensortype, boolean enabled) {
|
||||||
|
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||||
|
if (enabled) {
|
||||||
|
mSensorManager.registerListener(this,
|
||||||
|
mSensorManager.getDefaultSensor(sensortype),
|
||||||
|
SensorManager.SENSOR_DELAY_GAME, null);
|
||||||
|
} else {
|
||||||
|
mSensorManager.unregisterListener(this,
|
||||||
|
mSensorManager.getDefaultSensor(sensortype));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||||
|
|
||||||
|
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||||
|
// We thus should check here.
|
||||||
|
int newOrientation;
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
switch (mDisplay.getRotation()) {
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
x = -event.values[1];
|
||||||
|
y = event.values[0];
|
||||||
|
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
x = event.values[1];
|
||||||
|
y = -event.values[0];
|
||||||
|
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
x = -event.values[0];
|
||||||
|
y = -event.values[1];
|
||||||
|
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
default:
|
||||||
|
x = event.values[0];
|
||||||
|
y = event.values[1];
|
||||||
|
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||||
|
SDLActivity.mCurrentOrientation = newOrientation;
|
||||||
|
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||||
|
y / SensorManager.GRAVITY_EARTH,
|
||||||
|
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captured pointer events for API 26.
|
||||||
|
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||||
|
{
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||||
|
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||||
|
|
||||||
|
// Change our action value to what SDL's code expects.
|
||||||
|
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||||
|
action = MotionEvent.ACTION_DOWN;
|
||||||
|
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||||
|
action = MotionEvent.ACTION_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
int button = event.getButtonState();
|
||||||
|
|
||||||
|
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -490,6 +490,9 @@ end
|
||||||
|
|
||||||
|
|
||||||
function table.insert_all(t, other)
|
function table.insert_all(t, other)
|
||||||
|
if table.move then -- LuaJIT
|
||||||
|
return table.move(other, 1, #other, #t + 1, t)
|
||||||
|
end
|
||||||
for i=1, #other do
|
for i=1, #other do
|
||||||
t[#t + 1] = other[i]
|
t[#t + 1] = other[i]
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,6 +39,7 @@ core.features = {
|
||||||
dynamic_add_media_filepath = true,
|
dynamic_add_media_filepath = true,
|
||||||
lsystem_decoration_type = true,
|
lsystem_decoration_type = true,
|
||||||
item_meta_range = true,
|
item_meta_range = true,
|
||||||
|
node_interaction_actor = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
function core.has_feature(arg)
|
function core.has_feature(arg)
|
||||||
|
|
|
@ -26,7 +26,15 @@ do
|
||||||
core.print = nil -- don't pollute our namespace
|
core.print = nil -- don't pollute our namespace
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
math.randomseed(os.time())
|
|
||||||
|
do
|
||||||
|
-- Note that PUC Lua just calls srand() which is already initialized by C++,
|
||||||
|
-- but we don't want to rely on this implementation detail.
|
||||||
|
local seed = 1048576 * (os.time() % 1048576)
|
||||||
|
seed = seed + core.get_us_time() % 1048576
|
||||||
|
math.randomseed(seed)
|
||||||
|
end
|
||||||
|
|
||||||
minetest = core
|
minetest = core
|
||||||
|
|
||||||
-- Load other files
|
-- Load other files
|
||||||
|
|
|
@ -18,6 +18,26 @@
|
||||||
-- Global menu data
|
-- Global menu data
|
||||||
menudata = {}
|
menudata = {}
|
||||||
|
|
||||||
|
-- located in user cache path, for remembering this like e.g. last update check
|
||||||
|
cache_settings = Settings(core.get_cache_path() .. DIR_DELIM .. "common.conf")
|
||||||
|
|
||||||
|
--- Checks if the given key contains a timestamp less than a certain age.
|
||||||
|
--- Pair this with a call to `cache_settings:set(key, tostring(os.time()))`
|
||||||
|
--- after successfully refreshing the cache.
|
||||||
|
--- @param key Name of entry in cache_settings
|
||||||
|
--- @param max_age Age to check against, in seconds
|
||||||
|
--- @return true if the max age is not reached
|
||||||
|
function check_cache_age(key, max_age)
|
||||||
|
local time_now = os.time()
|
||||||
|
local time_checked = tonumber(cache_settings:get(key)) or 0
|
||||||
|
return time_now - time_checked < max_age
|
||||||
|
end
|
||||||
|
|
||||||
|
function core.on_before_close()
|
||||||
|
-- called before the menu is closed, either exit or to join a game
|
||||||
|
cache_settings:write()
|
||||||
|
end
|
||||||
|
|
||||||
-- Local cached values
|
-- Local cached values
|
||||||
local min_supp_proto, max_supp_proto
|
local min_supp_proto, max_supp_proto
|
||||||
|
|
||||||
|
@ -27,6 +47,16 @@ function common_update_cached_supp_proto()
|
||||||
end
|
end
|
||||||
common_update_cached_supp_proto()
|
common_update_cached_supp_proto()
|
||||||
|
|
||||||
|
-- Other global functions
|
||||||
|
|
||||||
|
function core.sound_stop(handle, ...)
|
||||||
|
return handle:stop(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function os.tmpname()
|
||||||
|
error('do not use') -- instead: core.get_temp_path()
|
||||||
|
end
|
||||||
|
|
||||||
-- Menu helper functions
|
-- Menu helper functions
|
||||||
|
|
||||||
local function render_client_count(n)
|
local function render_client_count(n)
|
||||||
|
@ -140,11 +170,6 @@ function render_serverlist_row(spec)
|
||||||
|
|
||||||
return table.concat(details, ",")
|
return table.concat(details, ",")
|
||||||
end
|
end
|
||||||
---------------------------------------------------------------------------------
|
|
||||||
os.tmpname = function()
|
|
||||||
error('do not use') -- instead use core.get_temp_path()
|
|
||||||
end
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function menu_render_worldlist()
|
function menu_render_worldlist()
|
||||||
local retval = {}
|
local retval = {}
|
||||||
|
|
|
@ -98,15 +98,12 @@ local function download_and_extract(param)
|
||||||
|
|
||||||
local tempfolder = core.get_temp_path()
|
local tempfolder = core.get_temp_path()
|
||||||
if tempfolder ~= "" then
|
if tempfolder ~= "" then
|
||||||
tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
|
|
||||||
if not core.extract_zip(filename, tempfolder) then
|
if not core.extract_zip(filename, tempfolder) then
|
||||||
tempfolder = nil
|
tempfolder = nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
tempfolder = nil
|
|
||||||
end
|
end
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
if not tempfolder then
|
if not tempfolder or tempfolder == "" then
|
||||||
return {
|
return {
|
||||||
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
|
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,23 @@ if not core.get_http_api then
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
assert(core.create_dir(core.get_cache_path() .. DIR_DELIM .. "cdb"))
|
||||||
|
local cache_file_path = core.get_cache_path() .. DIR_DELIM .. "cdb" .. DIR_DELIM .. "updates.json"
|
||||||
local has_fetched = false
|
local has_fetched = false
|
||||||
local latest_releases
|
local latest_releases
|
||||||
do
|
do
|
||||||
local tmp = core.get_once("cdb_latest_releases")
|
if check_cache_age("cdb_updates_last_checked", 3 * 3600) then
|
||||||
if tmp then
|
local f = io.open(cache_file_path, "r")
|
||||||
latest_releases = core.deserialize(tmp, true)
|
local data = ""
|
||||||
has_fetched = latest_releases ~= nil
|
if f then
|
||||||
|
data = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
data = data ~= "" and core.parse_json(data) or nil
|
||||||
|
if type(data) == "table" then
|
||||||
|
latest_releases = data
|
||||||
|
has_fetched = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -97,7 +107,8 @@ local function fetch()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
latest_releases = lowercase_keys(releases)
|
latest_releases = lowercase_keys(releases)
|
||||||
core.set_once("cdb_latest_releases", core.serialize(latest_releases))
|
core.safe_file_write(cache_file_path, core.write_json(latest_releases))
|
||||||
|
cache_settings:set("cdb_updates_last_checked", tostring(os.time()))
|
||||||
|
|
||||||
if update_detector.get_count() > 0 then
|
if update_detector.get_count() > 0 then
|
||||||
local maintab = ui.find_by_name("maintab")
|
local maintab = ui.find_by_name("maintab")
|
||||||
|
|
|
@ -15,15 +15,30 @@
|
||||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
---- IMPORTANT ----
|
||||||
|
-- This whole file can be removed after a while.
|
||||||
|
-- It was only directly useful for upgrades from 5.7.0 to 5.8.0, but
|
||||||
|
-- maybe some odd fellow directly upgrades from 5.6.1 to 5.9.0 in the future...
|
||||||
|
-- see <https://github.com/minetest/minetest/pull/13850> in case it's not obvious
|
||||||
|
---- ----
|
||||||
|
|
||||||
|
local SETTING_NAME = "no_mtg_notification"
|
||||||
|
|
||||||
function check_reinstall_mtg()
|
function check_reinstall_mtg()
|
||||||
if core.settings:get_bool("no_mtg_notification") then
|
-- used to be in minetest.conf
|
||||||
|
if core.settings:get_bool(SETTING_NAME) then
|
||||||
|
cache_settings:set_bool(SETTING_NAME, true)
|
||||||
|
core.settings:remove(SETTING_NAME)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cache_settings:get_bool(SETTING_NAME) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local games = core.get_games()
|
local games = core.get_games()
|
||||||
for _, game in ipairs(games) do
|
for _, game in ipairs(games) do
|
||||||
if game.id == "minetest" then
|
if game.id == "minetest" then
|
||||||
core.settings:set_bool("no_mtg_notification", true)
|
cache_settings:set_bool(SETTING_NAME, true)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -37,7 +52,7 @@ function check_reinstall_mtg()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not mtg_world_found then
|
if not mtg_world_found then
|
||||||
core.settings:set_bool("no_mtg_notification", true)
|
cache_settings:set_bool(SETTING_NAME, true)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,7 +102,7 @@ local function buttonhandler(this, fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
if fields.dismiss then
|
if fields.dismiss then
|
||||||
core.settings:set_bool("no_mtg_notification", true)
|
cache_settings:set_bool("no_mtg_notification", true)
|
||||||
this:delete()
|
this:delete()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,12 +51,13 @@ end
|
||||||
local function version_info_buttonhandler(this, fields)
|
local function version_info_buttonhandler(this, fields)
|
||||||
if fields.version_check_remind then
|
if fields.version_check_remind then
|
||||||
-- Erase last known, user will be reminded again at next check
|
-- Erase last known, user will be reminded again at next check
|
||||||
core.settings:set("update_last_known", "")
|
cache_settings:set("update_last_known", "")
|
||||||
this:delete()
|
this:delete()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if fields.version_check_never then
|
if fields.version_check_never then
|
||||||
core.settings:set("update_last_checked", "disabled")
|
-- clear checked URL
|
||||||
|
core.settings:set("update_information_url", "")
|
||||||
this:delete()
|
this:delete()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -116,7 +117,7 @@ local function on_version_info_received(json)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local known_update = tonumber(core.settings:get("update_last_known")) or 0
|
local known_update = tonumber(cache_settings:get("update_last_known")) or 0
|
||||||
|
|
||||||
-- Format: MMNNPPP (Major, Minor, Patch)
|
-- Format: MMNNPPP (Major, Minor, Patch)
|
||||||
local new_number = type(json.latest) == "table" and json.latest.version_code
|
local new_number = type(json.latest) == "table" and json.latest.version_code
|
||||||
|
@ -135,7 +136,7 @@ local function on_version_info_received(json)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
core.settings:set("update_last_known", tostring(new_number))
|
cache_settings:set("update_last_known", tostring(new_number))
|
||||||
|
|
||||||
-- Show version info dialog (once)
|
-- Show version info dialog (once)
|
||||||
maintab:hide()
|
maintab:hide()
|
||||||
|
@ -149,20 +150,20 @@ end
|
||||||
|
|
||||||
function check_new_version()
|
function check_new_version()
|
||||||
local url = core.settings:get("update_information_url")
|
local url = core.settings:get("update_information_url")
|
||||||
if core.settings:get("update_last_checked") == "disabled" or
|
if url == "" then
|
||||||
url == "" then
|
|
||||||
-- Never show any updates
|
-- Never show any updates
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_now = os.time()
|
-- every 2 days
|
||||||
local time_checked = tonumber(core.settings:get("update_last_checked")) or 0
|
if check_cache_age("update_last_checked", 2 * 24 * 3600) then
|
||||||
if time_now - time_checked < 2 * 24 * 3600 then
|
|
||||||
-- Check interval of 2 entire days
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
cache_settings:set("update_last_checked", tostring(os.time()))
|
||||||
|
|
||||||
core.settings:set("update_last_checked", tostring(time_now))
|
-- Clean old leftovers (this can be removed after 5.9.0 or so)
|
||||||
|
core.settings:remove("update_last_checked")
|
||||||
|
core.settings:remove("update_last_known")
|
||||||
|
|
||||||
core.handle_async(function(params)
|
core.handle_async(function(params)
|
||||||
local http = core.get_http_api()
|
local http = core.get_http_api()
|
||||||
|
|
|
@ -28,8 +28,6 @@ local basepath = core.get_builtin_path()
|
||||||
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||||
|
|
||||||
dofile(menupath .. DIR_DELIM .. "misc.lua")
|
|
||||||
|
|
||||||
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
||||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
||||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
|
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
-- old non-method sound function
|
|
||||||
|
|
||||||
function core.sound_stop(handle, ...)
|
|
||||||
return handle:stop(...)
|
|
||||||
end
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
serverlistmgr = {
|
serverlistmgr = {
|
||||||
-- continent code we detected for ourselves
|
-- continent code we detected for ourselves
|
||||||
my_continent = core.get_once("continent"),
|
my_continent = nil,
|
||||||
|
|
||||||
-- list of locally favorites servers
|
-- list of locally favorites servers
|
||||||
favorites = nil,
|
favorites = nil,
|
||||||
|
@ -26,6 +26,15 @@ serverlistmgr = {
|
||||||
servers = nil,
|
servers = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
if check_cache_age("geoip_last_checked", 3600) then
|
||||||
|
local tmp = cache_settings:get("geoip") or ""
|
||||||
|
if tmp:match("^[A-Z][A-Z]$") then
|
||||||
|
serverlistmgr.my_continent = tmp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Efficient data structure for normalizing arbitrary scores attached to objects
|
-- Efficient data structure for normalizing arbitrary scores attached to objects
|
||||||
-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}}
|
-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}}
|
||||||
|
@ -71,7 +80,8 @@ local WEIGHT_SORT = 2
|
||||||
-- how much the estimated latency contributes to the final ranking
|
-- how much the estimated latency contributes to the final ranking
|
||||||
local WEIGHT_LATENCY = 1
|
local WEIGHT_LATENCY = 1
|
||||||
|
|
||||||
local function order_server_list(list)
|
--- @param list of servers, will be modified.
|
||||||
|
local function order_server_list_internal(list)
|
||||||
-- calculate the scores
|
-- calculate the scores
|
||||||
local s1 = Normalizer:new()
|
local s1 = Normalizer:new()
|
||||||
local s2 = Normalizer:new()
|
local s2 = Normalizer:new()
|
||||||
|
@ -90,28 +100,58 @@ local function order_server_list(list)
|
||||||
s1 = s1:calc()
|
s1 = s1:calc()
|
||||||
s2 = s2:calc()
|
s2 = s2:calc()
|
||||||
|
|
||||||
-- make a shallow copy and pre-calculate ordering
|
-- pre-calculate ordering
|
||||||
local res, order = {}, {}
|
local order = {}
|
||||||
for i = 1, #list do
|
for _, fav in ipairs(list) do
|
||||||
local fav = list[i]
|
order[fav] = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
|
||||||
res[i] = fav
|
|
||||||
|
|
||||||
local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
|
|
||||||
order[fav] = n
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now sort the list
|
-- now sort the list
|
||||||
table.sort(res, function(fav1, fav2)
|
table.sort(list, function(fav1, fav2)
|
||||||
return order[fav1] > order[fav2]
|
return order[fav1] > order[fav2]
|
||||||
end)
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
return res
|
local function order_server_list(list)
|
||||||
|
-- split the list into two parts and sort them separately, to keep empty
|
||||||
|
-- servers at the bottom.
|
||||||
|
local nonempty, empty = {}, {}
|
||||||
|
|
||||||
|
for _, fav in ipairs(list) do
|
||||||
|
if (fav.clients or 0) > 0 then
|
||||||
|
table.insert(nonempty, fav)
|
||||||
|
else
|
||||||
|
table.insert(empty, fav)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
order_server_list_internal(nonempty)
|
||||||
|
order_server_list_internal(empty)
|
||||||
|
|
||||||
|
table.insert_all(nonempty, empty)
|
||||||
|
return nonempty
|
||||||
end
|
end
|
||||||
|
|
||||||
local public_downloading = false
|
local public_downloading = false
|
||||||
local geoip_downloading = false
|
local geoip_downloading = false
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
local function fetch_geoip()
|
||||||
|
local http = core.get_http_api()
|
||||||
|
local url = core.settings:get("serverlist_url") .. "/geoip"
|
||||||
|
|
||||||
|
local response = http.fetch_sync({ url = url })
|
||||||
|
if not response.succeeded then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local retval = core.parse_json(response.data)
|
||||||
|
if type(retval) ~= "table" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return type(retval.continent) == "string" and retval.continent
|
||||||
|
end
|
||||||
|
|
||||||
function serverlistmgr.sync()
|
function serverlistmgr.sync()
|
||||||
if not serverlistmgr.servers then
|
if not serverlistmgr.servers then
|
||||||
serverlistmgr.servers = {{
|
serverlistmgr.servers = {{
|
||||||
|
@ -129,37 +169,23 @@ function serverlistmgr.sync()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- only fetched once per MT instance
|
|
||||||
if not serverlistmgr.my_continent and not geoip_downloading then
|
if not serverlistmgr.my_continent and not geoip_downloading then
|
||||||
geoip_downloading = true
|
geoip_downloading = true
|
||||||
core.handle_async(
|
core.handle_async(fetch_geoip, nil, function(result)
|
||||||
function(param)
|
geoip_downloading = false
|
||||||
local http = core.get_http_api()
|
if not result then
|
||||||
local url = core.settings:get("serverlist_url") .. "/geoip"
|
return
|
||||||
|
|
||||||
local response = http.fetch_sync({ url = url })
|
|
||||||
if not response.succeeded then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local retval = core.parse_json(response.data)
|
|
||||||
return retval and type(retval.continent) == "string" and retval.continent
|
|
||||||
end,
|
|
||||||
nil,
|
|
||||||
function(result)
|
|
||||||
geoip_downloading = false
|
|
||||||
if not result then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
serverlistmgr.my_continent = result
|
|
||||||
core.set_once("continent", result)
|
|
||||||
-- reorder list if we already have it
|
|
||||||
if serverlistmgr.servers then
|
|
||||||
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
|
|
||||||
core.event_handler("Refresh")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
)
|
serverlistmgr.my_continent = result
|
||||||
|
cache_settings:set("geoip", result)
|
||||||
|
cache_settings:set("geoip_last_checked", tostring(os.time()))
|
||||||
|
|
||||||
|
-- re-sort list if applicable
|
||||||
|
if serverlistmgr.servers then
|
||||||
|
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
|
||||||
|
core.event_handler("Refresh")
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if public_downloading then
|
if public_downloading then
|
||||||
|
@ -167,6 +193,7 @@ function serverlistmgr.sync()
|
||||||
end
|
end
|
||||||
public_downloading = true
|
public_downloading = true
|
||||||
|
|
||||||
|
-- note: this isn't cached because it's way too dynamic
|
||||||
core.handle_async(
|
core.handle_async(
|
||||||
function(param)
|
function(param)
|
||||||
local http = core.get_http_api()
|
local http = core.get_http_api()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
_G.core = {get_once = function(_) end}
|
_G.core = {}
|
||||||
_G.unpack = table.unpack
|
_G.unpack = table.unpack
|
||||||
|
_G.check_cache_age = function() return false end
|
||||||
_G.serverlistmgr = {}
|
_G.serverlistmgr = {}
|
||||||
|
|
||||||
dofile("builtin/common/vector.lua")
|
dofile("builtin/common/vector.lua")
|
||||||
|
|
|
@ -114,7 +114,11 @@ always_fly_fast (Always fly fast) bool true
|
||||||
# the place button.
|
# the place button.
|
||||||
#
|
#
|
||||||
# Requires: keyboard_mouse
|
# Requires: keyboard_mouse
|
||||||
repeat_place_time (Place repetition interval) float 0.25 0.16 2
|
repeat_place_time (Place repetition interval) float 0.25 0.15 2.0
|
||||||
|
|
||||||
|
# The minimum time in seconds it takes between digging nodes when holding
|
||||||
|
# the dig button.
|
||||||
|
repeat_dig_time (Dig repetition interval) float 0.15 0.15 2.0
|
||||||
|
|
||||||
# Automatically jump up single-node obstacles.
|
# Automatically jump up single-node obstacles.
|
||||||
autojump (Automatic jumping) bool false
|
autojump (Automatic jumping) bool false
|
||||||
|
@ -149,20 +153,23 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
|
||||||
[*Touchscreen]
|
[*Touchscreen]
|
||||||
|
|
||||||
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
|
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
|
||||||
#
|
|
||||||
# Requires: !android
|
|
||||||
enable_touch (Enable touchscreen) bool true
|
enable_touch (Enable touchscreen) bool true
|
||||||
|
|
||||||
# The length in pixels it takes for touchscreen interaction to start.
|
|
||||||
#
|
|
||||||
# Requires: touchscreen_gui
|
|
||||||
touchscreen_threshold (Touchscreen threshold) int 20 0 100
|
|
||||||
|
|
||||||
# Touchscreen sensitivity multiplier.
|
# Touchscreen sensitivity multiplier.
|
||||||
#
|
#
|
||||||
# Requires: touchscreen_gui
|
# Requires: touchscreen_gui
|
||||||
touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0
|
touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0
|
||||||
|
|
||||||
|
# The length in pixels after which a touch interaction is considered movement.
|
||||||
|
#
|
||||||
|
# Requires: touchscreen_gui
|
||||||
|
touchscreen_threshold (Movement threshold) int 20 0 100
|
||||||
|
|
||||||
|
# The delay in milliseconds after which a touch interaction is considered a long tap.
|
||||||
|
#
|
||||||
|
# Requires: touchscreen_gui
|
||||||
|
touch_long_tap_delay (Threshold for long taps) int 400 100 1000
|
||||||
|
|
||||||
# Use crosshair to select object instead of whole screen.
|
# Use crosshair to select object instead of whole screen.
|
||||||
# If enabled, a crosshair will be shown and will be used for selecting object.
|
# If enabled, a crosshair will be shown and will be used for selecting object.
|
||||||
#
|
#
|
||||||
|
@ -181,6 +188,19 @@ fixed_virtual_joystick (Fixed virtual joystick) bool false
|
||||||
# Requires: touchscreen_gui
|
# Requires: touchscreen_gui
|
||||||
virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false
|
virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false
|
||||||
|
|
||||||
|
# The gesture for for punching players/entities.
|
||||||
|
# This can be overridden by games and mods.
|
||||||
|
#
|
||||||
|
# * short_tap
|
||||||
|
# Easy to use and well-known from other games that shall not be named.
|
||||||
|
#
|
||||||
|
# * long_tap
|
||||||
|
# Known from the classic Minetest mobile controls.
|
||||||
|
# Combat is more or less impossible.
|
||||||
|
#
|
||||||
|
# Requires: touchscreen_gui
|
||||||
|
touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap
|
||||||
|
|
||||||
|
|
||||||
[Graphics and Audio]
|
[Graphics and Audio]
|
||||||
|
|
||||||
|
@ -822,6 +842,7 @@ serverlist_url (Serverlist URL) string servers.minetest.net
|
||||||
enable_split_login_register (Enable split login/register) bool true
|
enable_split_login_register (Enable split login/register) bool true
|
||||||
|
|
||||||
# URL to JSON file which provides information about the newest Minetest release
|
# URL to JSON file which provides information about the newest Minetest release
|
||||||
|
# If this is empty the engine will never check for updates.
|
||||||
update_information_url (Update information URL) string https://www.minetest.net/release_info.json
|
update_information_url (Update information URL) string https://www.minetest.net/release_info.json
|
||||||
|
|
||||||
[*Server]
|
[*Server]
|
||||||
|
@ -2337,20 +2358,6 @@ show_advanced (Show advanced settings) bool false
|
||||||
# Changing this setting requires a restart.
|
# Changing this setting requires a restart.
|
||||||
enable_sound (Sound) bool true
|
enable_sound (Sound) bool true
|
||||||
|
|
||||||
# Unix timestamp (integer) of when the client last checked for an update
|
|
||||||
# Set this value to "disabled" to never check for updates.
|
|
||||||
update_last_checked (Last update check) string
|
|
||||||
|
|
||||||
# Version number which was last seen during an update check.
|
|
||||||
#
|
|
||||||
# Representation: MMMIIIPPP, where M=Major, I=Minor, P=Patch
|
|
||||||
# Ex: 5.5.0 is 005005000
|
|
||||||
update_last_known (Last known version update) int 0
|
|
||||||
|
|
||||||
# If this is set to true, the user will never (again) be shown the
|
|
||||||
# "reinstall Minetest Game" notification.
|
|
||||||
no_mtg_notification (Don't show "reinstall Minetest Game" notification) bool false
|
|
||||||
|
|
||||||
# Key for moving the player forward.
|
# Key for moving the player forward.
|
||||||
keymap_forward (Forward key) key KEY_KEY_W
|
keymap_forward (Forward key) key KEY_KEY_W
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../irr/media/Shaders
|
|
@ -1,7 +1,7 @@
|
||||||
uniform vec4 fogColor;
|
uniform lowp vec4 fogColor;
|
||||||
uniform float fogDistance;
|
uniform float fogDistance;
|
||||||
uniform float fogShadingParameter;
|
uniform float fogShadingParameter;
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
|
|
||||||
varying lowp vec4 varColor;
|
varying lowp vec4 varColor;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
uniform vec4 emissiveColor;
|
uniform lowp vec4 emissiveColor;
|
||||||
|
|
||||||
varying lowp vec4 varColor;
|
varying lowp vec4 varColor;
|
||||||
|
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,13 +9,12 @@ uniform sampler2D baseTexture;
|
||||||
uniform vec2 texelSize0;
|
uniform vec2 texelSize0;
|
||||||
|
|
||||||
uniform vec3 dayLight;
|
uniform vec3 dayLight;
|
||||||
uniform vec4 fogColor;
|
uniform lowp vec4 fogColor;
|
||||||
uniform float fogDistance;
|
uniform float fogDistance;
|
||||||
uniform float fogShadingParameter;
|
uniform float fogShadingParameter;
|
||||||
uniform vec3 eyePosition;
|
|
||||||
|
|
||||||
// The cameraOffset is the current center of the visible world.
|
// The cameraOffset is the current center of the visible world.
|
||||||
uniform vec3 cameraOffset;
|
uniform highp vec3 cameraOffset;
|
||||||
uniform float animationTimer;
|
uniform float animationTimer;
|
||||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||||
// shadow texture
|
// shadow texture
|
||||||
|
@ -53,11 +52,8 @@ varying mediump vec2 varTexCoord;
|
||||||
#else
|
#else
|
||||||
centroid varying vec2 varTexCoord;
|
centroid varying vec2 varTexCoord;
|
||||||
#endif
|
#endif
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
varying float nightRatio;
|
varying float nightRatio;
|
||||||
varying vec3 tsEyeVec;
|
|
||||||
varying vec3 lightVec;
|
|
||||||
varying vec3 tsLightVec;
|
|
||||||
|
|
||||||
varying vec3 viewVec;
|
varying vec3 viewVec;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
uniform mat4 mWorld;
|
uniform mat4 mWorld;
|
||||||
// Color of the light emitted by the sun.
|
// Color of the light emitted by the sun.
|
||||||
uniform vec3 dayLight;
|
uniform vec3 dayLight;
|
||||||
uniform vec3 eyePosition;
|
|
||||||
|
|
||||||
// The cameraOffset is the current center of the visible world.
|
// The cameraOffset is the current center of the visible world.
|
||||||
uniform vec3 cameraOffset;
|
uniform highp vec3 cameraOffset;
|
||||||
uniform float animationTimer;
|
uniform float animationTimer;
|
||||||
|
|
||||||
varying vec3 vNormal;
|
varying vec3 vNormal;
|
||||||
|
@ -45,7 +44,7 @@ centroid varying vec2 varTexCoord;
|
||||||
varying float area_enable_parallax;
|
varying float area_enable_parallax;
|
||||||
|
|
||||||
varying vec3 viewVec;
|
varying vec3 viewVec;
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
varying float nightRatio;
|
varying float nightRatio;
|
||||||
// Color of the light emitted by the light sources.
|
// Color of the light emitted by the light sources.
|
||||||
uniform vec3 artificialLight;
|
uniform vec3 artificialLight;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
uniform sampler2D baseTexture;
|
uniform sampler2D baseTexture;
|
||||||
|
|
||||||
uniform vec3 dayLight;
|
uniform vec3 dayLight;
|
||||||
uniform vec4 fogColor;
|
uniform lowp vec4 fogColor;
|
||||||
uniform float fogDistance;
|
uniform float fogDistance;
|
||||||
uniform float fogShadingParameter;
|
uniform float fogShadingParameter;
|
||||||
uniform vec3 eyePosition;
|
|
||||||
|
|
||||||
// The cameraOffset is the current center of the visible world.
|
// The cameraOffset is the current center of the visible world.
|
||||||
uniform vec3 cameraOffset;
|
uniform highp vec3 cameraOffset;
|
||||||
uniform float animationTimer;
|
uniform float animationTimer;
|
||||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||||
// shadow texture
|
// shadow texture
|
||||||
|
@ -45,7 +44,7 @@ varying mediump vec2 varTexCoord;
|
||||||
#else
|
#else
|
||||||
centroid varying vec2 varTexCoord;
|
centroid varying vec2 varTexCoord;
|
||||||
#endif
|
#endif
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
varying float nightRatio;
|
varying float nightRatio;
|
||||||
|
|
||||||
varying float vIDiff;
|
varying float vIDiff;
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
uniform mat4 mWorld;
|
uniform mat4 mWorld;
|
||||||
uniform vec3 dayLight;
|
uniform vec3 dayLight;
|
||||||
uniform vec3 eyePosition;
|
|
||||||
uniform float animationTimer;
|
uniform float animationTimer;
|
||||||
uniform vec4 emissiveColor;
|
uniform lowp vec4 emissiveColor;
|
||||||
uniform vec3 cameraOffset;
|
|
||||||
|
|
||||||
|
|
||||||
varying vec3 vNormal;
|
varying vec3 vNormal;
|
||||||
varying vec3 vPosition;
|
varying vec3 vPosition;
|
||||||
|
@ -33,7 +30,7 @@ centroid varying vec2 varTexCoord;
|
||||||
varying float perspective_factor;
|
varying float perspective_factor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
varying vec3 eyeVec;
|
varying highp vec3 eyeVec;
|
||||||
varying float nightRatio;
|
varying float nightRatio;
|
||||||
// Color of the light emitted by the light sources.
|
// Color of the light emitted by the light sources.
|
||||||
uniform vec3 artificialLight;
|
uniform vec3 artificialLight;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
uniform vec4 emissiveColor;
|
uniform lowp vec4 emissiveColor;
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,3 +25,5 @@ set(VORBIS_LIBRARY ${DEPS}/Vorbis/libvorbis.a)
|
||||||
set(VORBISFILE_LIBRARY ${DEPS}/Vorbis/libvorbisfile.a)
|
set(VORBISFILE_LIBRARY ${DEPS}/Vorbis/libvorbisfile.a)
|
||||||
set(ZSTD_INCLUDE_DIR ${DEPS}/Zstd/include)
|
set(ZSTD_INCLUDE_DIR ${DEPS}/Zstd/include)
|
||||||
set(ZSTD_LIBRARY ${DEPS}/Zstd/libzstd.a)
|
set(ZSTD_LIBRARY ${DEPS}/Zstd/libzstd.a)
|
||||||
|
set(SDL2_INCLUDE_DIRS ${DEPS}/SDL2/include/SDL2)
|
||||||
|
set(SDL2_LIBRARIES ${DEPS}/SDL2/libSDL2.a)
|
||||||
|
|
|
@ -659,8 +659,9 @@ The mask is applied using binary AND.
|
||||||
|
|
||||||
#### `[sheet:<w>x<h>:<x>,<y>`
|
#### `[sheet:<w>x<h>:<x>,<y>`
|
||||||
|
|
||||||
Retrieves a tile at position x,y from the base image
|
Retrieves a tile at position x, y (in tiles, 0-indexed)
|
||||||
which it assumes to be a tilesheet with dimensions w,h.
|
from the base image, which it assumes to be a tilesheet
|
||||||
|
with dimensions w, h (in tiles).
|
||||||
|
|
||||||
#### `[colorize:<color>:<ratio>`
|
#### `[colorize:<color>:<ratio>`
|
||||||
|
|
||||||
|
@ -5431,6 +5432,9 @@ Utilities
|
||||||
lsystem_decoration_type = true,
|
lsystem_decoration_type = true,
|
||||||
-- Overrideable pointing range using the itemstack meta key `"range"` (5.9.0)
|
-- Overrideable pointing range using the itemstack meta key `"range"` (5.9.0)
|
||||||
item_meta_range = true,
|
item_meta_range = true,
|
||||||
|
-- Allow passing an optional "actor" ObjectRef to the following functions:
|
||||||
|
-- minetest.place_node, minetest.dig_node, minetest.punch_node (5.9.0)
|
||||||
|
node_interaction_actor = true,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -6061,13 +6065,16 @@ Environment access
|
||||||
* Returns a number between `0` and `15`
|
* Returns a number between `0` and `15`
|
||||||
* Currently it's the same as `math.floor(param1 / 16)`, except that it
|
* Currently it's the same as `math.floor(param1 / 16)`, except that it
|
||||||
ensures compatibility.
|
ensures compatibility.
|
||||||
* `minetest.place_node(pos, node)`
|
* `minetest.place_node(pos, node[, placer])`
|
||||||
* Place node with the same effects that a player would cause
|
* Place node with the same effects that a player would cause
|
||||||
* `minetest.dig_node(pos)`
|
* `placer`: The ObjectRef that places the node (optional)
|
||||||
|
* `minetest.dig_node(pos[, digger])`
|
||||||
* Dig node with the same effects that a player would cause
|
* Dig node with the same effects that a player would cause
|
||||||
|
* `digger`: The ObjectRef that digs the node (optional)
|
||||||
* Returns `true` if successful, `false` on failure (e.g. protected location)
|
* Returns `true` if successful, `false` on failure (e.g. protected location)
|
||||||
* `minetest.punch_node(pos)`
|
* `minetest.punch_node(pos[, puncher])`
|
||||||
* Punch node with the same effects that a player would cause
|
* Punch node with the same effects that a player would cause
|
||||||
|
* `puncher`: The ObjectRef that punches the node (optional)
|
||||||
* `minetest.spawn_falling_node(pos)`
|
* `minetest.spawn_falling_node(pos)`
|
||||||
* Change node into falling node
|
* Change node into falling node
|
||||||
* Returns `true` and the ObjectRef of the spawned entity if successful, `false` on failure
|
* Returns `true` and the ObjectRef of the spawned entity if successful, `false` on failure
|
||||||
|
@ -7313,8 +7320,41 @@ Global tables
|
||||||
All callbacks registered with [Global callback registration functions] are added
|
All callbacks registered with [Global callback registration functions] are added
|
||||||
to corresponding `minetest.registered_*` tables.
|
to corresponding `minetest.registered_*` tables.
|
||||||
|
|
||||||
|
For historical reasons, the use of an -s suffix in these names is inconsistent.
|
||||||
|
|
||||||
|
* `minetest.registered_on_chat_messages`
|
||||||
|
* `minetest.registered_on_chatcommands`
|
||||||
|
* `minetest.registered_globalsteps`
|
||||||
|
* `minetest.registered_on_punchnodes`
|
||||||
|
* `minetest.registered_on_placenodes`
|
||||||
|
* `minetest.registered_on_dignodes`
|
||||||
|
* `minetest.registered_on_generateds`
|
||||||
|
* `minetest.registered_on_newplayers`
|
||||||
|
* `minetest.registered_on_dieplayers`
|
||||||
|
* `minetest.registered_on_respawnplayers`
|
||||||
|
* `minetest.registered_on_prejoinplayers`
|
||||||
|
* `minetest.registered_on_joinplayers`
|
||||||
|
* `minetest.registered_on_leaveplayers`
|
||||||
|
* `minetest.registered_on_player_receive_fields`
|
||||||
|
* `minetest.registered_on_cheats`
|
||||||
|
* `minetest.registered_on_crafts`
|
||||||
|
* `minetest.registered_craft_predicts`
|
||||||
|
* `minetest.registered_on_item_eats`
|
||||||
|
* `minetest.registered_on_item_pickups`
|
||||||
|
* `minetest.registered_on_punchplayers`
|
||||||
|
* `minetest.registered_on_authplayers`
|
||||||
|
* `minetest.registered_on_player_inventory_actions`
|
||||||
|
* `minetest.registered_allow_player_inventory_actions`
|
||||||
|
* `minetest.registered_on_rightclickplayers`
|
||||||
|
* `minetest.registered_on_mods_loaded`
|
||||||
|
* `minetest.registered_on_shutdown`
|
||||||
|
* `minetest.registered_on_protection_violation`
|
||||||
|
* `minetest.registered_on_priv_grant`
|
||||||
|
* `minetest.registered_on_priv_revoke`
|
||||||
|
* `minetest.registered_can_bypass_userlimit`
|
||||||
|
* `minetest.registered_on_modchannel_message`
|
||||||
|
* `minetest.registered_on_liquid_transformed`
|
||||||
|
* `minetest.registered_on_mapblocks_changed`
|
||||||
|
|
||||||
Class reference
|
Class reference
|
||||||
===============
|
===============
|
||||||
|
@ -7944,8 +7984,12 @@ child will follow movement and rotation of that bone.
|
||||||
* Fourth column: subject looking to the right
|
* Fourth column: subject looking to the right
|
||||||
* Fifth column: subject viewed from above
|
* Fifth column: subject viewed from above
|
||||||
* Sixth column: subject viewed from below
|
* Sixth column: subject viewed from below
|
||||||
* `get_entity_name()` (**Deprecated**: Will be removed in a future version, use the field `self.name` instead)
|
* `get_luaentity()`:
|
||||||
* `get_luaentity()`: returns the object's associated luaentity table
|
* Returns the object's associated luaentity table, if there is one
|
||||||
|
* Otherwise returns `nil` (e.g. for players)
|
||||||
|
* `get_entity_name()`:
|
||||||
|
* **Deprecated**: Will be removed in a future version,
|
||||||
|
use `:get_luaentity().name` instead.
|
||||||
|
|
||||||
#### Player only (no-op for other objects)
|
#### Player only (no-op for other objects)
|
||||||
|
|
||||||
|
@ -8666,7 +8710,8 @@ Player properties need to be saved manually.
|
||||||
-- "mesh" uses the defined mesh model.
|
-- "mesh" uses the defined mesh model.
|
||||||
-- "wielditem" is used for dropped items.
|
-- "wielditem" is used for dropped items.
|
||||||
-- (see builtin/game/item_entity.lua).
|
-- (see builtin/game/item_entity.lua).
|
||||||
-- For this use 'wield_item = itemname' (Deprecated: 'textures = {itemname}').
|
-- For this use 'wield_item = itemname'.
|
||||||
|
-- Setting 'textures = {itemname}' has the same effect, but is deprecated.
|
||||||
-- If the item has a 'wield_image' the object will be an extrusion of
|
-- If the item has a 'wield_image' the object will be an extrusion of
|
||||||
-- that, otherwise:
|
-- that, otherwise:
|
||||||
-- If 'itemname' is a cubic node or nodebox the object will appear
|
-- If 'itemname' is a cubic node or nodebox the object will appear
|
||||||
|
@ -8693,8 +8738,8 @@ Player properties need to be saved manually.
|
||||||
-- "cube" uses 6 textures just like a node, but all 6 must be defined.
|
-- "cube" uses 6 textures just like a node, but all 6 must be defined.
|
||||||
-- "sprite" uses 1 texture.
|
-- "sprite" uses 1 texture.
|
||||||
-- "upright_sprite" uses 2 textures: {front, back}.
|
-- "upright_sprite" uses 2 textures: {front, back}.
|
||||||
-- "wielditem" expects 'textures = {itemname}' (see 'visual' above).
|
|
||||||
-- "mesh" requires one texture for each mesh buffer/material (in order)
|
-- "mesh" requires one texture for each mesh buffer/material (in order)
|
||||||
|
-- Deprecated usage of "wielditem" expects 'textures = {itemname}' (see 'visual' above).
|
||||||
|
|
||||||
colors = {},
|
colors = {},
|
||||||
-- Number of required colors depends on visual
|
-- Number of required colors depends on visual
|
||||||
|
@ -9097,19 +9142,20 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
|
||||||
-- Otherwise should be name of node which the client immediately places
|
-- Otherwise should be name of node which the client immediately places
|
||||||
-- upon digging. Server will always update with actual result shortly.
|
-- upon digging. Server will always update with actual result shortly.
|
||||||
|
|
||||||
touch_interaction = {
|
touch_interaction = <TouchInteractionMode> OR {
|
||||||
-- Only affects touchscreen clients.
|
pointed_nothing = <TouchInteractionMode>,
|
||||||
-- Defines the meaning of short and long taps with the item in hand.
|
pointed_node = <TouchInteractionMode>,
|
||||||
-- The fields in this table have two valid values:
|
pointed_object = <TouchInteractionMode>,
|
||||||
-- * "long_dig_short_place" (long tap = dig, short tap = place)
|
|
||||||
-- * "short_dig_long_place" (short tap = dig, long tap = place)
|
|
||||||
-- The field to be used is selected according to the current
|
|
||||||
-- `pointed_thing`.
|
|
||||||
|
|
||||||
pointed_nothing = "long_dig_short_place",
|
|
||||||
pointed_node = "long_dig_short_place",
|
|
||||||
pointed_object = "short_dig_long_place",
|
|
||||||
},
|
},
|
||||||
|
-- Only affects touchscreen clients.
|
||||||
|
-- Defines the meaning of short and long taps with the item in hand.
|
||||||
|
-- If specified as a table, the field to be used is selected according to
|
||||||
|
-- the current `pointed_thing`.
|
||||||
|
-- There are three possible TouchInteractionMode values:
|
||||||
|
-- * "user" (meaning depends on client-side settings)
|
||||||
|
-- * "long_dig_short_place" (long tap = dig, short tap = place)
|
||||||
|
-- * "short_dig_long_place" (short tap = dig, long tap = place)
|
||||||
|
-- The default value is "user".
|
||||||
|
|
||||||
sound = {
|
sound = {
|
||||||
-- Definition of item sounds to be played at various events.
|
-- Definition of item sounds to be played at various events.
|
||||||
|
|
|
@ -55,10 +55,6 @@ Functions
|
||||||
* Android only. Shares file using the share popup
|
* Android only. Shares file using the share popup
|
||||||
* `core.get_version()` (possible in async calls)
|
* `core.get_version()` (possible in async calls)
|
||||||
* returns current core version
|
* returns current core version
|
||||||
* `core.set_once(key, value)`:
|
|
||||||
* save a string value that persists even if menu is closed
|
|
||||||
* `core.get_once(key)`:
|
|
||||||
* get a string value saved by above function, or `nil`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +93,8 @@ Filesystem
|
||||||
registered in the core (possible in async calls)
|
registered in the core (possible in async calls)
|
||||||
* `core.get_cache_path()` -> path of cache
|
* `core.get_cache_path()` -> path of cache
|
||||||
* `core.get_temp_path([param])` (possible in async calls)
|
* `core.get_temp_path([param])` (possible in async calls)
|
||||||
* `param`=true: returns path to a temporary file
|
* `param`=true: returns path to a newly created temporary file
|
||||||
otherwise: returns path to the temporary folder
|
* otherwise: returns path to a newly created temporary folder
|
||||||
|
|
||||||
|
|
||||||
HTTP Requests
|
HTTP Requests
|
||||||
|
|
|
@ -79,9 +79,6 @@ enum EEVENT_TYPE
|
||||||
*/
|
*/
|
||||||
EET_USER_EVENT,
|
EET_USER_EVENT,
|
||||||
|
|
||||||
//! Pass on raw events from the OS
|
|
||||||
EET_SYSTEM_EVENT,
|
|
||||||
|
|
||||||
//! Application state events like a resume, pause etc.
|
//! Application state events like a resume, pause etc.
|
||||||
EET_APPLICATION_EVENT,
|
EET_APPLICATION_EVENT,
|
||||||
|
|
||||||
|
@ -187,17 +184,6 @@ enum ETOUCH_INPUT_EVENT
|
||||||
ETIE_COUNT
|
ETIE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ESYSTEM_EVENT_TYPE
|
|
||||||
{
|
|
||||||
//! From Android command handler for native activity messages
|
|
||||||
ESET_ANDROID_CMD = 0,
|
|
||||||
|
|
||||||
// TODO: for example ESET_WINDOWS_MESSAGE for win32 message loop events
|
|
||||||
|
|
||||||
//! No real event, but to get number of event types
|
|
||||||
ESET_COUNT
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Enumeration for a commonly used application state events (it's useful mainly for mobile devices)
|
//! Enumeration for a commonly used application state events (it's useful mainly for mobile devices)
|
||||||
enum EAPPLICATION_EVENT_TYPE
|
enum EAPPLICATION_EVENT_TYPE
|
||||||
{
|
{
|
||||||
|
@ -528,25 +514,6 @@ struct SEvent
|
||||||
size_t UserData2;
|
size_t UserData2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Raw events from the OS
|
|
||||||
struct SSystemEvent
|
|
||||||
{
|
|
||||||
//! Android command handler native activity messages.
|
|
||||||
struct SAndroidCmd
|
|
||||||
{
|
|
||||||
//! APP_CMD_ enums defined in android_native_app_glue.h from the Android NDK
|
|
||||||
s32 Cmd;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TOOD: more structs for iphone, Windows, X11, etc.
|
|
||||||
|
|
||||||
ESYSTEM_EVENT_TYPE EventType;
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct SAndroidCmd AndroidCmd;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Application state event
|
// Application state event
|
||||||
struct SApplicationEvent
|
struct SApplicationEvent
|
||||||
{
|
{
|
||||||
|
@ -567,7 +534,6 @@ struct SEvent
|
||||||
struct SJoystickEvent JoystickEvent;
|
struct SJoystickEvent JoystickEvent;
|
||||||
struct SLogEvent LogEvent;
|
struct SLogEvent LogEvent;
|
||||||
struct SUserEvent UserEvent;
|
struct SUserEvent UserEvent;
|
||||||
struct SSystemEvent SystemEvent;
|
|
||||||
struct SApplicationEvent ApplicationEvent;
|
struct SApplicationEvent ApplicationEvent;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -180,10 +180,7 @@ public:
|
||||||
virtual bool isFullscreen() const = 0;
|
virtual bool isFullscreen() const = 0;
|
||||||
|
|
||||||
//! Checks if the window could possibly be visible.
|
//! Checks if the window could possibly be visible.
|
||||||
//! Currently, this only returns false when the activity is stopped on
|
/** If this returns false, you should not do any rendering. */
|
||||||
//! Android. Note that for Android activities, "stopped" means something
|
|
||||||
//! different than you might expect (and also something different than
|
|
||||||
//! "paused"). Read the Android lifecycle documentation.
|
|
||||||
virtual bool isWindowVisible() const { return true; };
|
virtual bool isWindowVisible() const { return true; };
|
||||||
|
|
||||||
//! Get the current color format of the window
|
//! Get the current color format of the window
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#include "CAndroidAssetReader.h"
|
|
||||||
|
|
||||||
#include "CReadFile.h"
|
|
||||||
#include "coreutil.h"
|
|
||||||
#include "CAndroidAssetFileArchive.h"
|
|
||||||
#include "CIrrDeviceAndroid.h"
|
|
||||||
#include "os.h" // for logging (just keep it in even when not needed right now as it's used all the time)
|
|
||||||
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
#include <android/native_activity.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace io
|
|
||||||
{
|
|
||||||
|
|
||||||
CAndroidAssetFileArchive::CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths) :
|
|
||||||
CFileList("/asset", ignoreCase, ignorePaths), AssetManager(assetManager)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CAndroidAssetFileArchive::~CAndroidAssetFileArchive()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
//! get the archive type
|
|
||||||
E_FILE_ARCHIVE_TYPE CAndroidAssetFileArchive::getType() const
|
|
||||||
{
|
|
||||||
return EFAT_ANDROID_ASSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
const IFileList *CAndroidAssetFileArchive::getFileList() const
|
|
||||||
{
|
|
||||||
// The assert_manager can not read directory names, so
|
|
||||||
// getFileList returns only files in folders which have been added.
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! opens a file by file name
|
|
||||||
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(const io::path &filename)
|
|
||||||
{
|
|
||||||
CAndroidAssetReader *reader = new CAndroidAssetReader(AssetManager, filename);
|
|
||||||
|
|
||||||
if (reader->isOpen())
|
|
||||||
return reader;
|
|
||||||
|
|
||||||
reader->drop();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! opens a file by index
|
|
||||||
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(u32 index)
|
|
||||||
{
|
|
||||||
const io::path &filename(getFullFileName(index));
|
|
||||||
if (filename.empty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return createAndOpenFile(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAndroidAssetFileArchive::addDirectoryToFileList(const io::path &dirname_)
|
|
||||||
{
|
|
||||||
io::path dirname(dirname_);
|
|
||||||
fschar_t lastChar = dirname.lastChar();
|
|
||||||
if (lastChar == '/' || lastChar == '\\')
|
|
||||||
dirname.erase(dirname.size() - 1);
|
|
||||||
|
|
||||||
// os::Printer::log("addDirectoryToFileList:", dirname.c_str(), ELL_DEBUG);
|
|
||||||
if (findFile(dirname, true) >= 0)
|
|
||||||
return; // was already added
|
|
||||||
|
|
||||||
AAssetDir *dir = AAssetManager_openDir(AssetManager, core::stringc(dirname).c_str());
|
|
||||||
if (!dir)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// add directory itself
|
|
||||||
addItem(dirname, 0, 0, /*isDir*/ true, getFileCount());
|
|
||||||
|
|
||||||
// add all files in folder.
|
|
||||||
// Note: AAssetDir_getNextFileName does not return directory names (neither does any other NDK function)
|
|
||||||
while (const char *filename = AAssetDir_getNextFileName(dir)) {
|
|
||||||
core::stringc full_filename = dirname == "" ? filename
|
|
||||||
: dirname + "/" + filename;
|
|
||||||
|
|
||||||
// We can't get the size without opening the file - so for performance
|
|
||||||
// reasons we set the file size to 0.
|
|
||||||
// TODO: Does this really cost so much performance that it's worth losing this information? Dirs are usually just added once at startup...
|
|
||||||
addItem(full_filename, /*offet*/ 0, /*size*/ 0, /*isDir*/ false, getFileCount());
|
|
||||||
// os::Printer::log("addItem:", full_filename.c_str(), ELL_DEBUG);
|
|
||||||
}
|
|
||||||
AAssetDir_close(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace io
|
|
||||||
} // end namespace irr
|
|
|
@ -1,58 +0,0 @@
|
||||||
// Copyright (C) 2012 Joerg Henrichs
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IReadFile.h"
|
|
||||||
#include "IFileArchive.h"
|
|
||||||
#include "CFileList.h"
|
|
||||||
|
|
||||||
#include <android/native_activity.h>
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace io
|
|
||||||
{
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Android asset file system written August 2012 by J.Henrichs (later reworked by others).
|
|
||||||
*/
|
|
||||||
class CAndroidAssetFileArchive : public virtual IFileArchive,
|
|
||||||
virtual CFileList
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//! constructor
|
|
||||||
CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths);
|
|
||||||
|
|
||||||
//! destructor
|
|
||||||
virtual ~CAndroidAssetFileArchive();
|
|
||||||
|
|
||||||
//! opens a file by file name
|
|
||||||
virtual IReadFile *createAndOpenFile(const io::path &filename);
|
|
||||||
|
|
||||||
//! opens a file by index
|
|
||||||
virtual IReadFile *createAndOpenFile(u32 index);
|
|
||||||
|
|
||||||
//! returns the list of files
|
|
||||||
virtual const IFileList *getFileList() const;
|
|
||||||
|
|
||||||
//! get the archive type
|
|
||||||
virtual E_FILE_ARCHIVE_TYPE getType() const;
|
|
||||||
|
|
||||||
//! Add a directory to read files from. Since the Android
|
|
||||||
//! API does not return names of directories, they need to
|
|
||||||
//! be added manually.
|
|
||||||
virtual void addDirectoryToFileList(const io::path &filename);
|
|
||||||
|
|
||||||
//! return the name (id) of the file Archive
|
|
||||||
const io::path &getArchiveName() const override { return Path; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
//! Android's asset manager
|
|
||||||
AAssetManager *AssetManager;
|
|
||||||
|
|
||||||
}; // CAndroidAssetFileArchive
|
|
||||||
|
|
||||||
} // end namespace io
|
|
||||||
} // end namespace irr
|
|
|
@ -1,65 +0,0 @@
|
||||||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#include "CAndroidAssetReader.h"
|
|
||||||
|
|
||||||
#include "CReadFile.h"
|
|
||||||
#include "coreutil.h"
|
|
||||||
#include "CAndroidAssetReader.h"
|
|
||||||
#include "CIrrDeviceAndroid.h"
|
|
||||||
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
#include <android/native_activity.h>
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace io
|
|
||||||
{
|
|
||||||
|
|
||||||
CAndroidAssetReader::CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename) :
|
|
||||||
AssetManager(assetManager), Filename(filename)
|
|
||||||
{
|
|
||||||
Asset = AAssetManager_open(AssetManager,
|
|
||||||
core::stringc(filename).c_str(),
|
|
||||||
AASSET_MODE_RANDOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
CAndroidAssetReader::~CAndroidAssetReader()
|
|
||||||
{
|
|
||||||
if (Asset)
|
|
||||||
AAsset_close(Asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t CAndroidAssetReader::read(void *buffer, size_t sizeToRead)
|
|
||||||
{
|
|
||||||
int readBytes = AAsset_read(Asset, buffer, sizeToRead);
|
|
||||||
if (readBytes >= 0)
|
|
||||||
return size_t(readBytes);
|
|
||||||
return 0; // direct fd access is not possible (for example, if the asset is compressed).
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CAndroidAssetReader::seek(long finalPos, bool relativeMovement)
|
|
||||||
{
|
|
||||||
off_t status = AAsset_seek(Asset, finalPos, relativeMovement ? SEEK_CUR : SEEK_SET);
|
|
||||||
|
|
||||||
return status + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
long CAndroidAssetReader::getSize() const
|
|
||||||
{
|
|
||||||
return AAsset_getLength(Asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
long CAndroidAssetReader::getPos() const
|
|
||||||
{
|
|
||||||
return AAsset_getLength(Asset) - AAsset_getRemainingLength(Asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
const io::path &CAndroidAssetReader::getFileName() const
|
|
||||||
{
|
|
||||||
return Filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace io
|
|
||||||
} // end namespace irr
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Copyright (C) 2012 Joerg Henrichs
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IReadFile.h"
|
|
||||||
|
|
||||||
struct AAssetManager;
|
|
||||||
struct AAsset;
|
|
||||||
struct ANativeActivity;
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace io
|
|
||||||
{
|
|
||||||
|
|
||||||
class CAndroidAssetReader : public virtual IReadFile
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename);
|
|
||||||
|
|
||||||
virtual ~CAndroidAssetReader();
|
|
||||||
|
|
||||||
//! Reads an amount of bytes from the file.
|
|
||||||
/** \param buffer Pointer to buffer where read bytes are written to.
|
|
||||||
\param sizeToRead Amount of bytes to read from the file.
|
|
||||||
\return How many bytes were read. */
|
|
||||||
virtual size_t read(void *buffer, size_t sizeToRead);
|
|
||||||
|
|
||||||
//! Changes position in file
|
|
||||||
/** \param finalPos Destination position in the file.
|
|
||||||
\param relativeMovement If set to true, the position in the file is
|
|
||||||
changed relative to current position. Otherwise the position is changed
|
|
||||||
from beginning of file.
|
|
||||||
\return True if successful, otherwise false. */
|
|
||||||
virtual bool seek(long finalPos, bool relativeMovement = false);
|
|
||||||
|
|
||||||
//! Get size of file.
|
|
||||||
/** \return Size of the file in bytes. */
|
|
||||||
virtual long getSize() const;
|
|
||||||
|
|
||||||
//! Get the current position in the file.
|
|
||||||
/** \return Current position in the file in bytes. */
|
|
||||||
virtual long getPos() const;
|
|
||||||
|
|
||||||
//! Get name of file.
|
|
||||||
/** \return File name as zero terminated character string. */
|
|
||||||
virtual const io::path &getFileName() const;
|
|
||||||
|
|
||||||
/** Return true if the file could be opened. */
|
|
||||||
bool isOpen() const { return Asset != NULL; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
//! Android's asset manager
|
|
||||||
AAssetManager *AssetManager;
|
|
||||||
|
|
||||||
// An asset, i.e. file
|
|
||||||
AAsset *Asset;
|
|
||||||
path Filename;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace io
|
|
||||||
} // end namespace irr
|
|
|
@ -1,828 +0,0 @@
|
||||||
// Copyright (C) 2002-2007 Nikolaus Gebhardt
|
|
||||||
// Copyright (C) 2007-2011 Christian Stehno
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#include "CIrrDeviceAndroid.h"
|
|
||||||
|
|
||||||
#include "os.h"
|
|
||||||
#include "CFileSystem.h"
|
|
||||||
#include "CAndroidAssetReader.h"
|
|
||||||
#include "CAndroidAssetFileArchive.h"
|
|
||||||
#include "CKeyEventWrapper.h"
|
|
||||||
#include "CEGLManager.h"
|
|
||||||
#include "ISceneManager.h"
|
|
||||||
#include "IGUIEnvironment.h"
|
|
||||||
#include "CEGLManager.h"
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace video
|
|
||||||
{
|
|
||||||
IVideoDriver *createOGLES1Driver(const SIrrlichtCreationParameters ¶ms,
|
|
||||||
io::IFileSystem *io, video::IContextManager *contextManager);
|
|
||||||
|
|
||||||
IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters ¶ms,
|
|
||||||
io::IFileSystem *io, video::IContextManager *contextManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
|
|
||||||
CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters ¶m) :
|
|
||||||
CIrrDeviceStub(param), Accelerometer(0), Gyroscope(0), Initialized(false),
|
|
||||||
Stopped(true), Paused(true), Focused(false), JNIEnvAttachedToVM(0)
|
|
||||||
{
|
|
||||||
#ifdef _DEBUG
|
|
||||||
setDebugName("CIrrDeviceAndroid");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get the interface to the native Android activity.
|
|
||||||
Android = (android_app *)(param.PrivateData);
|
|
||||||
|
|
||||||
// Set the private data so we can use it in any static callbacks.
|
|
||||||
Android->userData = this;
|
|
||||||
|
|
||||||
// Set the default command handler. This is a callback function that the Android
|
|
||||||
// OS invokes to send the native activity messages.
|
|
||||||
Android->onAppCmd = handleAndroidCommand;
|
|
||||||
|
|
||||||
createKeyMap();
|
|
||||||
|
|
||||||
// Create a sensor manager to receive touch screen events from the java activity.
|
|
||||||
SensorManager = ASensorManager_getInstance();
|
|
||||||
SensorEventQueue = ASensorManager_createEventQueue(SensorManager, Android->looper, LOOPER_ID_USER, 0, 0);
|
|
||||||
Android->onInputEvent = handleInput;
|
|
||||||
|
|
||||||
// Create EGL manager.
|
|
||||||
ContextManager = new video::CEGLManager();
|
|
||||||
|
|
||||||
os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);
|
|
||||||
|
|
||||||
do {
|
|
||||||
s32 Events = 0;
|
|
||||||
android_poll_source *Source = 0;
|
|
||||||
|
|
||||||
while ((ALooper_pollAll((!Initialized || isWindowActive()) ? 0 : -1, 0, &Events, (void **)&Source)) >= 0) {
|
|
||||||
if (Source)
|
|
||||||
Source->process(Android, Source);
|
|
||||||
}
|
|
||||||
} while (!Initialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
CIrrDeviceAndroid::~CIrrDeviceAndroid()
|
|
||||||
{
|
|
||||||
if (GUIEnvironment) {
|
|
||||||
GUIEnvironment->drop();
|
|
||||||
GUIEnvironment = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SceneManager) {
|
|
||||||
SceneManager->drop();
|
|
||||||
SceneManager = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VideoDriver) {
|
|
||||||
VideoDriver->drop();
|
|
||||||
VideoDriver = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::run()
|
|
||||||
{
|
|
||||||
if (!Initialized)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
os::Timer::tick();
|
|
||||||
|
|
||||||
s32 id;
|
|
||||||
s32 Events = 0;
|
|
||||||
android_poll_source *Source = 0;
|
|
||||||
|
|
||||||
while ((id = ALooper_pollAll(0, 0, &Events, (void **)&Source)) >= 0) {
|
|
||||||
if (Source)
|
|
||||||
Source->process(Android, Source);
|
|
||||||
|
|
||||||
// if a sensor has data, we'll process it now.
|
|
||||||
if (id == LOOPER_ID_USER) {
|
|
||||||
ASensorEvent sensorEvent;
|
|
||||||
while (ASensorEventQueue_getEvents(SensorEventQueue, &sensorEvent, 1) > 0) {
|
|
||||||
switch (sensorEvent.type) {
|
|
||||||
case ASENSOR_TYPE_ACCELEROMETER:
|
|
||||||
SEvent accEvent;
|
|
||||||
accEvent.EventType = EET_ACCELEROMETER_EVENT;
|
|
||||||
accEvent.AccelerometerEvent.X = sensorEvent.acceleration.x;
|
|
||||||
accEvent.AccelerometerEvent.Y = sensorEvent.acceleration.y;
|
|
||||||
accEvent.AccelerometerEvent.Z = sensorEvent.acceleration.z;
|
|
||||||
|
|
||||||
postEventFromUser(accEvent);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ASENSOR_TYPE_GYROSCOPE:
|
|
||||||
SEvent gyroEvent;
|
|
||||||
gyroEvent.EventType = EET_GYROSCOPE_EVENT;
|
|
||||||
gyroEvent.GyroscopeEvent.X = sensorEvent.vector.x;
|
|
||||||
gyroEvent.GyroscopeEvent.Y = sensorEvent.vector.y;
|
|
||||||
gyroEvent.GyroscopeEvent.Z = sensorEvent.vector.z;
|
|
||||||
|
|
||||||
postEventFromUser(gyroEvent);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Initialized)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::yield()
|
|
||||||
{
|
|
||||||
struct timespec ts = {0, 1};
|
|
||||||
nanosleep(&ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
|
|
||||||
{
|
|
||||||
const bool wasStopped = Timer ? Timer->isStopped() : true;
|
|
||||||
|
|
||||||
struct timespec ts;
|
|
||||||
ts.tv_sec = (time_t)(timeMs / 1000);
|
|
||||||
ts.tv_nsec = (long)(timeMs % 1000) * 1000000;
|
|
||||||
|
|
||||||
if (pauseTimer && !wasStopped)
|
|
||||||
Timer->stop();
|
|
||||||
|
|
||||||
nanosleep(&ts, NULL);
|
|
||||||
|
|
||||||
if (pauseTimer && !wasStopped)
|
|
||||||
Timer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::setWindowCaption(const wchar_t *text)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isWindowActive() const
|
|
||||||
{
|
|
||||||
return (Focused && !Paused && !Stopped);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isWindowFocused() const
|
|
||||||
{
|
|
||||||
return Focused;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isWindowMinimized() const
|
|
||||||
{
|
|
||||||
return !Focused;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isWindowVisible() const
|
|
||||||
{
|
|
||||||
return !Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::closeDevice()
|
|
||||||
{
|
|
||||||
ANativeActivity_finish(Android->activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::setResizable(bool resize)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::minimizeWindow()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::maximizeWindow()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::restoreWindow()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
core::position2di CIrrDeviceAndroid::getWindowPosition()
|
|
||||||
{
|
|
||||||
return core::position2di(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
|
|
||||||
{
|
|
||||||
return EIDT_ANDROID;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::handleAndroidCommand(android_app *app, int32_t cmd)
|
|
||||||
{
|
|
||||||
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
|
|
||||||
|
|
||||||
SEvent event;
|
|
||||||
event.EventType = EET_SYSTEM_EVENT;
|
|
||||||
event.SystemEvent.EventType = ESET_ANDROID_CMD;
|
|
||||||
event.SystemEvent.AndroidCmd.Cmd = cmd;
|
|
||||||
if (device->postEventFromUser(event))
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (cmd) {
|
|
||||||
case APP_CMD_INPUT_CHANGED:
|
|
||||||
os::Printer::log("Android command APP_CMD_INPUT_CHANGED", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_WINDOW_RESIZED:
|
|
||||||
os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_WINDOW_REDRAW_NEEDED:
|
|
||||||
os::Printer::log("Android command APP_CMD_WINDOW_REDRAW_NEEDED", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_SAVE_STATE:
|
|
||||||
os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_CONTENT_RECT_CHANGED:
|
|
||||||
os::Printer::log("Android command APP_CMD_CONTENT_RECT_CHANGED", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_CONFIG_CHANGED:
|
|
||||||
os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_LOW_MEMORY:
|
|
||||||
os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG);
|
|
||||||
break;
|
|
||||||
case APP_CMD_START:
|
|
||||||
os::Printer::log("Android command APP_CMD_START", ELL_DEBUG);
|
|
||||||
device->Stopped = false;
|
|
||||||
break;
|
|
||||||
case APP_CMD_INIT_WINDOW:
|
|
||||||
os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
|
|
||||||
device->getExposedVideoData().OGLESAndroid.Window = app->window;
|
|
||||||
|
|
||||||
if (device->CreationParams.WindowSize.Width == 0 || device->CreationParams.WindowSize.Height == 0) {
|
|
||||||
device->CreationParams.WindowSize.Width = ANativeWindow_getWidth(app->window);
|
|
||||||
device->CreationParams.WindowSize.Height = ANativeWindow_getHeight(app->window);
|
|
||||||
}
|
|
||||||
|
|
||||||
device->getContextManager()->initialize(device->CreationParams, device->ExposedVideoData);
|
|
||||||
device->getContextManager()->generateSurface();
|
|
||||||
device->getContextManager()->generateContext();
|
|
||||||
device->getContextManager()->activateContext(device->getContextManager()->getContext());
|
|
||||||
|
|
||||||
if (!device->Initialized) {
|
|
||||||
io::CAndroidAssetFileArchive *assets = new io::CAndroidAssetFileArchive(device->Android->activity->assetManager, false, false);
|
|
||||||
assets->addDirectoryToFileList("");
|
|
||||||
device->FileSystem->addFileArchive(assets);
|
|
||||||
assets->drop();
|
|
||||||
|
|
||||||
device->createDriver();
|
|
||||||
|
|
||||||
if (device->VideoDriver)
|
|
||||||
device->createGUIAndScene();
|
|
||||||
}
|
|
||||||
device->Initialized = true;
|
|
||||||
break;
|
|
||||||
case APP_CMD_TERM_WINDOW:
|
|
||||||
os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
|
|
||||||
device->getContextManager()->destroySurface();
|
|
||||||
break;
|
|
||||||
case APP_CMD_GAINED_FOCUS:
|
|
||||||
os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
|
|
||||||
device->Focused = true;
|
|
||||||
break;
|
|
||||||
case APP_CMD_LOST_FOCUS:
|
|
||||||
os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
|
|
||||||
device->Focused = false;
|
|
||||||
break;
|
|
||||||
case APP_CMD_DESTROY:
|
|
||||||
os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
|
|
||||||
if (device->JNIEnvAttachedToVM) {
|
|
||||||
device->JNIEnvAttachedToVM = 0;
|
|
||||||
device->Android->activity->vm->DetachCurrentThread();
|
|
||||||
}
|
|
||||||
device->Initialized = false;
|
|
||||||
break;
|
|
||||||
case APP_CMD_PAUSE:
|
|
||||||
os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
|
|
||||||
device->Paused = true;
|
|
||||||
break;
|
|
||||||
case APP_CMD_STOP:
|
|
||||||
os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
|
|
||||||
device->Stopped = true;
|
|
||||||
break;
|
|
||||||
case APP_CMD_RESUME:
|
|
||||||
os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
|
|
||||||
device->Paused = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 CIrrDeviceAndroid::handleInput(android_app *app, AInputEvent *androidEvent)
|
|
||||||
{
|
|
||||||
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
|
|
||||||
s32 status = 0;
|
|
||||||
|
|
||||||
switch (AInputEvent_getType(androidEvent)) {
|
|
||||||
case AINPUT_EVENT_TYPE_MOTION: {
|
|
||||||
SEvent event;
|
|
||||||
event.EventType = EET_TOUCH_INPUT_EVENT;
|
|
||||||
|
|
||||||
s32 eventAction = AMotionEvent_getAction(androidEvent);
|
|
||||||
s32 eventType = eventAction & AMOTION_EVENT_ACTION_MASK;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Useful for debugging. We might have to pass some of those infos on at some point.
|
|
||||||
// but preferably device independent (so iphone can use same irrlicht flags).
|
|
||||||
int32_t flags = AMotionEvent_getFlags(androidEvent);
|
|
||||||
os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG);
|
|
||||||
int32_t metaState = AMotionEvent_getMetaState(androidEvent);
|
|
||||||
os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG);
|
|
||||||
int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent);
|
|
||||||
os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool touchReceived = true;
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case AMOTION_EVENT_ACTION_DOWN:
|
|
||||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
|
||||||
event.TouchInput.Event = ETIE_PRESSED_DOWN;
|
|
||||||
break;
|
|
||||||
case AMOTION_EVENT_ACTION_MOVE:
|
|
||||||
event.TouchInput.Event = ETIE_MOVED;
|
|
||||||
break;
|
|
||||||
case AMOTION_EVENT_ACTION_UP:
|
|
||||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
|
||||||
case AMOTION_EVENT_ACTION_CANCEL:
|
|
||||||
event.TouchInput.Event = ETIE_LEFT_UP;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
touchReceived = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (touchReceived) {
|
|
||||||
// Process all touches for move action.
|
|
||||||
if (event.TouchInput.Event == ETIE_MOVED) {
|
|
||||||
s32 pointerCount = AMotionEvent_getPointerCount(androidEvent);
|
|
||||||
|
|
||||||
for (s32 i = 0; i < pointerCount; ++i) {
|
|
||||||
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
|
|
||||||
event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
|
|
||||||
event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
|
|
||||||
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
|
|
||||||
|
|
||||||
device->postEventFromUser(event);
|
|
||||||
}
|
|
||||||
} else // Process one touch for other actions.
|
|
||||||
{
|
|
||||||
s32 pointerIndex = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
|
||||||
|
|
||||||
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
|
|
||||||
event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
|
|
||||||
event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
|
|
||||||
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
|
|
||||||
|
|
||||||
device->postEventFromUser(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
status = 1;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case AINPUT_EVENT_TYPE_KEY: {
|
|
||||||
SEvent event;
|
|
||||||
event.EventType = EET_KEY_INPUT_EVENT;
|
|
||||||
|
|
||||||
int32_t keyCode = AKeyEvent_getKeyCode(androidEvent);
|
|
||||||
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
|
|
||||||
|
|
||||||
int32_t keyAction = AKeyEvent_getAction(androidEvent);
|
|
||||||
int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent);
|
|
||||||
|
|
||||||
if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size())
|
|
||||||
event.KeyInput.Key = device->KeyMap[keyCode];
|
|
||||||
else
|
|
||||||
event.KeyInput.Key = KEY_UNKNOWN;
|
|
||||||
event.KeyInput.SystemKeyCode = (u32)keyCode;
|
|
||||||
if (keyAction == AKEY_EVENT_ACTION_DOWN)
|
|
||||||
event.KeyInput.PressedDown = true;
|
|
||||||
else if (keyAction == AKEY_EVENT_ACTION_UP)
|
|
||||||
event.KeyInput.PressedDown = false;
|
|
||||||
else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE) {
|
|
||||||
// TODO: Multiple duplicate key events have occurred in a row,
|
|
||||||
// or a complex string is being delivered. The repeat_count
|
|
||||||
// property of the key event contains the number of times the
|
|
||||||
// given key code should be executed.
|
|
||||||
// I guess this might necessary for more complicated i18n key input,
|
|
||||||
// but don't see yet how to handle this correctly.
|
|
||||||
}
|
|
||||||
|
|
||||||
/* no use for meta keys so far.
|
|
||||||
if ( keyMetaState & AMETA_ALT_ON
|
|
||||||
|| keyMetaState & AMETA_ALT_LEFT_ON
|
|
||||||
|| keyMetaState & AMETA_ALT_RIGHT_ON )
|
|
||||||
;
|
|
||||||
// what is a sym?
|
|
||||||
if ( keyMetaState & AMETA_SYM_ON )
|
|
||||||
;
|
|
||||||
*/
|
|
||||||
if (keyMetaState & AMETA_SHIFT_ON || keyMetaState & AMETA_SHIFT_LEFT_ON || keyMetaState & AMETA_SHIFT_RIGHT_ON)
|
|
||||||
event.KeyInput.Shift = true;
|
|
||||||
else
|
|
||||||
event.KeyInput.Shift = false;
|
|
||||||
event.KeyInput.Control = false;
|
|
||||||
|
|
||||||
// Having memory allocations + going through JNI for each key-press is pretty bad (slow).
|
|
||||||
// So we do it only for those keys which are likely text-characters and avoid it for all other keys.
|
|
||||||
// So it's fast for keys like game controller input and special keys. And text keys are typically
|
|
||||||
// only used or entering text and not for gaming on Android, so speed likely doesn't matter there too much.
|
|
||||||
if (event.KeyInput.Key > 0) {
|
|
||||||
// TODO:
|
|
||||||
// Not sure why we have to attach a JNIEnv here, but it won't work when doing that in the constructor or
|
|
||||||
// trying to use the activity->env. My best guess is that the event-handling happens in an own thread.
|
|
||||||
// It means JNIEnvAttachedToVM will never get detached as I don't know a safe way where to do that
|
|
||||||
// (we could attach & detach each time, but that would probably be slow)
|
|
||||||
// Also - it has to be each time as it get's invalid when the application mode changes.
|
|
||||||
if (device->Initialized && device->Android && device->Android->activity && device->Android->activity->vm) {
|
|
||||||
JavaVMAttachArgs attachArgs;
|
|
||||||
attachArgs.version = JNI_VERSION_1_6;
|
|
||||||
attachArgs.name = 0;
|
|
||||||
attachArgs.group = NULL;
|
|
||||||
|
|
||||||
// Not a big problem calling it each time - it's a no-op when the thread already is attached.
|
|
||||||
// And we have to do that as someone else can have detached the thread in the meantime.
|
|
||||||
jint result = device->Android->activity->vm->AttachCurrentThread(&device->JNIEnvAttachedToVM, &attachArgs);
|
|
||||||
if (result == JNI_ERR) {
|
|
||||||
os::Printer::log("AttachCurrentThread for the JNI environment failed.", ELL_WARNING);
|
|
||||||
device->JNIEnvAttachedToVM = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device->JNIEnvAttachedToVM) {
|
|
||||||
jni::CKeyEventWrapper *keyEventWrapper = new jni::CKeyEventWrapper(device->JNIEnvAttachedToVM, keyAction, keyCode);
|
|
||||||
event.KeyInput.Char = keyEventWrapper->getUnicodeChar(keyMetaState);
|
|
||||||
delete keyEventWrapper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.KeyInput.Key == KEY_BACK) {
|
|
||||||
event.KeyInput.Char = 0x08; // same key-code as on other operating systems. Otherwise we have to handle too much system specific stuff in the editbox.
|
|
||||||
}
|
|
||||||
// os::Printer::log("char-code: ", core::stringc((int)event.KeyInput.Char).c_str(), ELL_DEBUG);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
|
|
||||||
event.KeyInput.Char = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = device->postEventFromUser(event);
|
|
||||||
} break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::createDriver()
|
|
||||||
{
|
|
||||||
switch (CreationParams.DriverType) {
|
|
||||||
case video::EDT_OGLES1:
|
|
||||||
#ifdef _IRR_COMPILE_WITH_OGLES1_
|
|
||||||
VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);
|
|
||||||
#else
|
|
||||||
os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
case video::EDT_OGLES2:
|
|
||||||
#ifdef _IRR_COMPILE_WITH_OGLES2_
|
|
||||||
VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);
|
|
||||||
#else
|
|
||||||
os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
case video::EDT_NULL:
|
|
||||||
VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
|
|
||||||
break;
|
|
||||||
case video::EDT_OPENGL:
|
|
||||||
os::Printer::log("This driver is not available in Android. Try OpenGL ES 1.0 or ES 2.0.", ELL_ERROR);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video::SExposedVideoData &CIrrDeviceAndroid::getExposedVideoData()
|
|
||||||
{
|
|
||||||
return ExposedVideoData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CIrrDeviceAndroid::createKeyMap()
|
|
||||||
{
|
|
||||||
KeyMap.set_used(223);
|
|
||||||
|
|
||||||
KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN
|
|
||||||
KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
|
|
||||||
KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
|
|
||||||
KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
|
|
||||||
KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
|
|
||||||
KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
|
|
||||||
KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
|
|
||||||
KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
|
|
||||||
KeyMap[8] = KEY_KEY_1; // AKEYCODE_1
|
|
||||||
KeyMap[9] = KEY_KEY_2; // AKEYCODE_2
|
|
||||||
KeyMap[10] = KEY_KEY_3; // AKEYCODE_3
|
|
||||||
KeyMap[11] = KEY_KEY_4; // AKEYCODE_4
|
|
||||||
KeyMap[12] = KEY_KEY_5; // AKEYCODE_5
|
|
||||||
KeyMap[13] = KEY_KEY_6; // AKEYCODE_6
|
|
||||||
KeyMap[14] = KEY_KEY_7; // AKEYCODE_7
|
|
||||||
KeyMap[15] = KEY_KEY_8; // AKEYCODE_8
|
|
||||||
KeyMap[16] = KEY_KEY_9; // AKEYCODE_9
|
|
||||||
KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR
|
|
||||||
KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND
|
|
||||||
KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP
|
|
||||||
KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN
|
|
||||||
KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT
|
|
||||||
KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT
|
|
||||||
KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER
|
|
||||||
KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP
|
|
||||||
KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN
|
|
||||||
KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER
|
|
||||||
KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA
|
|
||||||
KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR
|
|
||||||
KeyMap[29] = KEY_KEY_A; // AKEYCODE_A
|
|
||||||
KeyMap[30] = KEY_KEY_B; // AKEYCODE_B
|
|
||||||
KeyMap[31] = KEY_KEY_C; // AKEYCODE_C
|
|
||||||
KeyMap[32] = KEY_KEY_D; // AKEYCODE_D
|
|
||||||
KeyMap[33] = KEY_KEY_E; // AKEYCODE_E
|
|
||||||
KeyMap[34] = KEY_KEY_F; // AKEYCODE_F
|
|
||||||
KeyMap[35] = KEY_KEY_G; // AKEYCODE_G
|
|
||||||
KeyMap[36] = KEY_KEY_H; // AKEYCODE_H
|
|
||||||
KeyMap[37] = KEY_KEY_I; // AKEYCODE_I
|
|
||||||
KeyMap[38] = KEY_KEY_J; // AKEYCODE_J
|
|
||||||
KeyMap[39] = KEY_KEY_K; // AKEYCODE_K
|
|
||||||
KeyMap[40] = KEY_KEY_L; // AKEYCODE_L
|
|
||||||
KeyMap[41] = KEY_KEY_M; // AKEYCODE_M
|
|
||||||
KeyMap[42] = KEY_KEY_N; // AKEYCODE_N
|
|
||||||
KeyMap[43] = KEY_KEY_O; // AKEYCODE_O
|
|
||||||
KeyMap[44] = KEY_KEY_P; // AKEYCODE_P
|
|
||||||
KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q
|
|
||||||
KeyMap[46] = KEY_KEY_R; // AKEYCODE_R
|
|
||||||
KeyMap[47] = KEY_KEY_S; // AKEYCODE_S
|
|
||||||
KeyMap[48] = KEY_KEY_T; // AKEYCODE_T
|
|
||||||
KeyMap[49] = KEY_KEY_U; // AKEYCODE_U
|
|
||||||
KeyMap[50] = KEY_KEY_V; // AKEYCODE_V
|
|
||||||
KeyMap[51] = KEY_KEY_W; // AKEYCODE_W
|
|
||||||
KeyMap[52] = KEY_KEY_X; // AKEYCODE_X
|
|
||||||
KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y
|
|
||||||
KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z
|
|
||||||
KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA
|
|
||||||
KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD
|
|
||||||
KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT
|
|
||||||
KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT
|
|
||||||
KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT
|
|
||||||
KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT
|
|
||||||
KeyMap[61] = KEY_TAB; // AKEYCODE_TAB
|
|
||||||
KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE
|
|
||||||
KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM
|
|
||||||
KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER
|
|
||||||
KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE
|
|
||||||
KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER
|
|
||||||
KeyMap[67] = KEY_BACK; // AKEYCODE_DEL
|
|
||||||
KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE
|
|
||||||
KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS
|
|
||||||
KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS
|
|
||||||
KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET
|
|
||||||
KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET
|
|
||||||
KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH
|
|
||||||
KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON
|
|
||||||
KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE
|
|
||||||
KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH
|
|
||||||
KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT
|
|
||||||
KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM
|
|
||||||
KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK
|
|
||||||
KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus)
|
|
||||||
KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS
|
|
||||||
KeyMap[82] = KEY_MENU; // AKEYCODE_MENU
|
|
||||||
KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION
|
|
||||||
KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH
|
|
||||||
KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE
|
|
||||||
KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP
|
|
||||||
KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT
|
|
||||||
KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS
|
|
||||||
KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND
|
|
||||||
KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD
|
|
||||||
KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE
|
|
||||||
KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP
|
|
||||||
KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN
|
|
||||||
KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS
|
|
||||||
KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET
|
|
||||||
|
|
||||||
// following look like controller inputs
|
|
||||||
KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A
|
|
||||||
KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B
|
|
||||||
KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C
|
|
||||||
KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X
|
|
||||||
KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y
|
|
||||||
KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z
|
|
||||||
KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1
|
|
||||||
KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1
|
|
||||||
KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2
|
|
||||||
KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2
|
|
||||||
KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL
|
|
||||||
KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR
|
|
||||||
KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START
|
|
||||||
KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT
|
|
||||||
KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE
|
|
||||||
|
|
||||||
KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE
|
|
||||||
KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL
|
|
||||||
KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT
|
|
||||||
KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT
|
|
||||||
KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK
|
|
||||||
KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK
|
|
||||||
KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT
|
|
||||||
KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT
|
|
||||||
KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION
|
|
||||||
KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ
|
|
||||||
KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK
|
|
||||||
KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME
|
|
||||||
KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END
|
|
||||||
KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT
|
|
||||||
KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD
|
|
||||||
KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY
|
|
||||||
KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE
|
|
||||||
KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE
|
|
||||||
KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT
|
|
||||||
KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD
|
|
||||||
KeyMap[131] = KEY_F1; // AKEYCODE_F1
|
|
||||||
KeyMap[132] = KEY_F2; // AKEYCODE_F2
|
|
||||||
KeyMap[133] = KEY_F3; // AKEYCODE_F3
|
|
||||||
KeyMap[134] = KEY_F4; // AKEYCODE_F4
|
|
||||||
KeyMap[135] = KEY_F5; // AKEYCODE_F5
|
|
||||||
KeyMap[136] = KEY_F6; // AKEYCODE_F6
|
|
||||||
KeyMap[137] = KEY_F7; // AKEYCODE_F7
|
|
||||||
KeyMap[138] = KEY_F8; // AKEYCODE_F8
|
|
||||||
KeyMap[139] = KEY_F9; // AKEYCODE_F9
|
|
||||||
KeyMap[140] = KEY_F10; // AKEYCODE_F10
|
|
||||||
KeyMap[141] = KEY_F11; // AKEYCODE_F11
|
|
||||||
KeyMap[142] = KEY_F12; // AKEYCODE_F12
|
|
||||||
KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK
|
|
||||||
KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0
|
|
||||||
KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1
|
|
||||||
KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2
|
|
||||||
KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3
|
|
||||||
KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4
|
|
||||||
KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5
|
|
||||||
KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6
|
|
||||||
KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7
|
|
||||||
KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8
|
|
||||||
KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9
|
|
||||||
KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE
|
|
||||||
KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY
|
|
||||||
KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT
|
|
||||||
KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD
|
|
||||||
KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT
|
|
||||||
KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA
|
|
||||||
KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER
|
|
||||||
KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS
|
|
||||||
KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN
|
|
||||||
KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN
|
|
||||||
KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE
|
|
||||||
KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO
|
|
||||||
KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP
|
|
||||||
KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN
|
|
||||||
KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN
|
|
||||||
KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT
|
|
||||||
KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV
|
|
||||||
KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW
|
|
||||||
KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE
|
|
||||||
KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR
|
|
||||||
KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK
|
|
||||||
KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS
|
|
||||||
KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS
|
|
||||||
KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER
|
|
||||||
KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT
|
|
||||||
KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER
|
|
||||||
KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT
|
|
||||||
KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER
|
|
||||||
KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT
|
|
||||||
KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED
|
|
||||||
KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN
|
|
||||||
KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW
|
|
||||||
KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE
|
|
||||||
KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH
|
|
||||||
KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1
|
|
||||||
KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2
|
|
||||||
KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3
|
|
||||||
KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4
|
|
||||||
KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5
|
|
||||||
KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6
|
|
||||||
KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7
|
|
||||||
KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8
|
|
||||||
KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9
|
|
||||||
KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10
|
|
||||||
KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11
|
|
||||||
KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12
|
|
||||||
KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13
|
|
||||||
KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14
|
|
||||||
KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15
|
|
||||||
KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16
|
|
||||||
KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH
|
|
||||||
KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE
|
|
||||||
KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE
|
|
||||||
KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS
|
|
||||||
KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR
|
|
||||||
KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC
|
|
||||||
KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR
|
|
||||||
KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU
|
|
||||||
KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU
|
|
||||||
KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN
|
|
||||||
KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN
|
|
||||||
KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA
|
|
||||||
KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN
|
|
||||||
KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO
|
|
||||||
KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA
|
|
||||||
KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST
|
|
||||||
KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN
|
|
||||||
KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP ,
|
|
||||||
KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval)
|
|
||||||
{
|
|
||||||
if (!isAccelerometerAvailable())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer);
|
|
||||||
ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
|
|
||||||
|
|
||||||
os::Printer::log("Activated accelerometer", ELL_DEBUG);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::deactivateAccelerometer()
|
|
||||||
{
|
|
||||||
if (Accelerometer) {
|
|
||||||
ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer);
|
|
||||||
Accelerometer = 0;
|
|
||||||
os::Printer::log("Deactivated accelerometer", ELL_DEBUG);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isAccelerometerActive()
|
|
||||||
{
|
|
||||||
return (Accelerometer != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isAccelerometerAvailable()
|
|
||||||
{
|
|
||||||
if (!Accelerometer)
|
|
||||||
Accelerometer = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_ACCELEROMETER);
|
|
||||||
|
|
||||||
return (Accelerometer != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::activateGyroscope(float updateInterval)
|
|
||||||
{
|
|
||||||
if (!isGyroscopeAvailable())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope);
|
|
||||||
ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
|
|
||||||
|
|
||||||
os::Printer::log("Activated gyroscope", ELL_DEBUG);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::deactivateGyroscope()
|
|
||||||
{
|
|
||||||
if (Gyroscope) {
|
|
||||||
ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope);
|
|
||||||
Gyroscope = 0;
|
|
||||||
os::Printer::log("Deactivated gyroscope", ELL_DEBUG);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isGyroscopeActive()
|
|
||||||
{
|
|
||||||
return (Gyroscope != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CIrrDeviceAndroid::isGyroscopeAvailable()
|
|
||||||
{
|
|
||||||
if (!Gyroscope)
|
|
||||||
Gyroscope = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_GYROSCOPE);
|
|
||||||
|
|
||||||
return (Gyroscope != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace irr
|
|
|
@ -1,98 +0,0 @@
|
||||||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CIrrDeviceStub.h"
|
|
||||||
#include "IrrlichtDevice.h"
|
|
||||||
#include "ICursorControl.h"
|
|
||||||
|
|
||||||
#include <android/sensor.h>
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
class CIrrDeviceAndroid : public CIrrDeviceStub
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CIrrDeviceAndroid(const SIrrlichtCreationParameters ¶m);
|
|
||||||
|
|
||||||
virtual ~CIrrDeviceAndroid();
|
|
||||||
|
|
||||||
virtual bool run();
|
|
||||||
|
|
||||||
virtual void yield();
|
|
||||||
|
|
||||||
virtual void sleep(u32 timeMs, bool pauseTimer = false);
|
|
||||||
|
|
||||||
virtual void setWindowCaption(const wchar_t *text);
|
|
||||||
|
|
||||||
virtual bool isWindowActive() const;
|
|
||||||
|
|
||||||
virtual bool isWindowFocused() const;
|
|
||||||
|
|
||||||
virtual bool isWindowMinimized() const;
|
|
||||||
|
|
||||||
virtual bool isWindowVisible() const;
|
|
||||||
|
|
||||||
virtual void closeDevice();
|
|
||||||
|
|
||||||
virtual void setResizable(bool resize = false);
|
|
||||||
|
|
||||||
virtual void minimizeWindow();
|
|
||||||
|
|
||||||
virtual void maximizeWindow();
|
|
||||||
|
|
||||||
virtual void restoreWindow();
|
|
||||||
|
|
||||||
virtual core::position2di getWindowPosition();
|
|
||||||
|
|
||||||
virtual E_DEVICE_TYPE getType() const;
|
|
||||||
|
|
||||||
virtual bool activateAccelerometer(float updateInterval);
|
|
||||||
|
|
||||||
virtual bool deactivateAccelerometer();
|
|
||||||
|
|
||||||
virtual bool isAccelerometerActive();
|
|
||||||
|
|
||||||
virtual bool isAccelerometerAvailable();
|
|
||||||
|
|
||||||
virtual bool activateGyroscope(float updateInterval);
|
|
||||||
|
|
||||||
virtual bool deactivateGyroscope();
|
|
||||||
|
|
||||||
virtual bool isGyroscopeActive();
|
|
||||||
|
|
||||||
virtual bool isGyroscopeAvailable();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void handleAndroidCommand(android_app *app, int32_t cmd);
|
|
||||||
|
|
||||||
static s32 handleInput(android_app *app, AInputEvent *event);
|
|
||||||
|
|
||||||
void createDriver();
|
|
||||||
|
|
||||||
void createKeyMap();
|
|
||||||
|
|
||||||
video::SExposedVideoData &getExposedVideoData();
|
|
||||||
|
|
||||||
android_app *Android;
|
|
||||||
ASensorManager *SensorManager;
|
|
||||||
ASensorEventQueue *SensorEventQueue;
|
|
||||||
const ASensor *Accelerometer;
|
|
||||||
const ASensor *Gyroscope;
|
|
||||||
|
|
||||||
bool Initialized;
|
|
||||||
bool Stopped;
|
|
||||||
bool Paused;
|
|
||||||
bool Focused;
|
|
||||||
|
|
||||||
JNIEnv *JNIEnvAttachedToVM;
|
|
||||||
|
|
||||||
video::SExposedVideoData ExposedVideoData;
|
|
||||||
|
|
||||||
core::array<EKEY_CODE> KeyMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace irr
|
|
|
@ -1,52 +0,0 @@
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#include "CKeyEventWrapper.h"
|
|
||||||
|
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace jni
|
|
||||||
{
|
|
||||||
|
|
||||||
jclass CKeyEventWrapper::Class_KeyEvent = 0;
|
|
||||||
jmethodID CKeyEventWrapper::Method_constructor = 0;
|
|
||||||
jmethodID CKeyEventWrapper::Method_getUnicodeChar = 0;
|
|
||||||
|
|
||||||
CKeyEventWrapper::CKeyEventWrapper(JNIEnv *jniEnv, int action, int code) :
|
|
||||||
JniEnv(jniEnv), JniKeyEvent(0)
|
|
||||||
{
|
|
||||||
if (JniEnv) {
|
|
||||||
if (!Class_KeyEvent) {
|
|
||||||
// Find java classes & functions on first call
|
|
||||||
os::Printer::log("CKeyEventWrapper first initialize", ELL_DEBUG);
|
|
||||||
jclass localClass = JniEnv->FindClass("android/view/KeyEvent");
|
|
||||||
if (localClass) {
|
|
||||||
Class_KeyEvent = reinterpret_cast<jclass>(JniEnv->NewGlobalRef(localClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
Method_constructor = JniEnv->GetMethodID(Class_KeyEvent, "<init>", "(II)V");
|
|
||||||
Method_getUnicodeChar = JniEnv->GetMethodID(Class_KeyEvent, "getUnicodeChar", "(I)I");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Class_KeyEvent && Method_constructor) {
|
|
||||||
JniKeyEvent = JniEnv->NewObject(Class_KeyEvent, Method_constructor, action, code);
|
|
||||||
} else {
|
|
||||||
os::Printer::log("CKeyEventWrapper didn't find JNI classes/methods", ELL_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CKeyEventWrapper::~CKeyEventWrapper()
|
|
||||||
{
|
|
||||||
JniEnv->DeleteLocalRef(JniKeyEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
int CKeyEventWrapper::getUnicodeChar(int metaState)
|
|
||||||
{
|
|
||||||
return JniEnv->CallIntMethod(JniKeyEvent, Method_getUnicodeChar, metaState);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace jni
|
|
||||||
} // namespace irr
|
|
|
@ -1,35 +0,0 @@
|
||||||
// This file is part of the "Irrlicht Engine".
|
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
struct android_app;
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace jni
|
|
||||||
{
|
|
||||||
|
|
||||||
//! Minimal JNI wrapper class around android.view.KeyEvent
|
|
||||||
//! NOTE: Only functions we actually use in the engine are wrapped
|
|
||||||
//! This is currently not written to support multithreading - meaning threads are not attached/detached to the Java VM (to be discussed)
|
|
||||||
class CKeyEventWrapper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CKeyEventWrapper(JNIEnv *jniEnv, int action, int code);
|
|
||||||
~CKeyEventWrapper();
|
|
||||||
|
|
||||||
int getUnicodeChar(int metaState);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static jclass Class_KeyEvent;
|
|
||||||
static jmethodID Method_getUnicodeChar;
|
|
||||||
static jmethodID Method_constructor;
|
|
||||||
JNIEnv *JniEnv;
|
|
||||||
jobject JniKeyEvent; // this object in java
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace jni
|
|
||||||
} // namespace irr
|
|
|
@ -10,10 +10,6 @@
|
||||||
#include "irrArray.h"
|
#include "irrArray.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
|
||||||
#include <android/native_activity.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
namespace video
|
namespace video
|
||||||
|
@ -55,9 +51,6 @@ bool CEGLManager::initialize(const SIrrlichtCreationParameters ¶ms, const SE
|
||||||
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
|
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
|
||||||
EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window;
|
EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window;
|
||||||
EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLLinux.X11Display);
|
EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLLinux.X11Display);
|
||||||
#elif defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
|
||||||
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
|
|
||||||
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
||||||
#elif defined(_IRR_COMPILE_WITH_FB_DEVICE_)
|
#elif defined(_IRR_COMPILE_WITH_FB_DEVICE_)
|
||||||
EglWindow = (NativeWindowType)Data.OpenGLFB.Window;
|
EglWindow = (NativeWindowType)Data.OpenGLFB.Window;
|
||||||
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
@ -119,10 +112,6 @@ bool CEGLManager::generateSurface()
|
||||||
// at this time only Android support this feature.
|
// at this time only Android support this feature.
|
||||||
// this needs an update method instead!
|
// this needs an update method instead!
|
||||||
|
|
||||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
|
||||||
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_IRR_EMSCRIPTEN_PLATFORM_)
|
#if defined(_IRR_EMSCRIPTEN_PLATFORM_)
|
||||||
// eglChooseConfig is currently only implemented as stub in emscripten (version 1.37.22 at point of writing)
|
// eglChooseConfig is currently only implemented as stub in emscripten (version 1.37.22 at point of writing)
|
||||||
// But the other solution would also be fine as it also only generates a single context so there is not much to choose from.
|
// But the other solution would also be fine as it also only generates a single context so there is not much to choose from.
|
||||||
|
@ -136,13 +125,6 @@ bool CEGLManager::generateSurface()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
|
||||||
EGLint Format = 0;
|
|
||||||
eglGetConfigAttrib(EglDisplay, EglConfig, EGL_NATIVE_VISUAL_ID, &Format);
|
|
||||||
|
|
||||||
ANativeWindow_setBuffersGeometry(EglWindow, 0, 0, Format);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Now we are able to create EGL surface.
|
// Now we are able to create EGL surface.
|
||||||
EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0);
|
EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0);
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,20 @@ namespace video
|
||||||
// PNG function for error handling
|
// PNG function for error handling
|
||||||
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
||||||
{
|
{
|
||||||
os::Printer::log("PNG fatal error", msg, ELL_ERROR);
|
io::IReadFile *file = reinterpret_cast<io::IReadFile *>(png_get_error_ptr(png_ptr));
|
||||||
|
std::string logmsg = std::string("PNG fatal error for ")
|
||||||
|
+ file->getFileName().c_str() + ": " + msg;
|
||||||
|
os::Printer::log(logmsg.c_str(), ELL_ERROR);
|
||||||
longjmp(png_jmpbuf(png_ptr), 1);
|
longjmp(png_jmpbuf(png_ptr), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG function for warning handling
|
// PNG function for warning handling
|
||||||
static void png_cpexcept_warn(png_structp png_ptr, png_const_charp msg)
|
static void png_cpexcept_warn(png_structp png_ptr, png_const_charp msg)
|
||||||
{
|
{
|
||||||
os::Printer::log("PNG warning", msg, ELL_WARNING);
|
io::IReadFile *file = reinterpret_cast<io::IReadFile *>(png_get_error_ptr(png_ptr));
|
||||||
|
std::string logmsg = std::string("PNG warning for ")
|
||||||
|
+ file->getFileName().c_str() + ": " + msg;
|
||||||
|
os::Printer::log(logmsg.c_str(), ELL_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG function for file reading
|
// PNG function for file reading
|
||||||
|
@ -88,7 +94,7 @@ IImage *CImageLoaderPng::loadImage(io::IReadFile *file) const
|
||||||
|
|
||||||
// Allocate the png read struct
|
// Allocate the png read struct
|
||||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||||
NULL, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warn);
|
file, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warn);
|
||||||
if (!png_ptr) {
|
if (!png_ptr) {
|
||||||
os::Printer::log("LOAD PNG: Internal PNG create read struct failure", file->getFileName(), ELL_ERROR);
|
os::Printer::log("LOAD PNG: Internal PNG create read struct failure", file->getFileName(), ELL_ERROR);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -25,14 +25,20 @@ IImageWriter *createImageWriterPNG()
|
||||||
// PNG function for error handling
|
// PNG function for error handling
|
||||||
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
||||||
{
|
{
|
||||||
os::Printer::log("PNG fatal error", msg, ELL_ERROR);
|
io::IWriteFile *file = reinterpret_cast<io::IWriteFile *>(png_get_error_ptr(png_ptr));
|
||||||
|
std::string logmsg = std::string("PNG fatal error for ")
|
||||||
|
+ file->getFileName().c_str() + ": " + msg;
|
||||||
|
os::Printer::log(logmsg.c_str(), ELL_ERROR);
|
||||||
longjmp(png_jmpbuf(png_ptr), 1);
|
longjmp(png_jmpbuf(png_ptr), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG function for warning handling
|
// PNG function for warning handling
|
||||||
static void png_cpexcept_warning(png_structp png_ptr, png_const_charp msg)
|
static void png_cpexcept_warning(png_structp png_ptr, png_const_charp msg)
|
||||||
{
|
{
|
||||||
os::Printer::log("PNG warning", msg, ELL_WARNING);
|
io::IWriteFile *file = reinterpret_cast<io::IWriteFile *>(png_get_error_ptr(png_ptr));
|
||||||
|
std::string logmsg = std::string("PNG warning for ")
|
||||||
|
+ file->getFileName().c_str() + ": " + msg;
|
||||||
|
os::Printer::log(logmsg.c_str(), ELL_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG function for file writing
|
// PNG function for file writing
|
||||||
|
@ -66,7 +72,7 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param)
|
||||||
|
|
||||||
// Allocate the png write struct
|
// Allocate the png write struct
|
||||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
NULL, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warning);
|
file, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warning);
|
||||||
if (!png_ptr) {
|
if (!png_ptr) {
|
||||||
os::Printer::log("PNGWriter: Internal PNG create write struct failure", file->getFileName(), ELL_ERROR);
|
os::Printer::log("PNGWriter: Internal PNG create write struct failure", file->getFileName(), ELL_ERROR);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -249,16 +249,34 @@ void CIrrDeviceSDL::resetReceiveTextInputEvents()
|
||||||
//! constructor
|
//! constructor
|
||||||
CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
||||||
CIrrDeviceStub(param),
|
CIrrDeviceStub(param),
|
||||||
Window((SDL_Window *)param.WindowId), SDL_Flags(0),
|
Window((SDL_Window *)param.WindowId),
|
||||||
MouseX(0), MouseY(0), MouseXRel(0), MouseYRel(0), MouseButtonStates(0),
|
MouseX(0), MouseY(0), MouseXRel(0), MouseYRel(0), MouseButtonStates(0),
|
||||||
Width(param.WindowSize.Width), Height(param.WindowSize.Height),
|
Width(param.WindowSize.Width), Height(param.WindowSize.Height),
|
||||||
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0)
|
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0),
|
||||||
|
IsInBackground(false)
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
setDebugName("CIrrDeviceSDL");
|
setDebugName("CIrrDeviceSDL");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (++SDLDeviceInstances == 1) {
|
if (++SDLDeviceInstances == 1) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// Blocking on pause causes problems with multiplayer.
|
||||||
|
// See https://github.com/minetest/minetest/issues/10842.
|
||||||
|
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0");
|
||||||
|
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0");
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
|
||||||
|
|
||||||
|
// Minetest does its own screen keyboard handling.
|
||||||
|
SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Minetest has its own code to synthesize mouse events from touch events,
|
||||||
|
// so we prevent SDL from doing it.
|
||||||
|
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||||
|
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||||
|
|
||||||
u32 flags = SDL_INIT_TIMER | SDL_INIT_EVENTS;
|
u32 flags = SDL_INIT_TIMER | SDL_INIT_EVENTS;
|
||||||
if (CreationParams.DriverType != video::EDT_NULL)
|
if (CreationParams.DriverType != video::EDT_NULL)
|
||||||
flags |= SDL_INIT_VIDEO;
|
flags |= SDL_INIT_VIDEO;
|
||||||
|
@ -273,11 +291,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minetest has its own code to synthesize mouse events from touch events,
|
|
||||||
// so we prevent SDL from doing it.
|
|
||||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
|
||||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
|
||||||
|
|
||||||
// create keymap
|
// create keymap
|
||||||
createKeyMap();
|
createKeyMap();
|
||||||
|
|
||||||
|
@ -321,19 +334,21 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
||||||
//! destructor
|
//! destructor
|
||||||
CIrrDeviceSDL::~CIrrDeviceSDL()
|
CIrrDeviceSDL::~CIrrDeviceSDL()
|
||||||
{
|
{
|
||||||
if (--SDLDeviceInstances == 0) {
|
|
||||||
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
||||||
const u32 numJoysticks = Joysticks.size();
|
const u32 numJoysticks = Joysticks.size();
|
||||||
for (u32 i = 0; i < numJoysticks; ++i)
|
for (u32 i = 0; i < numJoysticks; ++i)
|
||||||
SDL_JoystickClose(Joysticks[i]);
|
SDL_JoystickClose(Joysticks[i]);
|
||||||
#endif
|
#endif
|
||||||
if (Window) {
|
if (Window && Context) {
|
||||||
SDL_GL_MakeCurrent(Window, NULL);
|
SDL_GL_MakeCurrent(Window, NULL);
|
||||||
SDL_GL_DeleteContext(Context);
|
SDL_GL_DeleteContext(Context);
|
||||||
SDL_DestroyWindow(Window);
|
}
|
||||||
}
|
if (Window) {
|
||||||
SDL_Quit();
|
SDL_DestroyWindow(Window);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (--SDLDeviceInstances == 0) {
|
||||||
|
SDL_Quit();
|
||||||
os::Printer::log("Quit SDL", ELL_INFORMATION);
|
os::Printer::log("Quit SDL", ELL_INFORMATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,6 +382,76 @@ void CIrrDeviceSDL::logAttributes()
|
||||||
|
|
||||||
bool CIrrDeviceSDL::createWindow()
|
bool CIrrDeviceSDL::createWindow()
|
||||||
{
|
{
|
||||||
|
if (Close)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (createWindowWithContext())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
while (CreationParams.AntiAlias > 0) {
|
||||||
|
CreationParams.AntiAlias--;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("AntiAlias reduced/disabled due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreationParams.WithAlphaChannel) {
|
||||||
|
CreationParams.WithAlphaChannel = false;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("WithAlphaChannel disabled due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreationParams.Stencilbuffer) {
|
||||||
|
CreationParams.Stencilbuffer = false;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("Stencilbuffer disabled due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (CreationParams.ZBufferBits > 16) {
|
||||||
|
CreationParams.ZBufferBits -= 8;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("ZBufferBits reduced due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (CreationParams.Bits > 16) {
|
||||||
|
CreationParams.Bits -= 8;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("Bits reduced due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreationParams.Stereobuffer) {
|
||||||
|
CreationParams.Stereobuffer = false;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("Stereobuffer disabled due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreationParams.Doublebuffer) {
|
||||||
|
// Try single buffer
|
||||||
|
CreationParams.Doublebuffer = false;
|
||||||
|
if (createWindowWithContext()) {
|
||||||
|
os::Printer::log("Doublebuffer disabled due to lack of support!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os::Printer::log("Could not create window and context!", ELL_ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIrrDeviceSDL::createWindowWithContext() {
|
||||||
|
u32 SDL_Flags = 0;
|
||||||
|
|
||||||
if (CreationParams.Fullscreen) {
|
if (CreationParams.Fullscreen) {
|
||||||
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
|
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
|
||||||
SDL_Flags |= SDL_WINDOW_FULLSCREEN;
|
SDL_Flags |= SDL_WINDOW_FULLSCREEN;
|
||||||
|
@ -419,9 +504,6 @@ bool CIrrDeviceSDL::createWindow()
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||||
if (Close)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
switch (CreationParams.DriverType) {
|
switch (CreationParams.DriverType) {
|
||||||
case video::EDT_OPENGL:
|
case video::EDT_OPENGL:
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||||
|
@ -446,9 +528,13 @@ bool CIrrDeviceSDL::createWindow()
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Makes context creation fail on some Android devices.
|
||||||
|
See discussion in https://github.com/minetest/minetest/pull/14498.
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
|
||||||
#endif
|
#endif
|
||||||
|
*/
|
||||||
|
|
||||||
if (CreationParams.Bits == 16) {
|
if (CreationParams.Bits == 16) {
|
||||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||||
|
@ -468,46 +554,33 @@ bool CIrrDeviceSDL::createWindow()
|
||||||
if (CreationParams.AntiAlias > 1) {
|
if (CreationParams.AntiAlias > 1) {
|
||||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
|
||||||
}
|
} else {
|
||||||
if (!Window)
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
||||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
|
||||||
if (!Window) {
|
|
||||||
os::Printer::log("Could not create window...", SDL_GetError(), ELL_WARNING);
|
|
||||||
}
|
|
||||||
if (!Window && CreationParams.AntiAlias > 1) {
|
|
||||||
while (--CreationParams.AntiAlias > 1) {
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
|
|
||||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
|
||||||
if (Window)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!Window) {
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
|
|
||||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
|
||||||
if (Window)
|
|
||||||
os::Printer::log("AntiAliasing disabled due to lack of support!", ELL_WARNING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Window && CreationParams.Doublebuffer) {
|
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||||
// Try single buffer
|
|
||||||
if (CreationParams.DriverType == video::EDT_OPENGL)
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
|
||||||
}
|
|
||||||
if (!Window) {
|
if (!Window) {
|
||||||
os::Printer::log("Could not initialize display", SDL_GetError(), ELL_ERROR);
|
os::Printer::log("Could not create window", SDL_GetError(), ELL_WARNING);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context = SDL_GL_CreateContext(Window);
|
Context = SDL_GL_CreateContext(Window);
|
||||||
if (!Context) {
|
if (!Context) {
|
||||||
os::Printer::log("Could not initialize context", SDL_GetError(), ELL_ERROR);
|
os::Printer::log("Could not create context", SDL_GetError(), ELL_WARNING);
|
||||||
SDL_DestroyWindow(Window);
|
SDL_DestroyWindow(Window);
|
||||||
|
Window = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Width and Height to match the actual window size.
|
||||||
|
// In fullscreen mode, the window size specified in SIrrlichtCreationParameters
|
||||||
|
// is ignored, so we cannot rely on it.
|
||||||
|
int w = 0, h = 0;
|
||||||
|
SDL_GetWindowSize(Window, &w, &h);
|
||||||
|
Width = w;
|
||||||
|
Height = h;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
|
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||||
}
|
}
|
||||||
|
@ -619,7 +692,17 @@ bool CIrrDeviceSDL::run()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (SDL_event.button.button) {
|
auto button = SDL_event.button.button;
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// Android likes to send the right mouse button as the back button.
|
||||||
|
// According to some web searches I did, this is probably
|
||||||
|
// vendor/device-specific.
|
||||||
|
// Since a working right mouse button is very important for
|
||||||
|
// Minetest, we have this little hack.
|
||||||
|
if (button == SDL_BUTTON_X2)
|
||||||
|
button = SDL_BUTTON_RIGHT;
|
||||||
|
#endif
|
||||||
|
switch (button) {
|
||||||
case SDL_BUTTON_LEFT:
|
case SDL_BUTTON_LEFT:
|
||||||
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
|
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
|
||||||
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
|
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
|
||||||
|
@ -770,6 +853,20 @@ bool CIrrDeviceSDL::run()
|
||||||
postEventFromUser(irrevent);
|
postEventFromUser(irrevent);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Contrary to what the SDL documentation says, SDL_APP_WILLENTERBACKGROUND
|
||||||
|
// and SDL_APP_WILLENTERFOREGROUND are actually sent in onStop/onStart,
|
||||||
|
// not onPause/onResume, on recent Android versions. This can be verified
|
||||||
|
// by testing or by looking at the org.libsdl.app.SDLActivity Java code.
|
||||||
|
// -> This means we can use them to implement isWindowVisible().
|
||||||
|
|
||||||
|
case SDL_APP_WILLENTERBACKGROUND:
|
||||||
|
IsInBackground = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_APP_WILLENTERFOREGROUND:
|
||||||
|
IsInBackground = false;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
} // end switch
|
} // end switch
|
||||||
|
@ -996,11 +1093,6 @@ void CIrrDeviceSDL::setResizable(bool resize)
|
||||||
return;
|
return;
|
||||||
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||||
if (resize != Resizable) {
|
if (resize != Resizable) {
|
||||||
if (resize)
|
|
||||||
SDL_Flags |= SDL_WINDOW_RESIZABLE;
|
|
||||||
else
|
|
||||||
SDL_Flags &= ~SDL_WINDOW_RESIZABLE;
|
|
||||||
|
|
||||||
if (Window) {
|
if (Window) {
|
||||||
SDL_SetWindowResizable(Window, (SDL_bool)resize);
|
SDL_SetWindowResizable(Window, (SDL_bool)resize);
|
||||||
}
|
}
|
||||||
|
@ -1051,6 +1143,11 @@ bool CIrrDeviceSDL::isFullscreen() const
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CIrrDeviceSDL::isWindowVisible() const
|
||||||
|
{
|
||||||
|
return !IsInBackground;
|
||||||
|
}
|
||||||
|
|
||||||
//! returns if window is active. if not, nothing need to be drawn
|
//! returns if window is active. if not, nothing need to be drawn
|
||||||
bool CIrrDeviceSDL::isWindowActive() const
|
bool CIrrDeviceSDL::isWindowActive() const
|
||||||
{
|
{
|
||||||
|
@ -1109,6 +1206,8 @@ void CIrrDeviceSDL::createKeyMap()
|
||||||
|
|
||||||
// buttons missing
|
// buttons missing
|
||||||
|
|
||||||
|
KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_CANCEL));
|
||||||
|
|
||||||
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
|
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
|
||||||
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
|
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
|
||||||
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));
|
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));
|
||||||
|
|
|
@ -86,6 +86,9 @@ public:
|
||||||
/** \return True if window is fullscreen. */
|
/** \return True if window is fullscreen. */
|
||||||
bool isFullscreen() const override;
|
bool isFullscreen() const override;
|
||||||
|
|
||||||
|
//! Checks if the window could possibly be visible.
|
||||||
|
bool isWindowVisible() const override;
|
||||||
|
|
||||||
//! Get the position of this window on screen
|
//! Get the position of this window on screen
|
||||||
core::position2di getWindowPosition() override;
|
core::position2di getWindowPosition() override;
|
||||||
|
|
||||||
|
@ -277,13 +280,13 @@ private:
|
||||||
void createDriver();
|
void createDriver();
|
||||||
|
|
||||||
bool createWindow();
|
bool createWindow();
|
||||||
|
bool createWindowWithContext();
|
||||||
|
|
||||||
void createKeyMap();
|
void createKeyMap();
|
||||||
|
|
||||||
void logAttributes();
|
void logAttributes();
|
||||||
SDL_GLContext Context;
|
SDL_GLContext Context;
|
||||||
SDL_Window *Window;
|
SDL_Window *Window;
|
||||||
int SDL_Flags;
|
|
||||||
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
||||||
core::array<SDL_Joystick *> Joysticks;
|
core::array<SDL_Joystick *> Joysticks;
|
||||||
#endif
|
#endif
|
||||||
|
@ -319,6 +322,7 @@ private:
|
||||||
SDL_SysWMinfo Info;
|
SDL_SysWMinfo Info;
|
||||||
|
|
||||||
s32 CurrentTouchCount;
|
s32 CurrentTouchCount;
|
||||||
|
bool IsInBackground;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace irr
|
} // end namespace irr
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
if(NOT ANDROID AND NOT APPLE)
|
if(NOT APPLE)
|
||||||
set(DEFAULT_SDL2 ON)
|
set(DEFAULT_SDL2 ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -77,10 +77,9 @@ elseif(APPLE)
|
||||||
set(DEVICE "OSX")
|
set(DEVICE "OSX")
|
||||||
elseif(ANDROID)
|
elseif(ANDROID)
|
||||||
add_definitions(-D_IRR_ANDROID_PLATFORM_)
|
add_definitions(-D_IRR_ANDROID_PLATFORM_)
|
||||||
if(USE_SDL2)
|
if(NOT USE_SDL2)
|
||||||
message(FATAL_ERROR "SDL2 device is not (yet) supported on Android")
|
message(FATAL_ERROR "The Android build requires SDL2")
|
||||||
endif()
|
endif()
|
||||||
set(DEVICE "ANDROID")
|
|
||||||
elseif(EMSCRIPTEN)
|
elseif(EMSCRIPTEN)
|
||||||
add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||||
set(LINUX_PLATFORM TRUE)
|
set(LINUX_PLATFORM TRUE)
|
||||||
|
@ -131,7 +130,10 @@ endif()
|
||||||
# OpenGL
|
# OpenGL
|
||||||
|
|
||||||
if(USE_SDL2)
|
if(USE_SDL2)
|
||||||
option(ENABLE_OPENGL3 "Enable OpenGL 3+" TRUE)
|
if(NOT ANDROID)
|
||||||
|
set(DEFAULT_OPENGL3 TRUE)
|
||||||
|
endif()
|
||||||
|
option(ENABLE_OPENGL3 "Enable OpenGL 3+" ${DEFAULT_OPENGL3})
|
||||||
else()
|
else()
|
||||||
set(ENABLE_OPENGL3 FALSE)
|
set(ENABLE_OPENGL3 FALSE)
|
||||||
endif()
|
endif()
|
||||||
|
@ -142,13 +144,10 @@ else()
|
||||||
option(ENABLE_OPENGL "Enable OpenGL" TRUE)
|
option(ENABLE_OPENGL "Enable OpenGL" TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(EMSCRIPTEN OR APPLE)
|
if(USE_SDL2 OR EMSCRIPTEN OR APPLE)
|
||||||
set(ENABLE_GLES1 FALSE)
|
set(ENABLE_GLES1 FALSE)
|
||||||
else()
|
else()
|
||||||
if(ANDROID)
|
option(ENABLE_GLES1 "Enable OpenGL ES" FALSE)
|
||||||
set(DEFAULT_GLES1 TRUE)
|
|
||||||
endif()
|
|
||||||
option(ENABLE_GLES1 "Enable OpenGL ES" ${DEFAULT_GLES1})
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
|
@ -188,19 +187,16 @@ if(ENABLE_OPENGL3)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_GLES1)
|
if(ENABLE_GLES1)
|
||||||
if (USE_SDL2)
|
|
||||||
message(FATAL_ERROR "OpenGL ES 1 is not supported with SDL2")
|
|
||||||
endif()
|
|
||||||
add_definitions(-D_IRR_COMPILE_WITH_OGLES1_)
|
add_definitions(-D_IRR_COMPILE_WITH_OGLES1_)
|
||||||
set(OPENGLES_DIRECT_LINK TRUE)
|
set(OPENGLES_DIRECT_LINK TRUE)
|
||||||
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$")
|
if(DEVICE MATCHES "^(WINDOWS|X11)$")
|
||||||
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_GLES2)
|
if(ENABLE_GLES2)
|
||||||
add_definitions(-D_IRR_COMPILE_WITH_OGLES2_)
|
add_definitions(-D_IRR_COMPILE_WITH_OGLES2_)
|
||||||
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$" OR EMSCRIPTEN)
|
if(DEVICE MATCHES "^(WINDOWS|X11)$" OR EMSCRIPTEN)
|
||||||
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
@ -248,11 +244,15 @@ endif()
|
||||||
if(ENABLE_GLES2)
|
if(ENABLE_GLES2)
|
||||||
find_package(OpenGLES2 REQUIRED)
|
find_package(OpenGLES2 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
if(ENABLE_OPENGL OR ENABLE_OPENGL3)
|
if(ENABLE_OPENGL)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
if(USE_SDL2)
|
if(USE_SDL2)
|
||||||
find_package(SDL2 REQUIRED)
|
if(NOT ANDROID)
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
else()
|
||||||
|
# provided by MinetestAndroidLibs.cmake
|
||||||
|
endif()
|
||||||
message(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
|
message(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
|
||||||
|
|
||||||
# unfortunately older SDL does not provide its version to cmake, so check header.
|
# unfortunately older SDL does not provide its version to cmake, so check header.
|
||||||
|
@ -323,7 +323,6 @@ set(link_includes
|
||||||
${OPENGLES2_INCLUDE_DIR}
|
${OPENGLES2_INCLUDE_DIR}
|
||||||
${EGL_INCLUDE_DIR}
|
${EGL_INCLUDE_DIR}
|
||||||
|
|
||||||
"$<$<PLATFORM_ID:Android>:${ANDROID_NDK}/sources/android/native_app_glue>"
|
|
||||||
"$<$<BOOL:${USE_X11}>:${X11_INCLUDE_DIR}>"
|
"$<$<BOOL:${USE_X11}>:${X11_INCLUDE_DIR}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -453,14 +452,7 @@ if(ENABLE_OPENGL3)
|
||||||
target_compile_definitions(IRROTHEROBJ PRIVATE ENABLE_OPENGL3)
|
target_compile_definitions(IRROTHEROBJ PRIVATE ENABLE_OPENGL3)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ANDROID)
|
if(APPLE)
|
||||||
target_sources(IRROTHEROBJ PRIVATE
|
|
||||||
Android/CIrrDeviceAndroid.cpp
|
|
||||||
Android/CAndroidAssetReader.cpp
|
|
||||||
Android/CAndroidAssetFileArchive.cpp
|
|
||||||
Android/CKeyEventWrapper.cpp
|
|
||||||
)
|
|
||||||
elseif(APPLE)
|
|
||||||
# Build all IRROTHEROBJ sources as objc++, including the .cpp's
|
# Build all IRROTHEROBJ sources as objc++, including the .cpp's
|
||||||
set_target_properties(IRROTHEROBJ PROPERTIES COMPILE_OPTIONS "-xobjective-c++")
|
set_target_properties(IRROTHEROBJ PROPERTIES COMPILE_OPTIONS "-xobjective-c++")
|
||||||
target_sources(IRROTHEROBJ PRIVATE
|
target_sources(IRROTHEROBJ PRIVATE
|
||||||
|
@ -535,7 +527,8 @@ target_link_libraries(IrrlichtMt PRIVATE
|
||||||
"$<$<BOOL:${OPENGLES_DIRECT_LINK}>:${OPENGLES_LIBRARY}>"
|
"$<$<BOOL:${OPENGLES_DIRECT_LINK}>:${OPENGLES_LIBRARY}>"
|
||||||
${EGL_LIBRARY}
|
${EGL_LIBRARY}
|
||||||
|
|
||||||
"$<$<PLATFORM_ID:Android>:-landroid -llog>"
|
# incl. transitive SDL2 dependencies for static linking
|
||||||
|
"$<$<PLATFORM_ID:Android>:-landroid -llog -lGLESv2 -lGLESv1_CM -lOpenSLES>"
|
||||||
${COCOA_LIB}
|
${COCOA_LIB}
|
||||||
${IOKIT_LIB}
|
${IOKIT_LIB}
|
||||||
"$<$<PLATFORM_ID:Windows>:gdi32>"
|
"$<$<PLATFORM_ID:Windows>:gdi32>"
|
||||||
|
|
|
@ -983,6 +983,11 @@ IImage *CNullDriver::createImageFromFile(io::IReadFile *file)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
file->seek(0); // reset file position which might have changed due to previous loadImage calls
|
file->seek(0); // reset file position which might have changed due to previous loadImage calls
|
||||||
|
// avoid warnings if extension is wrong
|
||||||
|
if (!SurfaceLoader[i]->isALoadableFileFormat(file))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
file->seek(0);
|
||||||
if (IImage *image = SurfaceLoader[i]->loadImage(file))
|
if (IImage *image = SurfaceLoader[i]->loadImage(file))
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,6 @@
|
||||||
#include "CImage.h"
|
#include "CImage.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
|
||||||
#include "android_native_app_glue.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
namespace video
|
namespace video
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "SMaterial.h"
|
#include "SMaterial.h"
|
||||||
#include "fast_atof.h"
|
#include "fast_atof.h"
|
||||||
|
|
||||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) || defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
|
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
|
||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
#else
|
#else
|
||||||
#include <GLES/egl.h>
|
#include <GLES/egl.h>
|
||||||
|
|
|
@ -28,10 +28,6 @@ static const char *const copyright = "Irrlicht Engine (c) 2002-2017 Nikolaus Geb
|
||||||
#include "CIrrDeviceSDL.h"
|
#include "CIrrDeviceSDL.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
|
||||||
#include "Android/CIrrDeviceAndroid.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
//! stub for calling createDeviceEx
|
//! stub for calling createDeviceEx
|
||||||
|
@ -74,11 +70,6 @@ extern "C" IRRLICHT_API IrrlichtDevice *IRRCALLCONV createDeviceEx(const SIrrlic
|
||||||
dev = new CIrrDeviceLinux(params);
|
dev = new CIrrDeviceLinux(params);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
|
||||||
if (params.DeviceType == EIDT_ANDROID || (!dev && params.DeviceType == EIDT_BEST))
|
|
||||||
dev = new CIrrDeviceAndroid(params);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
||||||
if (params.DeviceType == EIDT_SDL || (!dev && params.DeviceType == EIDT_BEST))
|
if (params.DeviceType == EIDT_SDL || (!dev && params.DeviceType == EIDT_BEST))
|
||||||
dev = new CIrrDeviceSDL(params);
|
dev = new CIrrDeviceSDL(params);
|
||||||
|
|
|
@ -21,10 +21,6 @@
|
||||||
#include "CImage.h"
|
#include "CImage.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
|
||||||
#include "android_native_app_glue.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "mt_opengl.h"
|
#include "mt_opengl.h"
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
|
|
|
@ -1,66 +1,66 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winuser.h>
|
#include <winuser.h>
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <richedit.h>
|
#include <richedit.h>
|
||||||
|
|
||||||
#ifndef USE_CMAKE_CONFIG_H
|
#ifndef USE_CMAKE_CONFIG_H
|
||||||
#define USE_CMAKE_CONFIG_H
|
#define USE_CMAKE_CONFIG_H
|
||||||
#endif
|
#endif
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#undef USE_CMAKE_CONFIG_H
|
#undef USE_CMAKE_CONFIG_H
|
||||||
|
|
||||||
#if RUN_IN_PLACE
|
#if RUN_IN_PLACE
|
||||||
#define BUILDMODE "RUN_IN_PLACE=1"
|
#define BUILDMODE "RUN_IN_PLACE=1"
|
||||||
#else
|
#else
|
||||||
#define BUILDMODE "RUN_IN_PLACE=0"
|
#define BUILDMODE "RUN_IN_PLACE=0"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
|
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LANGUAGE 0, SUBLANG_NEUTRAL
|
LANGUAGE 0, SUBLANG_NEUTRAL
|
||||||
130 ICON "minetest-icon.ico"
|
130 ICON "minetest-icon.ico"
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Version
|
// Version
|
||||||
//
|
//
|
||||||
|
|
||||||
1 VERSIONINFO
|
1 VERSIONINFO
|
||||||
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||||
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
#else
|
#else
|
||||||
FILEFLAGS 0x0L
|
FILEFLAGS 0x0L
|
||||||
#endif
|
#endif
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
FILETYPE VFT_APP
|
FILETYPE VFT_APP
|
||||||
FILESUBTYPE 0x0L
|
FILESUBTYPE 0x0L
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "StringFileInfo"
|
BLOCK "StringFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904b0"
|
BLOCK "040904b0"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "Comments", ""
|
VALUE "Comments", ""
|
||||||
VALUE "CompanyName", PROJECT_NAME_C " community"
|
VALUE "CompanyName", PROJECT_NAME_C " community"
|
||||||
VALUE "FileDescription", PROJECT_NAME_C " engine"
|
VALUE "FileDescription", PROJECT_NAME_C " engine"
|
||||||
VALUE "FileVersion", VERSION_STRING
|
VALUE "FileVersion", VERSION_STRING
|
||||||
VALUE "InternalName", PROJECT_NAME
|
VALUE "InternalName", PROJECT_NAME
|
||||||
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
|
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
|
||||||
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
|
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
|
||||||
VALUE "OriginalFilename", "minetest.exe"
|
VALUE "OriginalFilename", "minetest.exe"
|
||||||
VALUE "PrivateBuild", VERSION_EXTRA
|
VALUE "PrivateBuild", VERSION_EXTRA
|
||||||
VALUE "ProductName", PROJECT_NAME_C
|
VALUE "ProductName", PROJECT_NAME_C
|
||||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||||
VALUE "SpecialBuild", BUILDMODE
|
VALUE "SpecialBuild", BUILDMODE
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "Translation", 0x409, 1200
|
VALUE "Translation", 0x409, 1200
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
|
@ -307,10 +307,6 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
|
|
||||||
add_library(native_app_glue OBJECT ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
|
||||||
set(PLATFORM_LIBS ${PLATFORM_LIBS} native_app_glue)
|
|
||||||
|
|
||||||
set(PLATFORM_LIBS ${PLATFORM_LIBS} android log)
|
set(PLATFORM_LIBS ${PLATFORM_LIBS} android log)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
@ -518,6 +514,9 @@ include_directories(SYSTEM
|
||||||
${GMP_INCLUDE_DIR}
|
${GMP_INCLUDE_DIR}
|
||||||
${JSON_INCLUDE_DIR}
|
${JSON_INCLUDE_DIR}
|
||||||
${LUA_BIT_INCLUDE_DIR}
|
${LUA_BIT_INCLUDE_DIR}
|
||||||
|
# on Android, Minetest depends on SDL2 directly
|
||||||
|
# on other platforms, only IrrlichtMt depends on SDL2
|
||||||
|
"$<$<PLATFORM_ID:Android>:${SDL2_INCLUDE_DIRS}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(USE_GETTEXT)
|
if(USE_GETTEXT)
|
||||||
|
@ -562,6 +561,9 @@ if(BUILD_CLIENT)
|
||||||
${LUA_BIT_LIBRARY}
|
${LUA_BIT_LIBRARY}
|
||||||
${FREETYPE_LIBRARY}
|
${FREETYPE_LIBRARY}
|
||||||
${PLATFORM_LIBS}
|
${PLATFORM_LIBS}
|
||||||
|
# on Android, Minetest depends on SDL2 directly
|
||||||
|
# on other platforms, only IrrlichtMt depends on SDL2
|
||||||
|
"$<$<PLATFORM_ID:Android>:${SDL2_LIBRARIES}>"
|
||||||
)
|
)
|
||||||
if(NOT USE_LUAJIT)
|
if(NOT USE_LUAJIT)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
|
@ -698,11 +700,13 @@ include(CheckCSourceCompiles)
|
||||||
|
|
||||||
set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR})
|
set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR})
|
||||||
if(USE_LUAJIT)
|
if(USE_LUAJIT)
|
||||||
set(CMAKE_REQUIRED_LIBRARIES ${LUA_LIBRARY})
|
# libm usually required if statically linking
|
||||||
|
set(CMAKE_REQUIRED_LIBRARIES ${LUA_LIBRARY} m)
|
||||||
# LuaJIT provides exactly zero ways to determine how recent it is (the version
|
# LuaJIT provides exactly zero ways to determine how recent it is (the version
|
||||||
# is unchanged since 2017), however it happens that string buffers were added
|
# is unchanged since 2017), however it happens that string buffers were added
|
||||||
# after the changes which we care about so that works as an indicator.
|
# after the changes which we care about so that works as an indicator.
|
||||||
# (https://github.com/LuaJIT/LuaJIT/commit/4c6b669 March 2021)
|
# (https://github.com/LuaJIT/LuaJIT/commit/4c6b669 March 2021)
|
||||||
|
# Note: This is no longer true as of August 2023, but we're keeping the old check.
|
||||||
unset(HAVE_RECENT_LJ CACHE)
|
unset(HAVE_RECENT_LJ CACHE)
|
||||||
check_symbol_exists(luaopen_string_buffer "lualib.h" HAVE_RECENT_LJ)
|
check_symbol_exists(luaopen_string_buffer "lualib.h" HAVE_RECENT_LJ)
|
||||||
if(NOT HAVE_RECENT_LJ)
|
if(NOT HAVE_RECENT_LJ)
|
||||||
|
|
|
@ -69,6 +69,7 @@ set(client_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/texturepaths.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/texturepaths.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/imagesource.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
|
||||||
|
|
|
@ -799,7 +799,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
|
||||||
video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
|
video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
|
||||||
|
|
||||||
io::IReadFile *rfile = irrfs->createMemoryReadFile(
|
io::IReadFile *rfile = irrfs->createMemoryReadFile(
|
||||||
data.c_str(), data.size(), "_tempreadfile");
|
data.c_str(), data.size(), filename.c_str());
|
||||||
|
|
||||||
FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
|
FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
|
||||||
|
|
||||||
|
@ -1795,6 +1795,11 @@ float Client::mediaReceiveProgress()
|
||||||
return 1.0; // downloader only exists when not yet done
|
return 1.0; // downloader only exists when not yet done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::drawLoadScreen(const std::wstring &text, float dtime, int percent) {
|
||||||
|
m_rendering_engine->run();
|
||||||
|
m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, dtime, percent);
|
||||||
|
}
|
||||||
|
|
||||||
struct TextureUpdateArgs {
|
struct TextureUpdateArgs {
|
||||||
gui::IGUIEnvironment *guienv;
|
gui::IGUIEnvironment *guienv;
|
||||||
u64 last_time_ms;
|
u64 last_time_ms;
|
||||||
|
|
|
@ -356,6 +356,7 @@ public:
|
||||||
|
|
||||||
float mediaReceiveProgress();
|
float mediaReceiveProgress();
|
||||||
|
|
||||||
|
void drawLoadScreen(const std::wstring &text, float dtime, int percent);
|
||||||
void afterContentReceived();
|
void afterContentReceived();
|
||||||
void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
|
void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "clientmedia.h"
|
#include "clientmedia.h"
|
||||||
|
#include "gettext.h"
|
||||||
#include "httpfetch.h"
|
#include "httpfetch.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "filecache.h"
|
#include "filecache.h"
|
||||||
|
@ -184,6 +185,11 @@ void ClientMediaDownloader::step(Client *client)
|
||||||
|
|
||||||
void ClientMediaDownloader::initialStep(Client *client)
|
void ClientMediaDownloader::initialStep(Client *client)
|
||||||
{
|
{
|
||||||
|
std::wstring loading_text = wstrgettext("Media...");
|
||||||
|
// Tradeoff between responsiveness during media loading and media loading speed
|
||||||
|
const u64 chunk_time_ms = 33;
|
||||||
|
u64 last_time = porting::getTimeMs();
|
||||||
|
|
||||||
// Check media cache
|
// Check media cache
|
||||||
m_uncached_count = m_files.size();
|
m_uncached_count = m_files.size();
|
||||||
for (auto &file_it : m_files) {
|
for (auto &file_it : m_files) {
|
||||||
|
@ -195,6 +201,13 @@ void ClientMediaDownloader::initialStep(Client *client)
|
||||||
filestatus->received = true;
|
filestatus->received = true;
|
||||||
m_uncached_count--;
|
m_uncached_count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 cur_time = porting::getTimeMs();
|
||||||
|
u64 dtime = porting::getDeltaMs(last_time, cur_time);
|
||||||
|
if (dtime >= chunk_time_ms) {
|
||||||
|
client->drawLoadScreen(loading_text, dtime / 1000.0f, 30);
|
||||||
|
last_time = cur_time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(m_uncached_received_count == 0);
|
assert(m_uncached_received_count == 0);
|
||||||
|
|
|
@ -66,6 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "sky.h"
|
#include "sky.h"
|
||||||
|
#include "threading/lambda.h"
|
||||||
#include "translation.h"
|
#include "translation.h"
|
||||||
#include "util/basic_macros.h"
|
#include "util/basic_macros.h"
|
||||||
#include "util/directiontables.h"
|
#include "util/directiontables.h"
|
||||||
|
@ -386,7 +387,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
|
||||||
CachedVertexShaderSetting<float, 3> m_eye_position_vertex{"eyePosition"};
|
CachedVertexShaderSetting<float, 3> m_eye_position_vertex{"eyePosition"};
|
||||||
CachedPixelShaderSetting<float, 3> m_minimap_yaw{"yawVec"};
|
CachedPixelShaderSetting<float, 3> m_minimap_yaw{"yawVec"};
|
||||||
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel{"cameraOffset"};
|
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel{"cameraOffset"};
|
||||||
CachedPixelShaderSetting<float, 3> m_camera_offset_vertex{"cameraOffset"};
|
CachedVertexShaderSetting<float, 3> m_camera_offset_vertex{"cameraOffset"};
|
||||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
|
CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
|
||||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
|
CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
|
||||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
|
CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
|
||||||
|
@ -484,10 +485,6 @@ public:
|
||||||
m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services);
|
m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services);
|
||||||
m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services);
|
m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services);
|
||||||
|
|
||||||
v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
|
|
||||||
m_eye_position_pixel.set(epos, services);
|
|
||||||
m_eye_position_vertex.set(epos, services);
|
|
||||||
|
|
||||||
if (m_client->getMinimap()) {
|
if (m_client->getMinimap()) {
|
||||||
v3f minimap_yaw = m_client->getMinimap()->getYawVec();
|
v3f minimap_yaw = m_client->getMinimap()->getYawVec();
|
||||||
m_minimap_yaw.set(minimap_yaw, services);
|
m_minimap_yaw.set(minimap_yaw, services);
|
||||||
|
@ -945,6 +942,7 @@ private:
|
||||||
f32 m_cache_mouse_sensitivity;
|
f32 m_cache_mouse_sensitivity;
|
||||||
f32 m_cache_joystick_frustum_sensitivity;
|
f32 m_cache_joystick_frustum_sensitivity;
|
||||||
f32 m_repeat_place_time;
|
f32 m_repeat_place_time;
|
||||||
|
f32 m_repeat_dig_time;
|
||||||
f32 m_cache_cam_smoothing;
|
f32 m_cache_cam_smoothing;
|
||||||
|
|
||||||
bool m_invert_mouse;
|
bool m_invert_mouse;
|
||||||
|
@ -991,6 +989,8 @@ Game::Game() :
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
g_settings->registerChangedCallback("repeat_place_time",
|
g_settings->registerChangedCallback("repeat_place_time",
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
|
g_settings->registerChangedCallback("repeat_dig_time",
|
||||||
|
&settingChangedCallback, this);
|
||||||
g_settings->registerChangedCallback("noclip",
|
g_settings->registerChangedCallback("noclip",
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
g_settings->registerChangedCallback("free_move",
|
g_settings->registerChangedCallback("free_move",
|
||||||
|
@ -1023,12 +1023,6 @@ Game::Game() :
|
||||||
|
|
||||||
Game::~Game()
|
Game::~Game()
|
||||||
{
|
{
|
||||||
delete client;
|
|
||||||
delete soundmaker;
|
|
||||||
sound_manager.reset();
|
|
||||||
|
|
||||||
delete server; // deleted first to stop all server threads
|
|
||||||
|
|
||||||
delete hud;
|
delete hud;
|
||||||
delete camera;
|
delete camera;
|
||||||
delete quicktune;
|
delete quicktune;
|
||||||
|
@ -1057,6 +1051,8 @@ Game::~Game()
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
g_settings->deregisterChangedCallback("repeat_place_time",
|
g_settings->deregisterChangedCallback("repeat_place_time",
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
|
g_settings->deregisterChangedCallback("repeat_dig_time",
|
||||||
|
&settingChangedCallback, this);
|
||||||
g_settings->deregisterChangedCallback("noclip",
|
g_settings->deregisterChangedCallback("noclip",
|
||||||
&settingChangedCallback, this);
|
&settingChangedCallback, this);
|
||||||
g_settings->deregisterChangedCallback("free_move",
|
g_settings->deregisterChangedCallback("free_move",
|
||||||
|
@ -1275,6 +1271,28 @@ void Game::shutdown()
|
||||||
sleep_ms(100);
|
sleep_ms(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete client;
|
||||||
|
delete soundmaker;
|
||||||
|
sound_manager.reset();
|
||||||
|
|
||||||
|
auto stop_thread = runInThread([=] {
|
||||||
|
delete server;
|
||||||
|
}, "ServerStop");
|
||||||
|
|
||||||
|
FpsControl fps_control;
|
||||||
|
fps_control.reset();
|
||||||
|
|
||||||
|
while (stop_thread->isRunning()) {
|
||||||
|
m_rendering_engine->run();
|
||||||
|
f32 dtime;
|
||||||
|
fps_control.limit(device, &dtime);
|
||||||
|
showOverlayMessage(N_("Shutting down..."), dtime, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_thread->rethrow();
|
||||||
|
|
||||||
|
// to be continued in Game::~Game
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1380,11 +1398,33 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
|
||||||
|
|
||||||
server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
|
server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
|
||||||
false, nullptr, error_message);
|
false, nullptr, error_message);
|
||||||
server->start();
|
|
||||||
|
|
||||||
copyServerClientCache();
|
auto start_thread = runInThread([=] {
|
||||||
|
server->start();
|
||||||
|
copyServerClientCache();
|
||||||
|
}, "ServerStart");
|
||||||
|
|
||||||
return true;
|
input->clear();
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
FpsControl fps_control;
|
||||||
|
fps_control.reset();
|
||||||
|
|
||||||
|
while (start_thread->isRunning()) {
|
||||||
|
if (!m_rendering_engine->run() || input->cancelPressed())
|
||||||
|
success = false;
|
||||||
|
f32 dtime;
|
||||||
|
fps_control.limit(device, &dtime);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
showOverlayMessage(N_("Creating server..."), dtime, 5);
|
||||||
|
else
|
||||||
|
showOverlayMessage(N_("Shutting down..."), dtime, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
start_thread->rethrow();
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::copyServerClientCache()
|
void Game::copyServerClientCache()
|
||||||
|
@ -2226,9 +2266,11 @@ void Game::openConsole(float scale, const wchar_t *line)
|
||||||
assert(scale > 0.0f && scale <= 1.0f);
|
assert(scale > 0.0f && scale <= 1.0f);
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
porting::showTextInputDialog("", "", 2);
|
if (!porting::hasPhysicalKeyboardAndroid()) {
|
||||||
m_android_chat_open = true;
|
porting::showTextInputDialog("", "", 2);
|
||||||
#else
|
m_android_chat_open = true;
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
if (gui_chat_console->isOpenInhibited())
|
if (gui_chat_console->isOpenInhibited())
|
||||||
return;
|
return;
|
||||||
gui_chat_console->openConsole(scale);
|
gui_chat_console->openConsole(scale);
|
||||||
|
@ -2236,6 +2278,8 @@ void Game::openConsole(float scale, const wchar_t *line)
|
||||||
gui_chat_console->setCloseOnEnter(true);
|
gui_chat_console->setCloseOnEnter(true);
|
||||||
gui_chat_console->replaceAndAddToHistory(line);
|
gui_chat_console->replaceAndAddToHistory(line);
|
||||||
}
|
}
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
} // else
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3282,8 +3326,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
|
||||||
if (pointed != runData.pointed_old)
|
if (pointed != runData.pointed_old)
|
||||||
infostream << "Pointing at " << pointed.dump() << std::endl;
|
infostream << "Pointing at " << pointed.dump() << std::endl;
|
||||||
|
|
||||||
if (g_touchscreengui)
|
if (g_touchscreengui) {
|
||||||
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
|
auto mode = selected_def.touch_interaction.getMode(pointed.type);
|
||||||
|
g_touchscreengui->applyContextControls(mode);
|
||||||
|
}
|
||||||
|
|
||||||
// Note that updating the selection mesh every frame is not particularly efficient,
|
// Note that updating the selection mesh every frame is not particularly efficient,
|
||||||
// but the halo rendering code is already inefficient so there's no point in optimizing it here
|
// but the halo rendering code is already inefficient so there's no point in optimizing it here
|
||||||
|
@ -3919,12 +3965,14 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
|
||||||
runData.nodig_delay_timer =
|
runData.nodig_delay_timer =
|
||||||
runData.dig_time_complete / (float)crack_animation_length;
|
runData.dig_time_complete / (float)crack_animation_length;
|
||||||
|
|
||||||
// We don't want a corresponding delay to very time consuming nodes
|
// Don't add a corresponding delay to very time consuming nodes.
|
||||||
// and nodes without digging time (e.g. torches) get a fixed delay.
|
runData.nodig_delay_timer = std::min(runData.nodig_delay_timer, 0.3f);
|
||||||
if (runData.nodig_delay_timer > 0.3)
|
|
||||||
runData.nodig_delay_timer = 0.3;
|
// Ensure that the delay between breaking nodes
|
||||||
else if (runData.dig_instantly)
|
// (dig_time_complete + nodig_delay_timer) is at least the
|
||||||
runData.nodig_delay_timer = 0.15;
|
// value of the repeat_dig_time setting.
|
||||||
|
runData.nodig_delay_timer = std::max(runData.nodig_delay_timer,
|
||||||
|
m_repeat_dig_time - runData.dig_time_complete);
|
||||||
|
|
||||||
if (client->modsLoaded() &&
|
if (client->modsLoaded() &&
|
||||||
client->getScript()->on_dignode(nodepos, n)) {
|
client->getScript()->on_dignode(nodepos, n)) {
|
||||||
|
@ -4323,7 +4371,8 @@ void Game::readSettings()
|
||||||
m_cache_enable_fog = g_settings->getBool("enable_fog");
|
m_cache_enable_fog = g_settings->getBool("enable_fog");
|
||||||
m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
|
m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
|
||||||
m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
|
m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
|
||||||
m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.16f, 2.0);
|
m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.15f, 2.0f);
|
||||||
|
m_repeat_dig_time = g_settings->getFloat("repeat_dig_time", 0.15f, 2.0f);
|
||||||
|
|
||||||
m_cache_enable_noclip = g_settings->getBool("noclip");
|
m_cache_enable_noclip = g_settings->getBool("noclip");
|
||||||
m_cache_enable_free_move = g_settings->getBool("free_move");
|
m_cache_enable_free_move = g_settings->getBool("free_move");
|
||||||
|
|
|
@ -44,6 +44,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#define OBJECT_CROSSHAIR_LINE_SIZE 8
|
#define OBJECT_CROSSHAIR_LINE_SIZE 8
|
||||||
#define CROSSHAIR_LINE_SIZE 10
|
#define CROSSHAIR_LINE_SIZE 10
|
||||||
|
|
||||||
|
static void setting_changed_callback(const std::string &name, void *data)
|
||||||
|
{
|
||||||
|
static_cast<Hud*>(data)->readScalingSetting();
|
||||||
|
}
|
||||||
|
|
||||||
Hud::Hud(Client *client, LocalPlayer *player,
|
Hud::Hud(Client *client, LocalPlayer *player,
|
||||||
Inventory *inventory)
|
Inventory *inventory)
|
||||||
{
|
{
|
||||||
|
@ -52,12 +57,8 @@ Hud::Hud(Client *client, LocalPlayer *player,
|
||||||
this->player = player;
|
this->player = player;
|
||||||
this->inventory = inventory;
|
this->inventory = inventory;
|
||||||
|
|
||||||
m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
|
readScalingSetting();
|
||||||
m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
|
g_settings->registerChangedCallback("hud_scaling", setting_changed_callback, this);
|
||||||
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
|
|
||||||
RenderingEngine::getDisplayDensity() + 0.5f);
|
|
||||||
m_hotbar_imagesize *= m_hud_scaling;
|
|
||||||
m_padding = m_hotbar_imagesize / 12;
|
|
||||||
|
|
||||||
for (auto &hbar_color : hbar_colors)
|
for (auto &hbar_color : hbar_colors)
|
||||||
hbar_color = video::SColor(255, 255, 255, 255);
|
hbar_color = video::SColor(255, 255, 255, 255);
|
||||||
|
@ -138,8 +139,20 @@ Hud::Hud(Client *client, LocalPlayer *player,
|
||||||
m_rotation_mesh_buffer.setHardwareMappingHint(scene::EHM_STATIC);
|
m_rotation_mesh_buffer.setHardwareMappingHint(scene::EHM_STATIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Hud::readScalingSetting()
|
||||||
|
{
|
||||||
|
m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
|
||||||
|
m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
|
||||||
|
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
|
||||||
|
RenderingEngine::getDisplayDensity() + 0.5f);
|
||||||
|
m_hotbar_imagesize *= m_hud_scaling;
|
||||||
|
m_padding = m_hotbar_imagesize / 12;
|
||||||
|
}
|
||||||
|
|
||||||
Hud::~Hud()
|
Hud::~Hud()
|
||||||
{
|
{
|
||||||
|
g_settings->deregisterChangedCallback("hud_scaling", setting_changed_callback, this);
|
||||||
|
|
||||||
if (m_selection_mesh)
|
if (m_selection_mesh)
|
||||||
m_selection_mesh->drop();
|
m_selection_mesh->drop();
|
||||||
}
|
}
|
||||||
|
@ -786,9 +799,9 @@ void Hud::drawCrosshair()
|
||||||
{
|
{
|
||||||
auto draw_image_crosshair = [this] (video::ITexture *tex) {
|
auto draw_image_crosshair = [this] (video::ITexture *tex) {
|
||||||
core::dimension2di orig_size(tex->getOriginalSize());
|
core::dimension2di orig_size(tex->getOriginalSize());
|
||||||
core::dimension2di scaled_size(
|
// Integer scaling to avoid artifacts, floor instead of round since too
|
||||||
core::round32(orig_size.Width * m_scale_factor),
|
// small looks better than too large in this case.
|
||||||
core::round32(orig_size.Height * m_scale_factor));
|
core::dimension2di scaled_size = orig_size * std::max(std::floor(m_scale_factor), 1.0f);
|
||||||
|
|
||||||
core::rect<s32> src_rect(orig_size);
|
core::rect<s32> src_rect(orig_size);
|
||||||
core::position2d pos(m_displaycenter.X - scaled_size.Width / 2,
|
core::position2d pos(m_displaycenter.X - scaled_size.Width / 2,
|
||||||
|
|
|
@ -57,6 +57,7 @@ public:
|
||||||
|
|
||||||
Hud(Client *client, LocalPlayer *player,
|
Hud(Client *client, LocalPlayer *player,
|
||||||
Inventory *inventory);
|
Inventory *inventory);
|
||||||
|
void readScalingSetting();
|
||||||
~Hud();
|
~Hud();
|
||||||
|
|
||||||
enum BlockBoundsMode toggleBlockBounds();
|
enum BlockBoundsMode toggleBlockBounds();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IImage.h>
|
||||||
|
#include <string>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
// This file is only used for internal generation of images.
|
||||||
|
// Use texturesource.h to handle textures.
|
||||||
|
|
||||||
|
// A cache used for storing source images.
|
||||||
|
// (A "source image" is an unmodified image directly taken from the filesystem.)
|
||||||
|
// Does not contain modified images.
|
||||||
|
class SourceImageCache {
|
||||||
|
public:
|
||||||
|
~SourceImageCache();
|
||||||
|
|
||||||
|
void insert(const std::string &name, video::IImage *img, bool prefer_local);
|
||||||
|
|
||||||
|
video::IImage* get(const std::string &name);
|
||||||
|
|
||||||
|
// Primarily fetches from cache, secondarily tries to read from filesystem.
|
||||||
|
video::IImage *getOrLoad(const std::string &name);
|
||||||
|
private:
|
||||||
|
std::map<std::string, video::IImage*> m_images;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates images using texture modifiers, and caches source images.
|
||||||
|
struct ImageSource {
|
||||||
|
/*! Generates an image from a full string like
|
||||||
|
* "stone.png^mineral_coal.png^[crack:1:0".
|
||||||
|
* The returned Image should be dropped.
|
||||||
|
* source_image_names is important to determine when to flush the image from a cache (dynamic media)
|
||||||
|
*/
|
||||||
|
video::IImage* generateImage(std::string_view name, std::set<std::string> &source_image_names);
|
||||||
|
|
||||||
|
// Insert a source image into the cache without touching the filesystem.
|
||||||
|
void insertSourceImage(const std::string &name, video::IImage *img, bool prefer_local);
|
||||||
|
|
||||||
|
// TODO should probably be moved elsewhere
|
||||||
|
static video::SColor getImageAverageColor(const video::IImage &image);
|
||||||
|
|
||||||
|
ImageSource() :
|
||||||
|
m_setting_mipmap{g_settings->getBool("mip_map")},
|
||||||
|
m_setting_trilinear_filter{g_settings->getBool("trilinear_filter")},
|
||||||
|
m_setting_bilinear_filter{g_settings->getBool("bilinear_filter")},
|
||||||
|
m_setting_anisotropic_filter{g_settings->getBool("anisotropic_filter")}
|
||||||
|
{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Generate image based on a string like "stone.png" or "[crack:1:0".
|
||||||
|
// If baseimg is NULL, it is created. Otherwise stuff is made on it.
|
||||||
|
// source_image_names is important to determine when to flush the image from a cache (dynamic media).
|
||||||
|
bool generateImagePart(std::string_view part_of_name, video::IImage *& baseimg,
|
||||||
|
std::set<std::string> &source_image_names);
|
||||||
|
|
||||||
|
// Cached settings needed for making textures from meshes
|
||||||
|
bool m_setting_mipmap;
|
||||||
|
bool m_setting_trilinear_filter;
|
||||||
|
bool m_setting_bilinear_filter;
|
||||||
|
bool m_setting_anisotropic_filter;
|
||||||
|
|
||||||
|
// Cache of source images
|
||||||
|
SourceImageCache m_sourcecache;
|
||||||
|
};
|
|
@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
#include "particles.h"
|
#include "particles.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <array>
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "collision.h"
|
#include "collision.h"
|
||||||
#include "client/content_cao.h"
|
#include "client/content_cao.h"
|
||||||
|
@ -26,21 +27,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "client/renderingengine.h"
|
#include "client/renderingengine.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "light.h"
|
#include "light.h"
|
||||||
|
#include "localplayer.h"
|
||||||
#include "environment.h"
|
#include "environment.h"
|
||||||
#include "clientmap.h"
|
#include "clientmap.h"
|
||||||
#include "mapnode.h"
|
#include "mapnode.h"
|
||||||
#include "nodedef.h"
|
#include "nodedef.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "profiler.h"
|
||||||
|
|
||||||
|
ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc)
|
||||||
|
{
|
||||||
|
tex = p;
|
||||||
|
// note: getTextureForMesh not needed here because we don't use texture filtering
|
||||||
|
ref = tsrc->getTexture(p.string);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Particle
|
Particle
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Particle::Particle(
|
Particle::Particle(
|
||||||
IGameDef *gamedef,
|
|
||||||
LocalPlayer *player,
|
|
||||||
ClientEnvironment *env,
|
|
||||||
const ParticleParameters &p,
|
const ParticleParameters &p,
|
||||||
const ClientParticleTexRef &texture,
|
const ClientParticleTexRef &texture,
|
||||||
v2f texpos,
|
v2f texpos,
|
||||||
|
@ -49,14 +56,10 @@ Particle::Particle(
|
||||||
ParticleSpawner *parent,
|
ParticleSpawner *parent,
|
||||||
std::unique_ptr<ClientParticleTexture> owned_texture
|
std::unique_ptr<ClientParticleTexture> owned_texture
|
||||||
) :
|
) :
|
||||||
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
|
|
||||||
((Client *)gamedef)->getSceneManager()),
|
|
||||||
|
|
||||||
m_expiration(p.expirationtime),
|
m_expiration(p.expirationtime),
|
||||||
|
|
||||||
m_env(env),
|
m_base_color(color),
|
||||||
m_gamedef(gamedef),
|
|
||||||
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
|
|
||||||
m_texture(texture),
|
m_texture(texture),
|
||||||
m_texpos(texpos),
|
m_texpos(texpos),
|
||||||
m_texsize(texsize),
|
m_texsize(texsize),
|
||||||
|
@ -64,102 +67,30 @@ Particle::Particle(
|
||||||
m_velocity(p.vel),
|
m_velocity(p.vel),
|
||||||
m_acceleration(p.acc),
|
m_acceleration(p.acc),
|
||||||
m_p(p),
|
m_p(p),
|
||||||
m_player(player),
|
|
||||||
|
|
||||||
m_base_color(color),
|
|
||||||
m_color(color),
|
|
||||||
|
|
||||||
m_parent(parent),
|
m_parent(parent),
|
||||||
m_owned_texture(std::move(owned_texture))
|
m_owned_texture(std::move(owned_texture))
|
||||||
{
|
{
|
||||||
// Set material
|
}
|
||||||
{
|
|
||||||
// translate blend modes to GL blend functions
|
|
||||||
video::E_BLEND_FACTOR bfsrc, bfdst;
|
|
||||||
video::E_BLEND_OPERATION blendop;
|
|
||||||
const auto blendmode = texture.tex != nullptr
|
|
||||||
? texture.tex->blendmode
|
|
||||||
: ParticleParamTypes::BlendMode::alpha;
|
|
||||||
|
|
||||||
switch (blendmode) {
|
Particle::~Particle()
|
||||||
case ParticleParamTypes::BlendMode::add:
|
{
|
||||||
bfsrc = video::EBF_SRC_ALPHA;
|
if (m_buffer)
|
||||||
bfdst = video::EBF_DST_ALPHA;
|
m_buffer->release(m_index);
|
||||||
blendop = video::EBO_ADD;
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case ParticleParamTypes::BlendMode::sub:
|
bool Particle::attachToBuffer(ParticleBuffer *buffer)
|
||||||
bfsrc = video::EBF_SRC_ALPHA;
|
{
|
||||||
bfdst = video::EBF_DST_ALPHA;
|
auto index_opt = buffer->allocate();
|
||||||
blendop = video::EBO_REVSUBTRACT;
|
if (index_opt.has_value()) {
|
||||||
break;
|
m_index = index_opt.value();
|
||||||
|
m_buffer = buffer;
|
||||||
case ParticleParamTypes::BlendMode::screen:
|
return true;
|
||||||
bfsrc = video::EBF_ONE;
|
|
||||||
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
|
||||||
blendop = video::EBO_ADD;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // includes ParticleParamTypes::BlendMode::alpha
|
|
||||||
bfsrc = video::EBF_SRC_ALPHA;
|
|
||||||
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
|
||||||
blendop = video::EBO_ADD;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Texture
|
|
||||||
m_material.Lighting = false;
|
|
||||||
m_material.BackfaceCulling = false;
|
|
||||||
m_material.FogEnable = true;
|
|
||||||
m_material.forEachTexture([] (auto &tex) {
|
|
||||||
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
|
||||||
tex.MagFilter = video::ETMAGF_NEAREST;
|
|
||||||
});
|
|
||||||
|
|
||||||
// correctly render layered transparent particles -- see #10398
|
|
||||||
m_material.ZWriteEnable = video::EZW_AUTO;
|
|
||||||
|
|
||||||
// enable alpha blending and set blend mode
|
|
||||||
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
|
||||||
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
|
|
||||||
bfsrc, bfdst,
|
|
||||||
video::EMFN_MODULATE_1X,
|
|
||||||
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
|
||||||
m_material.BlendOperation = blendop;
|
|
||||||
m_material.setTexture(0, m_texture.ref);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
// Irrlicht stuff
|
|
||||||
this->setAutomaticCulling(scene::EAC_OFF);
|
|
||||||
|
|
||||||
// Init lighting
|
|
||||||
updateLight();
|
|
||||||
|
|
||||||
// Init model
|
|
||||||
updateVertices();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Particle::OnRegisterSceneNode()
|
void Particle::step(float dtime, ClientEnvironment *env)
|
||||||
{
|
|
||||||
if (IsVisible)
|
|
||||||
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
|
||||||
|
|
||||||
ISceneNode::OnRegisterSceneNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Particle::render()
|
|
||||||
{
|
|
||||||
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
|
||||||
driver->setMaterial(m_material);
|
|
||||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
|
||||||
|
|
||||||
u16 indices[] = {0,1,2, 2,3,0};
|
|
||||||
driver->drawVertexPrimitiveList(m_vertices, 4,
|
|
||||||
indices, 2, video::EVT_STANDARD,
|
|
||||||
scene::EPT_TRIANGLES, video::EIT_16BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Particle::step(float dtime)
|
|
||||||
{
|
{
|
||||||
m_time += dtime;
|
m_time += dtime;
|
||||||
|
|
||||||
|
@ -169,10 +100,10 @@ void Particle::step(float dtime)
|
||||||
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
|
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
|
||||||
|
|
||||||
if (m_p.collisiondetection) {
|
if (m_p.collisiondetection) {
|
||||||
aabb3f box = m_collisionbox;
|
aabb3f box(v3f(-m_p.size / 2.0f), v3f(m_p.size / 2.0f));
|
||||||
v3f p_pos = m_pos * BS;
|
v3f p_pos = m_pos * BS;
|
||||||
v3f p_velocity = m_velocity * BS;
|
v3f p_velocity = m_velocity * BS;
|
||||||
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
|
collisionMoveResult r = collisionMoveSimple(env, env->getGameDef(), BS * 0.5f,
|
||||||
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
|
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
|
||||||
m_p.object_collision);
|
m_p.object_collision);
|
||||||
|
|
||||||
|
@ -215,7 +146,7 @@ void Particle::step(float dtime)
|
||||||
m_animation_time += dtime;
|
m_animation_time += dtime;
|
||||||
int frame_length_i = 0;
|
int frame_length_i = 0;
|
||||||
m_p.animation.determineParams(
|
m_p.animation.determineParams(
|
||||||
m_material.getTexture(0)->getSize(),
|
m_texture.ref->getSize(),
|
||||||
NULL, &frame_length_i, NULL);
|
NULL, &frame_length_i, NULL);
|
||||||
float frame_length = frame_length_i / 1000.0;
|
float frame_length = frame_length_i / 1000.0;
|
||||||
while (m_animation_time > frame_length) {
|
while (m_animation_time > frame_length) {
|
||||||
|
@ -225,23 +156,19 @@ void Particle::step(float dtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// animate particle alpha in accordance with settings
|
// animate particle alpha in accordance with settings
|
||||||
|
float alpha = 1.f;
|
||||||
if (m_texture.tex != nullptr)
|
if (m_texture.tex != nullptr)
|
||||||
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
||||||
else
|
|
||||||
m_alpha = 1.f;
|
|
||||||
|
|
||||||
// Update lighting
|
// Update lighting
|
||||||
updateLight();
|
auto col = updateLight(env);
|
||||||
|
col.setAlpha(255 * alpha);
|
||||||
|
|
||||||
// Update model
|
// Update model
|
||||||
updateVertices();
|
updateVertices(env, col);
|
||||||
|
|
||||||
// Update position -- see #10398
|
|
||||||
v3s16 camera_offset = m_env->getCameraOffset();
|
|
||||||
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Particle::updateLight()
|
video::SColor Particle::updateLight(ClientEnvironment *env)
|
||||||
{
|
{
|
||||||
u8 light = 0;
|
u8 light = 0;
|
||||||
bool pos_ok;
|
bool pos_ok;
|
||||||
|
@ -251,32 +178,37 @@ void Particle::updateLight()
|
||||||
floor(m_pos.Y+0.5),
|
floor(m_pos.Y+0.5),
|
||||||
floor(m_pos.Z+0.5)
|
floor(m_pos.Z+0.5)
|
||||||
);
|
);
|
||||||
MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
|
MapNode n = env->getClientMap().getNode(p, &pos_ok);
|
||||||
if (pos_ok)
|
if (pos_ok)
|
||||||
light = n.getLightBlend(m_env->getDayNightRatio(),
|
light = n.getLightBlend(env->getDayNightRatio(),
|
||||||
m_gamedef->ndef()->getLightingFlags(n));
|
env->getGameDef()->ndef()->getLightingFlags(n));
|
||||||
else
|
else
|
||||||
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
|
light = blend_light(env->getDayNightRatio(), LIGHT_SUN, 0);
|
||||||
|
|
||||||
u8 m_light = decode_light(light + m_p.glow);
|
u8 m_light = decode_light(light + m_p.glow);
|
||||||
m_color.set(m_alpha*255,
|
return video::SColor(255,
|
||||||
m_light * m_base_color.getRed() / 255,
|
m_light * m_base_color.getRed() / 255,
|
||||||
m_light * m_base_color.getGreen() / 255,
|
m_light * m_base_color.getGreen() / 255,
|
||||||
m_light * m_base_color.getBlue() / 255);
|
m_light * m_base_color.getBlue() / 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Particle::updateVertices()
|
void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
|
||||||
{
|
{
|
||||||
f32 tx0, tx1, ty0, ty1;
|
f32 tx0, tx1, ty0, ty1;
|
||||||
v2f scale;
|
v2f scale;
|
||||||
|
|
||||||
|
if (!m_buffer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
|
||||||
|
|
||||||
if (m_texture.tex != nullptr)
|
if (m_texture.tex != nullptr)
|
||||||
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
|
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
|
||||||
else
|
else
|
||||||
scale = v2f(1.f, 1.f);
|
scale = v2f(1.f, 1.f);
|
||||||
|
|
||||||
if (m_p.animation.type != TAT_NONE) {
|
if (m_p.animation.type != TAT_NONE) {
|
||||||
const v2u32 texsize = m_material.getTexture(0)->getSize();
|
const v2u32 texsize = m_texture.ref->getSize();
|
||||||
v2f texcoord, framesize_f;
|
v2f texcoord, framesize_f;
|
||||||
v2u32 framesize;
|
v2u32 framesize;
|
||||||
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
|
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
|
||||||
|
@ -297,31 +229,30 @@ void Particle::updateVertices()
|
||||||
auto half = m_p.size * .5f,
|
auto half = m_p.size * .5f,
|
||||||
hx = half * scale.X,
|
hx = half * scale.X,
|
||||||
hy = half * scale.Y;
|
hy = half * scale.Y;
|
||||||
m_vertices[0] = video::S3DVertex(-hx, -hy,
|
vertices[0] = video::S3DVertex(-hx, -hy,
|
||||||
0, 0, 0, 0, m_color, tx0, ty1);
|
0, 0, 0, 0, color, tx0, ty1);
|
||||||
m_vertices[1] = video::S3DVertex(hx, -hy,
|
vertices[1] = video::S3DVertex(hx, -hy,
|
||||||
0, 0, 0, 0, m_color, tx1, ty1);
|
0, 0, 0, 0, color, tx1, ty1);
|
||||||
m_vertices[2] = video::S3DVertex(hx, hy,
|
vertices[2] = video::S3DVertex(hx, hy,
|
||||||
0, 0, 0, 0, m_color, tx1, ty0);
|
0, 0, 0, 0, color, tx1, ty0);
|
||||||
m_vertices[3] = video::S3DVertex(-hx, hy,
|
vertices[3] = video::S3DVertex(-hx, hy,
|
||||||
0, 0, 0, 0, m_color, tx0, ty0);
|
0, 0, 0, 0, color, tx0, ty0);
|
||||||
|
|
||||||
|
// Update position -- see #10398
|
||||||
|
auto *player = env->getLocalPlayer();
|
||||||
|
v3s16 camera_offset = env->getCameraOffset();
|
||||||
|
|
||||||
// see #10398
|
for (u16 i = 0; i < 4; i++) {
|
||||||
// v3s16 camera_offset = m_env->getCameraOffset();
|
video::S3DVertex &vertex = vertices[i];
|
||||||
// particle position is now handled by step()
|
|
||||||
m_box.reset(v3f());
|
|
||||||
|
|
||||||
for (video::S3DVertex &vertex : m_vertices) {
|
|
||||||
if (m_p.vertical) {
|
if (m_p.vertical) {
|
||||||
v3f ppos = m_player->getPosition()/BS;
|
v3f ppos = player->getPosition() / BS;
|
||||||
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
|
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
|
||||||
core::DEGTORAD + 90);
|
core::DEGTORAD + 90);
|
||||||
} else {
|
} else {
|
||||||
vertex.Pos.rotateYZBy(m_player->getPitch());
|
vertex.Pos.rotateYZBy(player->getPitch());
|
||||||
vertex.Pos.rotateXZBy(m_player->getYaw());
|
vertex.Pos.rotateXZBy(player->getYaw());
|
||||||
}
|
}
|
||||||
m_box.addInternalPoint(vertex.Pos);
|
vertex.Pos += m_pos * BS - intToFloat(camera_offset, BS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +261,6 @@ void Particle::updateVertices()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ParticleSpawner::ParticleSpawner(
|
ParticleSpawner::ParticleSpawner(
|
||||||
IGameDef *gamedef,
|
|
||||||
LocalPlayer *player,
|
LocalPlayer *player,
|
||||||
const ParticleSpawnerParameters ¶ms,
|
const ParticleSpawnerParameters ¶ms,
|
||||||
u16 attached_id,
|
u16 attached_id,
|
||||||
|
@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner(
|
||||||
m_active(0),
|
m_active(0),
|
||||||
m_particlemanager(p_manager),
|
m_particlemanager(p_manager),
|
||||||
m_time(0.0f),
|
m_time(0.0f),
|
||||||
m_gamedef(gamedef),
|
|
||||||
m_player(player),
|
m_player(player),
|
||||||
p(params),
|
p(params),
|
||||||
m_texpool(std::move(texpool)),
|
m_texpool(std::move(texpool)),
|
||||||
|
@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
|
||||||
|
|
||||||
++m_active;
|
++m_active;
|
||||||
m_particlemanager->addParticle(std::make_unique<Particle>(
|
m_particlemanager->addParticle(std::make_unique<Particle>(
|
||||||
m_gamedef,
|
|
||||||
m_player,
|
|
||||||
env,
|
|
||||||
pp,
|
pp,
|
||||||
texture,
|
texture,
|
||||||
texpos,
|
texpos,
|
||||||
|
@ -624,6 +550,109 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ParticleBuffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
ParticleBuffer::ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material)
|
||||||
|
: scene::ISceneNode(
|
||||||
|
env->getGameDef()->getSceneManager()->getRootSceneNode(),
|
||||||
|
env->getGameDef()->getSceneManager()),
|
||||||
|
m_mesh_buffer(make_irr<scene::SMeshBuffer>())
|
||||||
|
{
|
||||||
|
m_mesh_buffer->getMaterial() = material;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr u16 quad_indices[] = { 0, 1, 2, 2, 3, 0 };
|
||||||
|
|
||||||
|
std::optional<u16> ParticleBuffer::allocate()
|
||||||
|
{
|
||||||
|
u16 index;
|
||||||
|
|
||||||
|
m_usage_timer = 0;
|
||||||
|
|
||||||
|
if (!m_free_list.empty()) {
|
||||||
|
index = m_free_list.back();
|
||||||
|
m_free_list.pop_back();
|
||||||
|
auto *vertices = static_cast<video::S3DVertex*>(m_mesh_buffer->getVertices());
|
||||||
|
u16 *indices = m_mesh_buffer->getIndices();
|
||||||
|
// reset vertices, because it is only written in Particle::step()
|
||||||
|
for (u16 i = 0; i < 4; i++)
|
||||||
|
vertices[4 * index + i] = video::S3DVertex();
|
||||||
|
for (u16 i = 0; i < 6; i++)
|
||||||
|
indices[6 * index + i] = 4 * index + quad_indices[i];
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_count >= MAX_PARTICLES_PER_BUFFER)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
// append new vertices
|
||||||
|
// note: Our buffer never gets smaller, but ParticleManager will delete
|
||||||
|
// us after a while.
|
||||||
|
std::array<video::S3DVertex, 4> vertices {};
|
||||||
|
m_mesh_buffer->append(&vertices.front(), 4, quad_indices, 6);
|
||||||
|
index = m_count++;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParticleBuffer::release(u16 index)
|
||||||
|
{
|
||||||
|
assert(index < m_count);
|
||||||
|
u16 *indices = m_mesh_buffer->getIndices();
|
||||||
|
for (u16 i = 0; i < 6; i++)
|
||||||
|
indices[6 * index + i] = 0;
|
||||||
|
m_free_list.push_back(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
video::S3DVertex *ParticleBuffer::getVertices(u16 index)
|
||||||
|
{
|
||||||
|
if (index >= m_count)
|
||||||
|
return nullptr;
|
||||||
|
m_bounding_box_dirty = true;
|
||||||
|
return &(static_cast<video::S3DVertex *>(m_mesh_buffer->getVertices())[4 * index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParticleBuffer::OnRegisterSceneNode()
|
||||||
|
{
|
||||||
|
if (IsVisible)
|
||||||
|
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
||||||
|
scene::ISceneNode::OnRegisterSceneNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::aabbox3df &ParticleBuffer::getBoundingBox() const
|
||||||
|
{
|
||||||
|
if (!m_bounding_box_dirty)
|
||||||
|
return m_mesh_buffer->BoundingBox;
|
||||||
|
|
||||||
|
core::aabbox3df box;
|
||||||
|
for (u16 i = 0; i < m_count; i++) {
|
||||||
|
// check if this index is used
|
||||||
|
static_assert(quad_indices[1] != 0);
|
||||||
|
if (m_mesh_buffer->getIndices()[6 * i + 1] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (u16 j = 0; j < 4; j++)
|
||||||
|
box.addInternalPoint(m_mesh_buffer->getPosition(i * 4 + j));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mesh_buffer->BoundingBox = box;
|
||||||
|
m_bounding_box_dirty = false;
|
||||||
|
return m_mesh_buffer->BoundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParticleBuffer::render()
|
||||||
|
{
|
||||||
|
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
||||||
|
|
||||||
|
if (isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
driver->setTransform(video::ETS_WORLD, core::matrix4());
|
||||||
|
driver->setMaterial(m_mesh_buffer->getMaterial());
|
||||||
|
driver->drawMeshBuffer(m_mesh_buffer.get());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ParticleManager
|
ParticleManager
|
||||||
*/
|
*/
|
||||||
|
@ -639,8 +668,9 @@ ParticleManager::~ParticleManager()
|
||||||
|
|
||||||
void ParticleManager::step(float dtime)
|
void ParticleManager::step(float dtime)
|
||||||
{
|
{
|
||||||
stepParticles (dtime);
|
stepParticles(dtime);
|
||||||
stepSpawners (dtime);
|
stepSpawners(dtime);
|
||||||
|
stepBuffers(dtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleManager::stepSpawners(float dtime)
|
void ParticleManager::stepSpawners(float dtime)
|
||||||
|
@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime)
|
||||||
assert(parent->hasActive());
|
assert(parent->hasActive());
|
||||||
parent->decrActive();
|
parent->decrActive();
|
||||||
}
|
}
|
||||||
// remove scene node
|
|
||||||
p.remove();
|
|
||||||
// delete
|
// delete
|
||||||
m_particles[i] = std::move(m_particles.back());
|
m_particles[i] = std::move(m_particles.back());
|
||||||
m_particles.pop_back();
|
m_particles.pop_back();
|
||||||
} else {
|
} else {
|
||||||
p.step(dtime);
|
p.step(dtime, m_env);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ParticleManager::stepBuffers(float dtime)
|
||||||
|
{
|
||||||
|
constexpr float INTERVAL = 0.5f;
|
||||||
|
if (!m_buffer_gc.step(dtime, INTERVAL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
MutexAutoLock lock(m_particle_list_lock);
|
||||||
|
|
||||||
|
// remove buffers that have been unused for 5 seconds
|
||||||
|
size_t alloc = 0;
|
||||||
|
for (size_t i = 0; i < m_particle_buffers.size(); ) {
|
||||||
|
auto &buf = m_particle_buffers[i];
|
||||||
|
buf->m_usage_timer += INTERVAL;
|
||||||
|
if (buf->isEmpty() && buf->m_usage_timer > 5.0f) {
|
||||||
|
// delete and swap with last
|
||||||
|
buf->remove();
|
||||||
|
buf = std::move(m_particle_buffers.back());
|
||||||
|
m_particle_buffers.pop_back();
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
alloc += buf->m_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_profiler->avg("ParticleManager: particle buffer count [#]", m_particle_buffers.size());
|
||||||
|
if (!m_particle_buffers.empty())
|
||||||
|
g_profiler->avg("ParticleManager: buffer allocated size [#]", alloc);
|
||||||
|
}
|
||||||
|
|
||||||
void ParticleManager::clearAll()
|
void ParticleManager::clearAll()
|
||||||
{
|
{
|
||||||
MutexAutoLock lock(m_spawner_list_lock);
|
MutexAutoLock lock(m_spawner_list_lock);
|
||||||
MutexAutoLock lock2(m_particle_list_lock);
|
MutexAutoLock lock2(m_particle_list_lock);
|
||||||
|
|
||||||
// clear particle spawners
|
|
||||||
m_particle_spawners.clear();
|
m_particle_spawners.clear();
|
||||||
m_dying_particle_spawners.clear();
|
m_dying_particle_spawners.clear();
|
||||||
|
|
||||||
// clear particles
|
|
||||||
for (std::unique_ptr<Particle> &p : m_particles) {
|
|
||||||
// remove scene node
|
|
||||||
p->remove();
|
|
||||||
// delete
|
|
||||||
p.reset();
|
|
||||||
}
|
|
||||||
m_particles.clear();
|
m_particles.clear();
|
||||||
|
|
||||||
|
// have to remove from scene first because it keeps a reference
|
||||||
|
for (auto &it : m_particle_buffers)
|
||||||
|
it->remove();
|
||||||
|
m_particle_buffers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||||
|
@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||||
|
|
||||||
addParticleSpawner(event->add_particlespawner.id,
|
addParticleSpawner(event->add_particlespawner.id,
|
||||||
std::make_unique<ParticleSpawner>(
|
std::make_unique<ParticleSpawner>(
|
||||||
client,
|
|
||||||
player,
|
player,
|
||||||
p,
|
p,
|
||||||
event->add_particlespawner.attached_id,
|
event->add_particlespawner.attached_id,
|
||||||
|
@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||||
p.size = oldsize;
|
p.size = oldsize;
|
||||||
|
|
||||||
if (texture.ref) {
|
if (texture.ref) {
|
||||||
addParticle(std::make_unique<Particle>(client, player, m_env,
|
addParticle(std::make_unique<Particle>(
|
||||||
p, texture, texpos, texsize, color, nullptr,
|
p, texture, texpos, texsize, color, nullptr,
|
||||||
std::move(texstore)));
|
std::move(texstore)));
|
||||||
}
|
}
|
||||||
|
@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
|
||||||
);
|
);
|
||||||
|
|
||||||
addParticle(std::make_unique<Particle>(
|
addParticle(std::make_unique<Particle>(
|
||||||
gamedef,
|
|
||||||
player,
|
|
||||||
m_env,
|
|
||||||
p,
|
p,
|
||||||
ClientParticleTexRef(ref),
|
ClientParticleTexRef(ref),
|
||||||
texpos,
|
texpos,
|
||||||
|
@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate)
|
||||||
m_particles.reserve(m_particles.size() + max_estimate);
|
m_particles.reserve(m_particles.size() + max_estimate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture)
|
||||||
|
{
|
||||||
|
// translate blend modes to GL blend functions
|
||||||
|
video::E_BLEND_FACTOR bfsrc, bfdst;
|
||||||
|
video::E_BLEND_OPERATION blendop;
|
||||||
|
const auto blendmode = texture.tex ? texture.tex->blendmode :
|
||||||
|
ParticleParamTypes::BlendMode::alpha;
|
||||||
|
|
||||||
|
switch (blendmode) {
|
||||||
|
case ParticleParamTypes::BlendMode::add:
|
||||||
|
bfsrc = video::EBF_SRC_ALPHA;
|
||||||
|
bfdst = video::EBF_DST_ALPHA;
|
||||||
|
blendop = video::EBO_ADD;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParticleParamTypes::BlendMode::sub:
|
||||||
|
bfsrc = video::EBF_SRC_ALPHA;
|
||||||
|
bfdst = video::EBF_DST_ALPHA;
|
||||||
|
blendop = video::EBO_REVSUBTRACT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParticleParamTypes::BlendMode::screen:
|
||||||
|
bfsrc = video::EBF_ONE;
|
||||||
|
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
||||||
|
blendop = video::EBO_ADD;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // includes ParticleParamTypes::BlendMode::alpha
|
||||||
|
bfsrc = video::EBF_SRC_ALPHA;
|
||||||
|
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
||||||
|
blendop = video::EBO_ADD;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
video::SMaterial material;
|
||||||
|
|
||||||
|
// Texture
|
||||||
|
material.Lighting = false;
|
||||||
|
material.BackfaceCulling = false;
|
||||||
|
material.FogEnable = true;
|
||||||
|
material.forEachTexture([] (auto &tex) {
|
||||||
|
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
||||||
|
tex.MagFilter = video::ETMAGF_NEAREST;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We don't have working transparency sorting. Disable Z-Write for
|
||||||
|
// correct results for clipped-alpha at least.
|
||||||
|
material.ZWriteEnable = video::EZW_OFF;
|
||||||
|
|
||||||
|
// enable alpha blending and set blend mode
|
||||||
|
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
||||||
|
material.MaterialTypeParam = video::pack_textureBlendFunc(
|
||||||
|
bfsrc, bfdst,
|
||||||
|
video::EMFN_MODULATE_1X,
|
||||||
|
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
||||||
|
material.BlendOperation = blendop;
|
||||||
|
assert(texture.ref);
|
||||||
|
material.setTexture(0, texture.ref);
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
||||||
{
|
{
|
||||||
MutexAutoLock lock(m_particle_list_lock);
|
MutexAutoLock lock(m_particle_list_lock);
|
||||||
|
|
||||||
m_particles.push_back(std::move(toadd));
|
auto material = getMaterialForParticle(toadd->getTextureRef());
|
||||||
}
|
|
||||||
|
|
||||||
|
ParticleBuffer *found = nullptr;
|
||||||
|
// simple shortcut when multiple particles of the same type get added
|
||||||
|
if (!m_particles.empty()) {
|
||||||
|
auto &last = m_particles.back();
|
||||||
|
if (last->getBuffer() && last->getBuffer()->getMaterial(0) == material)
|
||||||
|
found = last->getBuffer();
|
||||||
|
}
|
||||||
|
// search fitting buffer
|
||||||
|
if (!found) {
|
||||||
|
for (auto &buffer : m_particle_buffers) {
|
||||||
|
if (buffer->getMaterial(0) == material) {
|
||||||
|
found = buffer.get();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// or create a new one
|
||||||
|
if (!found) {
|
||||||
|
auto tmp = make_irr<ParticleBuffer>(m_env, material);
|
||||||
|
found = tmp.get();
|
||||||
|
m_particle_buffers.push_back(std::move(tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toadd->attachToBuffer(found)) {
|
||||||
|
infostream << "ParticleManager: buffer full, dropping particle" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_particles.push_back(std::move(toadd));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
|
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
#include "irrlichttypes_extrabloated.h"
|
#include "irrlichttypes_extrabloated.h"
|
||||||
#include "localplayer.h"
|
#include "irr_ptr.h"
|
||||||
#include "../particles.h"
|
#include "../particles.h"
|
||||||
|
|
||||||
struct ClientEvent;
|
struct ClientEvent;
|
||||||
|
@ -29,6 +30,10 @@ class ParticleManager;
|
||||||
class ClientEnvironment;
|
class ClientEnvironment;
|
||||||
struct MapNode;
|
struct MapNode;
|
||||||
struct ContentFeatures;
|
struct ContentFeatures;
|
||||||
|
class LocalPlayer;
|
||||||
|
class ITextureSource;
|
||||||
|
class IGameDef;
|
||||||
|
class Client;
|
||||||
|
|
||||||
struct ClientParticleTexture
|
struct ClientParticleTexture
|
||||||
{
|
{
|
||||||
|
@ -38,9 +43,7 @@ struct ClientParticleTexture
|
||||||
video::ITexture *ref = nullptr;
|
video::ITexture *ref = nullptr;
|
||||||
|
|
||||||
ClientParticleTexture() = default;
|
ClientParticleTexture() = default;
|
||||||
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t):
|
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc);
|
||||||
tex(p),
|
|
||||||
ref(t->getTextureForMesh(p.string)) {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientParticleTexRef
|
struct ClientParticleTexRef
|
||||||
|
@ -61,14 +64,12 @@ struct ClientParticleTexRef
|
||||||
};
|
};
|
||||||
|
|
||||||
class ParticleSpawner;
|
class ParticleSpawner;
|
||||||
|
class ParticleBuffer;
|
||||||
|
|
||||||
class Particle : public scene::ISceneNode
|
class Particle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Particle(
|
Particle(
|
||||||
IGameDef *gamedef,
|
|
||||||
LocalPlayer *player,
|
|
||||||
ClientEnvironment *env,
|
|
||||||
const ParticleParameters &p,
|
const ParticleParameters &p,
|
||||||
const ClientParticleTexRef &texture,
|
const ClientParticleTexRef &texture,
|
||||||
v2f texpos,
|
v2f texpos,
|
||||||
|
@ -78,61 +79,46 @@ public:
|
||||||
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
|
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
virtual const aabb3f &getBoundingBox() const
|
~Particle();
|
||||||
{
|
|
||||||
return m_box;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual u32 getMaterialCount() const
|
DISABLE_CLASS_COPY(Particle)
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual video::SMaterial& getMaterial(u32 i)
|
void step(float dtime, ClientEnvironment *env);
|
||||||
{
|
|
||||||
return m_material;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void OnRegisterSceneNode();
|
bool isExpired () const
|
||||||
virtual void render();
|
|
||||||
|
|
||||||
void step(float dtime);
|
|
||||||
|
|
||||||
bool isExpired ()
|
|
||||||
{ return m_expiration < m_time; }
|
{ return m_expiration < m_time; }
|
||||||
|
|
||||||
ParticleSpawner *getParent() { return m_parent; }
|
ParticleSpawner *getParent() const { return m_parent; }
|
||||||
|
|
||||||
|
const ClientParticleTexRef &getTextureRef() const { return m_texture; }
|
||||||
|
|
||||||
|
ParticleBuffer *getBuffer() const { return m_buffer; }
|
||||||
|
bool attachToBuffer(ParticleBuffer *buffer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateLight();
|
video::SColor updateLight(ClientEnvironment *env);
|
||||||
void updateVertices();
|
void updateVertices(ClientEnvironment *env, video::SColor color);
|
||||||
void setVertexAlpha(float a);
|
|
||||||
|
ParticleBuffer *m_buffer = nullptr;
|
||||||
|
u16 m_index; // index in m_buffer
|
||||||
|
|
||||||
video::S3DVertex m_vertices[4];
|
|
||||||
float m_time = 0.0f;
|
float m_time = 0.0f;
|
||||||
float m_expiration;
|
float m_expiration;
|
||||||
|
|
||||||
ClientEnvironment *m_env;
|
// Color without lighting
|
||||||
IGameDef *m_gamedef;
|
video::SColor m_base_color;
|
||||||
aabb3f m_box;
|
|
||||||
aabb3f m_collisionbox;
|
|
||||||
ClientParticleTexRef m_texture;
|
ClientParticleTexRef m_texture;
|
||||||
video::SMaterial m_material;
|
|
||||||
v2f m_texpos;
|
v2f m_texpos;
|
||||||
v2f m_texsize;
|
v2f m_texsize;
|
||||||
v3f m_pos;
|
v3f m_pos;
|
||||||
v3f m_velocity;
|
v3f m_velocity;
|
||||||
v3f m_acceleration;
|
v3f m_acceleration;
|
||||||
const ParticleParameters m_p;
|
|
||||||
LocalPlayer *m_player;
|
|
||||||
|
|
||||||
//! Color without lighting
|
const ParticleParameters m_p;
|
||||||
video::SColor m_base_color;
|
|
||||||
//! Final rendered color
|
|
||||||
video::SColor m_color;
|
|
||||||
float m_animation_time = 0.0f;
|
float m_animation_time = 0.0f;
|
||||||
int m_animation_frame = 0;
|
int m_animation_frame = 0;
|
||||||
float m_alpha = 0.0f;
|
|
||||||
|
|
||||||
ParticleSpawner *m_parent = nullptr;
|
ParticleSpawner *m_parent = nullptr;
|
||||||
// Used if not spawned from a particlespawner
|
// Used if not spawned from a particlespawner
|
||||||
|
@ -142,8 +128,7 @@ private:
|
||||||
class ParticleSpawner
|
class ParticleSpawner
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ParticleSpawner(IGameDef *gamedef,
|
ParticleSpawner(LocalPlayer *player,
|
||||||
LocalPlayer *player,
|
|
||||||
const ParticleSpawnerParameters ¶ms,
|
const ParticleSpawnerParameters ¶ms,
|
||||||
u16 attached_id,
|
u16 attached_id,
|
||||||
std::vector<ClientParticleTexture> &&texpool,
|
std::vector<ClientParticleTexture> &&texpool,
|
||||||
|
@ -164,7 +149,6 @@ private:
|
||||||
size_t m_active;
|
size_t m_active;
|
||||||
ParticleManager *m_particlemanager;
|
ParticleManager *m_particlemanager;
|
||||||
float m_time;
|
float m_time;
|
||||||
IGameDef *m_gamedef;
|
|
||||||
LocalPlayer *m_player;
|
LocalPlayer *m_player;
|
||||||
ParticleSpawnerParameters p;
|
ParticleSpawnerParameters p;
|
||||||
std::vector<ClientParticleTexture> m_texpool;
|
std::vector<ClientParticleTexture> m_texpool;
|
||||||
|
@ -172,12 +156,61 @@ private:
|
||||||
u16 m_attached_id;
|
u16 m_attached_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ParticleBuffer : public scene::ISceneNode
|
||||||
|
{
|
||||||
|
friend class ParticleManager;
|
||||||
|
public:
|
||||||
|
ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material);
|
||||||
|
|
||||||
|
// for pointer stability
|
||||||
|
DISABLE_CLASS_COPY(ParticleBuffer)
|
||||||
|
|
||||||
|
/// Reserves one more slot for a particle (4 vertices, 6 indices)
|
||||||
|
/// @return particle index within buffer
|
||||||
|
std::optional<u16> allocate();
|
||||||
|
/// Frees the particle at `index`
|
||||||
|
void release(u16 index);
|
||||||
|
|
||||||
|
/// @return video::S3DVertex[4]
|
||||||
|
video::S3DVertex *getVertices(u16 index);
|
||||||
|
|
||||||
|
inline bool isEmpty() const {
|
||||||
|
return m_free_list.size() == m_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual video::SMaterial &getMaterial(u32 num) override {
|
||||||
|
return m_mesh_buffer->getMaterial();
|
||||||
|
}
|
||||||
|
virtual u32 getMaterialCount() const override {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const core::aabbox3df &getBoundingBox() const override;
|
||||||
|
|
||||||
|
virtual void render() override;
|
||||||
|
|
||||||
|
virtual void OnRegisterSceneNode() override;
|
||||||
|
|
||||||
|
// we have 16-bit indices
|
||||||
|
static constexpr u16 MAX_PARTICLES_PER_BUFFER = 16000;
|
||||||
|
|
||||||
|
private:
|
||||||
|
irr_ptr<scene::SMeshBuffer> m_mesh_buffer;
|
||||||
|
// unused (e.g. expired) particle indices for re-use
|
||||||
|
std::vector<u16> m_free_list;
|
||||||
|
// for automatic deletion when unused for a while. is reset on allocate().
|
||||||
|
float m_usage_timer = 0;
|
||||||
|
// total count of contained particles
|
||||||
|
u16 m_count = 0;
|
||||||
|
mutable bool m_bounding_box_dirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class doing particle as well as their spawners handling
|
* Class doing particle as well as their spawners handling
|
||||||
*/
|
*/
|
||||||
class ParticleManager
|
class ParticleManager
|
||||||
{
|
{
|
||||||
friend class ParticleSpawner;
|
friend class ParticleSpawner;
|
||||||
public:
|
public:
|
||||||
ParticleManager(ClientEnvironment* env);
|
ParticleManager(ClientEnvironment* env);
|
||||||
DISABLE_CLASS_COPY(ParticleManager)
|
DISABLE_CLASS_COPY(ParticleManager)
|
||||||
|
@ -213,7 +246,9 @@ protected:
|
||||||
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
|
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
|
||||||
v2f &texsize, video::SColor *color, u8 tilenum = 0);
|
v2f &texsize, video::SColor *color, u8 tilenum = 0);
|
||||||
|
|
||||||
void addParticle(std::unique_ptr<Particle> toadd);
|
static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture);
|
||||||
|
|
||||||
|
bool addParticle(std::unique_ptr<Particle> toadd);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
|
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
|
||||||
|
@ -221,17 +256,23 @@ private:
|
||||||
|
|
||||||
void stepParticles(float dtime);
|
void stepParticles(float dtime);
|
||||||
void stepSpawners(float dtime);
|
void stepSpawners(float dtime);
|
||||||
|
void stepBuffers(float dtime);
|
||||||
|
|
||||||
void clearAll();
|
void clearAll();
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Particle>> m_particles;
|
std::vector<std::unique_ptr<Particle>> m_particles;
|
||||||
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
|
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
|
||||||
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
|
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
|
||||||
// Start the particle spawner ids generated from here after u32_max. lower values are
|
std::vector<irr_ptr<ParticleBuffer>> m_particle_buffers;
|
||||||
// for server sent spawners.
|
|
||||||
u64 m_next_particle_spawner_id = U32_MAX + 1;
|
// Start the particle spawner ids generated from here after u32_max.
|
||||||
|
// lower values are for server sent spawners.
|
||||||
|
u64 m_next_particle_spawner_id = static_cast<u64>(U32_MAX) + 1;
|
||||||
|
|
||||||
ClientEnvironment *m_env;
|
ClientEnvironment *m_env;
|
||||||
|
|
||||||
|
IntervalLimiter m_buffer_gc;
|
||||||
|
|
||||||
std::mutex m_particle_list_lock;
|
std::mutex m_particle_list_lock;
|
||||||
std::mutex m_spawner_list_lock;
|
std::mutex m_spawner_list_lock;
|
||||||
};
|
};
|
||||||
|
|
|
@ -164,13 +164,19 @@ static std::optional<video::E_DRIVER_TYPE> chooseVideoDriver()
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline auto getVideoDriverName(video::E_DRIVER_TYPE driver)
|
||||||
|
{
|
||||||
|
return RenderingEngine::getVideoDriverInfo(driver).friendly_name;
|
||||||
|
}
|
||||||
|
|
||||||
static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std::optional<video::E_DRIVER_TYPE> requested_driver)
|
static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std::optional<video::E_DRIVER_TYPE> requested_driver)
|
||||||
{
|
{
|
||||||
if (requested_driver) {
|
if (requested_driver) {
|
||||||
params.DriverType = *requested_driver;
|
params.DriverType = *requested_driver;
|
||||||
|
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
|
||||||
if (auto *device = createDeviceEx(params))
|
if (auto *device = createDeviceEx(params))
|
||||||
return device;
|
return device;
|
||||||
errorstream << "Failed to initialize the " << RenderingEngine::getVideoDriverInfo(*requested_driver).friendly_name << " video driver" << std::endl;
|
errorstream << "Failed to initialize the " << getVideoDriverName(params.DriverType) << " video driver" << std::endl;
|
||||||
}
|
}
|
||||||
sanity_check(requested_driver != video::EDT_NULL);
|
sanity_check(requested_driver != video::EDT_NULL);
|
||||||
|
|
||||||
|
@ -179,6 +185,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std
|
||||||
if (fallback_driver == video::EDT_NULL || fallback_driver == requested_driver)
|
if (fallback_driver == video::EDT_NULL || fallback_driver == requested_driver)
|
||||||
continue;
|
continue;
|
||||||
params.DriverType = fallback_driver;
|
params.DriverType = fallback_driver;
|
||||||
|
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
|
||||||
if (auto *device = createDeviceEx(params))
|
if (auto *device = createDeviceEx(params))
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
@ -222,9 +229,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
|
||||||
params.Stencilbuffer = false;
|
params.Stencilbuffer = false;
|
||||||
params.Vsync = vsync;
|
params.Vsync = vsync;
|
||||||
params.EventReceiver = receiver;
|
params.EventReceiver = receiver;
|
||||||
#ifdef __ANDROID__
|
|
||||||
params.PrivateData = porting::app_global;
|
|
||||||
#endif
|
|
||||||
// there is no standardized path for these on desktop
|
// there is no standardized path for these on desktop
|
||||||
std::string rel_path = std::string("client") + DIR_DELIM
|
std::string rel_path = std::string("client") + DIR_DELIM
|
||||||
+ "shaders" + DIR_DELIM + "Irrlicht";
|
+ "shaders" + DIR_DELIM + "Irrlicht";
|
||||||
|
@ -232,7 +237,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
|
||||||
|
|
||||||
m_device = createDevice(params, driverType);
|
m_device = createDevice(params, driverType);
|
||||||
driver = m_device->getVideoDriver();
|
driver = m_device->getVideoDriver();
|
||||||
infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl;
|
verbosestream << "Using the " << getVideoDriverName(driver->getDriverType()) << " video driver" << std::endl;
|
||||||
|
|
||||||
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
|
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
|
||||||
driver->setMinHardwareBufferVertexCount(4);
|
driver->setMinHardwareBufferVertexCount(4);
|
||||||
|
|
|
@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
#include "al_extensions.h"
|
#include "al_extensions.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "sound_constants.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
@ -77,33 +78,27 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
|
||||||
warn_if_al_error("when creating non-streaming sound");
|
warn_if_al_error("when creating non-streaming sound");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Start with 2 buffers
|
// Start with first buffer
|
||||||
ALuint buf_ids[2];
|
|
||||||
|
|
||||||
// If m_next_sample_pos >= len_samples (happens only if not looped), one
|
// If m_next_sample_pos >= len_samples (happens only if not looped), buf0
|
||||||
// or both of buf_ids will be 0. Queuing 0 is a NOP.
|
// will be 0. Queuing 0 is a NOP.
|
||||||
|
|
||||||
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||||
buf_ids[0] = buf0;
|
|
||||||
m_next_sample_pos = buf0_end;
|
m_next_sample_pos = buf0_end;
|
||||||
|
|
||||||
if (m_looping && m_next_sample_pos == len_samples)
|
alSourceQueueBuffers(m_source_id, 1, &buf0);
|
||||||
m_next_sample_pos = 0;
|
|
||||||
|
|
||||||
auto [buf1, buf1_end, offset_in_buf1] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
|
||||||
buf_ids[1] = buf1;
|
|
||||||
m_next_sample_pos = buf1_end;
|
|
||||||
assert(offset_in_buf1 == 0);
|
|
||||||
|
|
||||||
alSourceQueueBuffers(m_source_id, 2, buf_ids);
|
|
||||||
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
|
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
|
||||||
|
|
||||||
// We can't use AL_LOOPING because more buffers are queued later
|
// We can't use AL_LOOPING because more buffers are queued later.
|
||||||
// looping is therefore done manually
|
// Looping is therefore done manually.
|
||||||
|
|
||||||
|
// Sound is not dead if queue runs empty prematurely
|
||||||
m_stopped_means_dead = false;
|
m_stopped_means_dead = false;
|
||||||
|
|
||||||
warn_if_al_error("when creating streaming sound");
|
warn_if_al_error("when creating streaming sound");
|
||||||
|
|
||||||
|
// Enqueue more buffers
|
||||||
|
stepStream(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial pos, volume, pitch
|
// Set initial pos, volume, pitch
|
||||||
|
@ -129,23 +124,44 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
|
||||||
setPitch(pitch);
|
setPitch(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlayingSound::stepStream()
|
bool PlayingSound::stepStream(bool playback_speed_changed)
|
||||||
{
|
{
|
||||||
if (isDead())
|
if (isDead())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// unqueue finished buffers
|
// Unqueue finished buffers
|
||||||
ALint num_unqueued_bufs = 0;
|
ALint num_processed_bufs = 0;
|
||||||
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_unqueued_bufs);
|
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_processed_bufs);
|
||||||
if (num_unqueued_bufs == 0)
|
if (num_processed_bufs == 0 && !playback_speed_changed)
|
||||||
return true;
|
return true; // Nothing to do
|
||||||
// We always have 2 buffers enqueued at most
|
if (num_processed_bufs > 0) {
|
||||||
SANITY_CHECK(num_unqueued_bufs <= 2);
|
ALint num_to_unqueue = num_processed_bufs;
|
||||||
ALuint unqueued_buffer_ids[2];
|
ALuint unqueued_buffer_ids[8];
|
||||||
alSourceUnqueueBuffers(m_source_id, num_unqueued_bufs, unqueued_buffer_ids);
|
while (num_to_unqueue > 8) {
|
||||||
|
alSourceUnqueueBuffers(m_source_id, 8, unqueued_buffer_ids);
|
||||||
|
num_to_unqueue -= 8;
|
||||||
|
}
|
||||||
|
alSourceUnqueueBuffers(m_source_id, num_to_unqueue, unqueued_buffer_ids);
|
||||||
|
}
|
||||||
|
|
||||||
// Fill up again
|
// Find out how many buffers we want to enqueue
|
||||||
for (ALint i = 0; i < num_unqueued_bufs; ++i) {
|
f32 pitch = 1.0f;
|
||||||
|
alGetSourcef(m_source_id, AL_PITCH, &pitch);
|
||||||
|
ALint num_queued_bufs = 0;
|
||||||
|
alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &num_queued_bufs);
|
||||||
|
// Min. length of untouched buffers
|
||||||
|
const f32 playback_left = MIN_STREAM_BUFFER_LENGTH * std::max(0, num_queued_bufs - 1);
|
||||||
|
// Max. time until next stepStream() call, see also [Streaming of sounds] in
|
||||||
|
// sound_constants.h.
|
||||||
|
// Multiplied by pitch because pitch makes playback faster than real time.
|
||||||
|
// (Does not account for doppler effect, if we had that.)
|
||||||
|
// +0.1 seconds to accommodate hickups.
|
||||||
|
const f32 playback_until_next_check = (2.0f * STREAM_BIGSTEP_TIME + 0.1f) * pitch;
|
||||||
|
const f32 playback_to_fill_up = std::max(0.0f, playback_until_next_check - playback_left);
|
||||||
|
const int num_bufs_to_enqueue = std::ceil(playback_to_fill_up / MIN_STREAM_BUFFER_LENGTH);
|
||||||
|
|
||||||
|
// Fill up
|
||||||
|
for (int i = 0; i < num_bufs_to_enqueue; ++i) {
|
||||||
if (m_next_sample_pos == m_data->m_decode_info.length_samples) {
|
if (m_next_sample_pos == m_data->m_decode_info.length_samples) {
|
||||||
// Reached end
|
// Reached end
|
||||||
if (m_looping) {
|
if (m_looping) {
|
||||||
|
@ -256,4 +272,11 @@ f32 PlayingSound::getGain() noexcept
|
||||||
return gain;
|
return gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayingSound::setPitch(f32 pitch)
|
||||||
|
{
|
||||||
|
alSourcef(m_source_id, AL_PITCH, pitch);
|
||||||
|
if (isStreaming())
|
||||||
|
stepStream(true);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sound
|
} // namespace sound
|
||||||
|
|
|
@ -63,7 +63,7 @@ public:
|
||||||
DISABLE_CLASS_COPY(PlayingSound)
|
DISABLE_CLASS_COPY(PlayingSound)
|
||||||
|
|
||||||
// return false means streaming finished
|
// return false means streaming finished
|
||||||
bool stepStream();
|
bool stepStream(bool playback_speed_changed = false);
|
||||||
|
|
||||||
// retruns true if it wasn't fading already
|
// retruns true if it wasn't fading already
|
||||||
bool fade(f32 step, f32 target_gain) noexcept;
|
bool fade(f32 step, f32 target_gain) noexcept;
|
||||||
|
@ -77,7 +77,7 @@ public:
|
||||||
|
|
||||||
f32 getGain() noexcept;
|
f32 getGain() noexcept;
|
||||||
|
|
||||||
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
|
void setPitch(f32 pitch);
|
||||||
|
|
||||||
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
||||||
|
|
||||||
|
|
|
@ -89,14 +89,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
||||||
* end of the next bigstep. So between two stepStream()-calls lie at most
|
* end of the next bigstep. So between two stepStream()-calls lie at most
|
||||||
* 2 * STREAM_BIGSTEP_TIME seconds.
|
* 2 * STREAM_BIGSTEP_TIME seconds.
|
||||||
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
|
* We ensure that there are always enough untouched full buffers left such that
|
||||||
* is still available after the first stepStream().
|
* we do not run into an empty queue in this time period, see stepStream().
|
||||||
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
|
|
||||||
* not run into an empty queue.
|
|
||||||
*
|
|
||||||
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
|
|
||||||
* other sounds that may have taken long to stepStream(), and sounds being played
|
|
||||||
* faster due to Doppler effect.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -115,8 +109,6 @@ constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
|
||||||
// step duration for the OpenALSoundManager thread, in seconds
|
// step duration for the OpenALSoundManager thread, in seconds
|
||||||
constexpr f32 SOUNDTHREAD_DTIME = 0.016f;
|
constexpr f32 SOUNDTHREAD_DTIME = 0.016f;
|
||||||
|
|
||||||
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
|
|
||||||
"See [Streaming of sounds].");
|
|
||||||
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
||||||
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "nodetimer.h"
|
#include "nodetimer.h"
|
||||||
#include "inventory.h"
|
#include "inventory.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "debug.h"
|
||||||
#include "serialization.h"
|
#include "serialization.h"
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
|
@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include "gettime.h"
|
#include "gettime.h"
|
||||||
|
|
|
@ -310,6 +310,7 @@ void set_default_settings()
|
||||||
settings->setDefault("invert_hotbar_mouse_wheel", "false");
|
settings->setDefault("invert_hotbar_mouse_wheel", "false");
|
||||||
settings->setDefault("mouse_sensitivity", "0.2");
|
settings->setDefault("mouse_sensitivity", "0.2");
|
||||||
settings->setDefault("repeat_place_time", "0.25");
|
settings->setDefault("repeat_place_time", "0.25");
|
||||||
|
settings->setDefault("repeat_dig_time", "0.15");
|
||||||
settings->setDefault("safe_dig_and_place", "false");
|
settings->setDefault("safe_dig_and_place", "false");
|
||||||
settings->setDefault("random_input", "false");
|
settings->setDefault("random_input", "false");
|
||||||
settings->setDefault("aux1_descends", "false");
|
settings->setDefault("aux1_descends", "false");
|
||||||
|
@ -364,11 +365,10 @@ void set_default_settings()
|
||||||
settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default");
|
settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
|
|
||||||
#if ENABLE_UPDATE_CHECKER
|
#if ENABLE_UPDATE_CHECKER
|
||||||
settings->setDefault("update_last_checked", "");
|
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
|
||||||
#else
|
#else
|
||||||
settings->setDefault("update_last_checked", "disabled");
|
settings->setDefault("update_information_url", "");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Server
|
// Server
|
||||||
|
@ -494,11 +494,13 @@ void set_default_settings()
|
||||||
settings->setDefault("keymap_sneak", "KEY_SHIFT");
|
settings->setDefault("keymap_sneak", "KEY_SHIFT");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
settings->setDefault("touchscreen_threshold", "20");
|
|
||||||
settings->setDefault("touchscreen_sensitivity", "0.2");
|
settings->setDefault("touchscreen_sensitivity", "0.2");
|
||||||
|
settings->setDefault("touchscreen_threshold", "20");
|
||||||
|
settings->setDefault("touch_long_tap_delay", "400");
|
||||||
settings->setDefault("touch_use_crosshair", "false");
|
settings->setDefault("touch_use_crosshair", "false");
|
||||||
settings->setDefault("fixed_virtual_joystick", "false");
|
settings->setDefault("fixed_virtual_joystick", "false");
|
||||||
settings->setDefault("virtual_joystick_triggers_aux1", "false");
|
settings->setDefault("virtual_joystick_triggers_aux1", "false");
|
||||||
|
settings->setDefault("touch_punch_gesture", "short_tap");
|
||||||
#ifdef ENABLE_TOUCH
|
#ifdef ENABLE_TOUCH
|
||||||
settings->setDefault("clickable_chat_weblinks", "false");
|
settings->setDefault("clickable_chat_weblinks", "false");
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -223,6 +223,16 @@ std::string CreateTempFile()
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CreateTempDir()
|
||||||
|
{
|
||||||
|
std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
|
||||||
|
_mktemp_s(&path[0], path.size() + 1); // modifies path
|
||||||
|
// will error if it already exists
|
||||||
|
if (!CreateDirectory(path.c_str(), nullptr))
|
||||||
|
return "";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
bool CopyFileContents(const std::string &source, const std::string &target)
|
bool CopyFileContents(const std::string &source, const std::string &target)
|
||||||
{
|
{
|
||||||
BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr,
|
BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr,
|
||||||
|
@ -446,6 +456,15 @@ std::string CreateTempFile()
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CreateTempDir()
|
||||||
|
{
|
||||||
|
std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
|
||||||
|
auto r = mkdtemp(&path[0]); // modifies path
|
||||||
|
if (!r)
|
||||||
|
return "";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct FileDeleter {
|
struct FileDeleter {
|
||||||
void operator()(FILE *stream) {
|
void operator()(FILE *stream) {
|
||||||
|
|
|
@ -75,13 +75,19 @@ bool RecursiveDelete(const std::string &path);
|
||||||
|
|
||||||
bool DeleteSingleFileOrEmptyDirectory(const std::string &path);
|
bool DeleteSingleFileOrEmptyDirectory(const std::string &path);
|
||||||
|
|
||||||
// Returns path to temp directory, can return "" on error
|
/// Returns path to temp directory.
|
||||||
|
/// You probably don't want to use this directly, see `CreateTempFile` or `CreateTempDir`.
|
||||||
|
/// @return path or "" on error
|
||||||
std::string TempPath();
|
std::string TempPath();
|
||||||
|
|
||||||
// Returns path to securely-created temporary file (will already exist when this function returns)
|
/// Returns path to securely-created temporary file (will already exist when this function returns).
|
||||||
// can return "" on error
|
/// @return path or "" on error
|
||||||
std::string CreateTempFile();
|
std::string CreateTempFile();
|
||||||
|
|
||||||
|
/// Returns path to securely-created temporary directory (will already exist when this function returns).
|
||||||
|
/// @return path or "" on error
|
||||||
|
std::string CreateTempDir();
|
||||||
|
|
||||||
/* Returns a list of subdirectories, including the path itself, but excluding
|
/* Returns a list of subdirectories, including the path itself, but excluding
|
||||||
hidden directories (whose names start with . or _)
|
hidden directories (whose names start with . or _)
|
||||||
*/
|
*/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,280 +1,280 @@
|
||||||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||||
// This file is part of the "Irrlicht Engine".
|
// This file is part of the "Irrlicht Engine".
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IGUIStaticText.h>
|
#include <IGUIStaticText.h>
|
||||||
#include "irrlicht_changes/static_text.h"
|
#include "irrlicht_changes/static_text.h"
|
||||||
#include "IGUIButton.h"
|
#include "IGUIButton.h"
|
||||||
#include "IGUISpriteBank.h"
|
#include "IGUISpriteBank.h"
|
||||||
#include "ITexture.h"
|
#include "ITexture.h"
|
||||||
#include "SColor.h"
|
#include "SColor.h"
|
||||||
#include "guiSkin.h"
|
#include "guiSkin.h"
|
||||||
#include "StyleSpec.h"
|
#include "StyleSpec.h"
|
||||||
|
|
||||||
using namespace irr;
|
using namespace irr;
|
||||||
|
|
||||||
class ISimpleTextureSource;
|
class ISimpleTextureSource;
|
||||||
|
|
||||||
class GUIButton : public gui::IGUIButton
|
class GUIButton : public gui::IGUIButton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
//! constructor
|
//! constructor
|
||||||
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
|
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
|
||||||
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
|
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
|
||||||
bool noclip=false);
|
bool noclip=false);
|
||||||
|
|
||||||
//! destructor
|
//! destructor
|
||||||
virtual ~GUIButton();
|
virtual ~GUIButton();
|
||||||
|
|
||||||
//! called if an event happened.
|
//! called if an event happened.
|
||||||
virtual bool OnEvent(const SEvent& event) override;
|
virtual bool OnEvent(const SEvent& event) override;
|
||||||
|
|
||||||
//! draws the element and its children
|
//! draws the element and its children
|
||||||
virtual void draw() override;
|
virtual void draw() override;
|
||||||
|
|
||||||
//! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
|
//! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
|
||||||
virtual void setOverrideFont(gui::IGUIFont* font=0) override;
|
virtual void setOverrideFont(gui::IGUIFont* font=0) override;
|
||||||
|
|
||||||
//! Gets the override font (if any)
|
//! Gets the override font (if any)
|
||||||
virtual gui::IGUIFont* getOverrideFont() const override;
|
virtual gui::IGUIFont* getOverrideFont() const override;
|
||||||
|
|
||||||
//! Get the font which is used right now for drawing
|
//! Get the font which is used right now for drawing
|
||||||
virtual gui::IGUIFont* getActiveFont() const override;
|
virtual gui::IGUIFont* getActiveFont() const override;
|
||||||
|
|
||||||
//! Sets another color for the button text.
|
//! Sets another color for the button text.
|
||||||
virtual void setOverrideColor(video::SColor color) override;
|
virtual void setOverrideColor(video::SColor color) override;
|
||||||
|
|
||||||
//! Gets the override color
|
//! Gets the override color
|
||||||
virtual video::SColor getOverrideColor() const override;
|
virtual video::SColor getOverrideColor() const override;
|
||||||
|
|
||||||
//! Gets the currently used text color
|
//! Gets the currently used text color
|
||||||
virtual video::SColor getActiveColor() const override;
|
virtual video::SColor getActiveColor() const override;
|
||||||
|
|
||||||
//! Sets if the button text should use the override color or the color in the gui skin.
|
//! Sets if the button text should use the override color or the color in the gui skin.
|
||||||
virtual void enableOverrideColor(bool enable) override;
|
virtual void enableOverrideColor(bool enable) override;
|
||||||
|
|
||||||
//! Checks if an override color is enabled
|
//! Checks if an override color is enabled
|
||||||
virtual bool isOverrideColorEnabled(void) const override;
|
virtual bool isOverrideColorEnabled(void) const override;
|
||||||
|
|
||||||
// PATCH
|
// PATCH
|
||||||
//! Sets an image which should be displayed on the button when it is in the given state.
|
//! Sets an image which should be displayed on the button when it is in the given state.
|
||||||
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
|
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
|
||||||
video::ITexture* image=nullptr,
|
video::ITexture* image=nullptr,
|
||||||
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0)) override;
|
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0)) override;
|
||||||
|
|
||||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||||
virtual void setImage(video::ITexture* image=nullptr) override;
|
virtual void setImage(video::ITexture* image=nullptr) override;
|
||||||
|
|
||||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||||
virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||||
|
|
||||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||||
virtual void setPressedImage(video::ITexture* image=nullptr) override;
|
virtual void setPressedImage(video::ITexture* image=nullptr) override;
|
||||||
|
|
||||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||||
virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||||
|
|
||||||
//! Sets the text displayed by the button
|
//! Sets the text displayed by the button
|
||||||
virtual void setText(const wchar_t* text) override;
|
virtual void setText(const wchar_t* text) override;
|
||||||
// END PATCH
|
// END PATCH
|
||||||
|
|
||||||
//! Sets the sprite bank used by the button
|
//! Sets the sprite bank used by the button
|
||||||
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
|
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
|
||||||
|
|
||||||
//! Sets the animated sprite for a specific button state
|
//! Sets the animated sprite for a specific button state
|
||||||
/** \param index: Number of the sprite within the sprite bank, use -1 for no sprite
|
/** \param index: Number of the sprite within the sprite bank, use -1 for no sprite
|
||||||
\param state: State of the button to set the sprite for
|
\param state: State of the button to set the sprite for
|
||||||
\param index: The sprite number from the current sprite bank
|
\param index: The sprite number from the current sprite bank
|
||||||
\param color: The color of the sprite
|
\param color: The color of the sprite
|
||||||
*/
|
*/
|
||||||
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
|
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
|
||||||
video::SColor color=video::SColor(255,255,255,255),
|
video::SColor color=video::SColor(255,255,255,255),
|
||||||
bool loop=false, bool scale=false) override;
|
bool loop=false, bool scale=false) override;
|
||||||
|
|
||||||
//! Get the sprite-index for the given state or -1 when no sprite is set
|
//! Get the sprite-index for the given state or -1 when no sprite is set
|
||||||
virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override;
|
virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override;
|
||||||
|
|
||||||
//! Get the sprite color for the given state. Color is only used when a sprite is set.
|
//! Get the sprite color for the given state. Color is only used when a sprite is set.
|
||||||
virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const override;
|
virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const override;
|
||||||
|
|
||||||
//! Returns if the sprite in the given state does loop
|
//! Returns if the sprite in the given state does loop
|
||||||
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
|
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
|
||||||
|
|
||||||
//! Returns if the sprite in the given state is scaled
|
//! Returns if the sprite in the given state is scaled
|
||||||
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
|
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
|
||||||
|
|
||||||
//! Sets if the button should behave like a push button. Which means it
|
//! Sets if the button should behave like a push button. Which means it
|
||||||
//! can be in two states: Normal or Pressed. With a click on the button,
|
//! can be in two states: Normal or Pressed. With a click on the button,
|
||||||
//! the user can change the state of the button.
|
//! the user can change the state of the button.
|
||||||
virtual void setIsPushButton(bool isPushButton=true) override;
|
virtual void setIsPushButton(bool isPushButton=true) override;
|
||||||
|
|
||||||
//! Checks whether the button is a push button
|
//! Checks whether the button is a push button
|
||||||
virtual bool isPushButton() const override;
|
virtual bool isPushButton() const override;
|
||||||
|
|
||||||
//! Sets the pressed state of the button if this is a pushbutton
|
//! Sets the pressed state of the button if this is a pushbutton
|
||||||
virtual void setPressed(bool pressed=true) override;
|
virtual void setPressed(bool pressed=true) override;
|
||||||
|
|
||||||
//! Returns if the button is currently pressed
|
//! Returns if the button is currently pressed
|
||||||
virtual bool isPressed() const override;
|
virtual bool isPressed() const override;
|
||||||
|
|
||||||
// PATCH
|
// PATCH
|
||||||
//! Returns if this element (or one of its direct children) is hovered
|
//! Returns if this element (or one of its direct children) is hovered
|
||||||
bool isHovered() const;
|
bool isHovered() const;
|
||||||
|
|
||||||
//! Returns if this element (or one of its direct children) is focused
|
//! Returns if this element (or one of its direct children) is focused
|
||||||
bool isFocused() const;
|
bool isFocused() const;
|
||||||
// END PATCH
|
// END PATCH
|
||||||
|
|
||||||
//! Sets if the button should use the skin to draw its border
|
//! Sets if the button should use the skin to draw its border
|
||||||
virtual void setDrawBorder(bool border=true) override;
|
virtual void setDrawBorder(bool border=true) override;
|
||||||
|
|
||||||
//! Checks if the button face and border are being drawn
|
//! Checks if the button face and border are being drawn
|
||||||
virtual bool isDrawingBorder() const override;
|
virtual bool isDrawingBorder() const override;
|
||||||
|
|
||||||
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
|
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
|
||||||
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
|
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
|
||||||
|
|
||||||
//! Checks if the alpha channel should be used for drawing images on the button
|
//! Checks if the alpha channel should be used for drawing images on the button
|
||||||
virtual bool isAlphaChannelUsed() const override;
|
virtual bool isAlphaChannelUsed() const override;
|
||||||
|
|
||||||
//! Sets if the button should scale the button images to fit
|
//! Sets if the button should scale the button images to fit
|
||||||
virtual void setScaleImage(bool scaleImage=true) override;
|
virtual void setScaleImage(bool scaleImage=true) override;
|
||||||
|
|
||||||
//! Checks whether the button scales the used images
|
//! Checks whether the button scales the used images
|
||||||
virtual bool isScalingImage() const override;
|
virtual bool isScalingImage() const override;
|
||||||
|
|
||||||
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
|
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
|
||||||
virtual bool getClickShiftState() const override
|
virtual bool getClickShiftState() const override
|
||||||
{
|
{
|
||||||
return ClickShiftState;
|
return ClickShiftState;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
|
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
|
||||||
virtual bool getClickControlState() const override
|
virtual bool getClickControlState() const override
|
||||||
{
|
{
|
||||||
return ClickControlState;
|
return ClickControlState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColor(video::SColor color);
|
void setColor(video::SColor color);
|
||||||
// PATCH
|
// PATCH
|
||||||
//! Set element properties from a StyleSpec corresponding to the button state
|
//! Set element properties from a StyleSpec corresponding to the button state
|
||||||
void setFromState();
|
void setFromState();
|
||||||
|
|
||||||
//! Set element properties from a StyleSpec
|
//! Set element properties from a StyleSpec
|
||||||
virtual void setFromStyle(const StyleSpec& style);
|
virtual void setFromStyle(const StyleSpec& style);
|
||||||
|
|
||||||
//! Set the styles used for each state
|
//! Set the styles used for each state
|
||||||
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
|
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
|
||||||
// END PATCH
|
// END PATCH
|
||||||
|
|
||||||
|
|
||||||
//! Do not drop returned handle
|
//! Do not drop returned handle
|
||||||
static GUIButton* addButton(gui::IGUIEnvironment *environment,
|
static GUIButton* addButton(gui::IGUIEnvironment *environment,
|
||||||
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
|
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
|
||||||
IGUIElement* parent, s32 id, const wchar_t* text,
|
IGUIElement* parent, s32 id, const wchar_t* text,
|
||||||
const wchar_t *tooltiptext=L"");
|
const wchar_t *tooltiptext=L"");
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
|
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
|
||||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
|
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
|
||||||
|
|
||||||
ISimpleTextureSource *getTextureSource() { return TSrc; }
|
ISimpleTextureSource *getTextureSource() { return TSrc; }
|
||||||
|
|
||||||
struct ButtonImage
|
struct ButtonImage
|
||||||
{
|
{
|
||||||
ButtonImage() = default;
|
ButtonImage() = default;
|
||||||
|
|
||||||
ButtonImage(const ButtonImage& other)
|
ButtonImage(const ButtonImage& other)
|
||||||
{
|
{
|
||||||
*this = other;
|
*this = other;
|
||||||
}
|
}
|
||||||
|
|
||||||
~ButtonImage()
|
~ButtonImage()
|
||||||
{
|
{
|
||||||
if ( Texture )
|
if ( Texture )
|
||||||
Texture->drop();
|
Texture->drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonImage& operator=(const ButtonImage& other)
|
ButtonImage& operator=(const ButtonImage& other)
|
||||||
{
|
{
|
||||||
if ( this == &other )
|
if ( this == &other )
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
if (other.Texture)
|
if (other.Texture)
|
||||||
other.Texture->grab();
|
other.Texture->grab();
|
||||||
if ( Texture )
|
if ( Texture )
|
||||||
Texture->drop();
|
Texture->drop();
|
||||||
Texture = other.Texture;
|
Texture = other.Texture;
|
||||||
SourceRect = other.SourceRect;
|
SourceRect = other.SourceRect;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const ButtonImage& other) const
|
bool operator==(const ButtonImage& other) const
|
||||||
{
|
{
|
||||||
return Texture == other.Texture && SourceRect == other.SourceRect;
|
return Texture == other.Texture && SourceRect == other.SourceRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
video::ITexture* Texture = nullptr;
|
video::ITexture* Texture = nullptr;
|
||||||
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
|
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
|
||||||
};
|
};
|
||||||
|
|
||||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
|
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
struct ButtonSprite
|
struct ButtonSprite
|
||||||
{
|
{
|
||||||
bool operator==(const ButtonSprite &other) const
|
bool operator==(const ButtonSprite &other) const
|
||||||
{
|
{
|
||||||
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
|
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 Index = -1;
|
s32 Index = -1;
|
||||||
video::SColor Color;
|
video::SColor Color;
|
||||||
bool Loop = false;
|
bool Loop = false;
|
||||||
bool Scale = false;
|
bool Scale = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
|
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
|
||||||
gui::IGUISpriteBank* SpriteBank = nullptr;
|
gui::IGUISpriteBank* SpriteBank = nullptr;
|
||||||
|
|
||||||
ButtonImage ButtonImages[gui::EGBIS_COUNT];
|
ButtonImage ButtonImages[gui::EGBIS_COUNT];
|
||||||
|
|
||||||
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
|
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
|
||||||
|
|
||||||
gui::IGUIFont* OverrideFont = nullptr;
|
gui::IGUIFont* OverrideFont = nullptr;
|
||||||
|
|
||||||
bool OverrideColorEnabled = false;
|
bool OverrideColorEnabled = false;
|
||||||
video::SColor OverrideColor = video::SColor(101,255,255,255);
|
video::SColor OverrideColor = video::SColor(101,255,255,255);
|
||||||
|
|
||||||
u32 ClickTime = 0;
|
u32 ClickTime = 0;
|
||||||
u32 HoverTime = 0;
|
u32 HoverTime = 0;
|
||||||
u32 FocusTime = 0;
|
u32 FocusTime = 0;
|
||||||
|
|
||||||
bool ClickShiftState = false;
|
bool ClickShiftState = false;
|
||||||
bool ClickControlState = false;
|
bool ClickControlState = false;
|
||||||
|
|
||||||
bool IsPushButton = false;
|
bool IsPushButton = false;
|
||||||
bool Pressed = false;
|
bool Pressed = false;
|
||||||
bool UseAlphaChannel = false;
|
bool UseAlphaChannel = false;
|
||||||
bool DrawBorder = true;
|
bool DrawBorder = true;
|
||||||
bool ScaleImage = false;
|
bool ScaleImage = false;
|
||||||
|
|
||||||
video::SColor Colors[4];
|
video::SColor Colors[4];
|
||||||
// PATCH
|
// PATCH
|
||||||
bool WasHovered = false;
|
bool WasHovered = false;
|
||||||
bool WasFocused = false;
|
bool WasFocused = false;
|
||||||
ISimpleTextureSource *TSrc;
|
ISimpleTextureSource *TSrc;
|
||||||
|
|
||||||
gui::IGUIStaticText *StaticText;
|
gui::IGUIStaticText *StaticText;
|
||||||
|
|
||||||
core::rect<s32> BgMiddle;
|
core::rect<s32> BgMiddle;
|
||||||
core::rect<s32> Padding;
|
core::rect<s32> Padding;
|
||||||
core::vector2d<s32> ContentOffset;
|
core::vector2d<s32> ContentOffset;
|
||||||
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
|
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
|
||||||
// END PATCH
|
// END PATCH
|
||||||
};
|
};
|
||||||
|
|
|
@ -369,6 +369,8 @@ void GUIEngine::run()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_script->beforeClose();
|
||||||
|
|
||||||
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
|
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2074
src/gui/guiSkin.cpp
2074
src/gui/guiSkin.cpp
File diff suppressed because it is too large
Load Diff
|
@ -1,360 +1,360 @@
|
||||||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||||
// This file is part of the "Irrlicht Engine".
|
// This file is part of the "Irrlicht Engine".
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||||
|
|
||||||
#ifndef __GUI_SKIN_H_INCLUDED__
|
#ifndef __GUI_SKIN_H_INCLUDED__
|
||||||
#define __GUI_SKIN_H_INCLUDED__
|
#define __GUI_SKIN_H_INCLUDED__
|
||||||
|
|
||||||
#include "IGUISkin.h"
|
#include "IGUISkin.h"
|
||||||
#include "irrString.h"
|
#include "irrString.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "ITexture.h"
|
#include "ITexture.h"
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
namespace video
|
namespace video
|
||||||
{
|
{
|
||||||
class IVideoDriver;
|
class IVideoDriver;
|
||||||
}
|
}
|
||||||
namespace gui
|
namespace gui
|
||||||
{
|
{
|
||||||
class GUISkin : public IGUISkin
|
class GUISkin : public IGUISkin
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
|
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
|
||||||
|
|
||||||
//! destructor
|
//! destructor
|
||||||
virtual ~GUISkin();
|
virtual ~GUISkin();
|
||||||
|
|
||||||
//! returns default color
|
//! returns default color
|
||||||
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
|
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
|
||||||
|
|
||||||
//! sets a default color
|
//! sets a default color
|
||||||
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
|
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
|
||||||
|
|
||||||
//! returns size for the given size type
|
//! returns size for the given size type
|
||||||
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
|
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
|
||||||
|
|
||||||
//! sets a default size
|
//! sets a default size
|
||||||
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
|
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
|
||||||
|
|
||||||
//! returns the default font
|
//! returns the default font
|
||||||
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
|
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
|
||||||
|
|
||||||
//! sets a default font
|
//! sets a default font
|
||||||
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
|
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
|
||||||
|
|
||||||
//! sets the sprite bank used for drawing icons
|
//! sets the sprite bank used for drawing icons
|
||||||
virtual void setSpriteBank(IGUISpriteBank* bank);
|
virtual void setSpriteBank(IGUISpriteBank* bank);
|
||||||
|
|
||||||
//! gets the sprite bank used for drawing icons
|
//! gets the sprite bank used for drawing icons
|
||||||
virtual IGUISpriteBank* getSpriteBank() const;
|
virtual IGUISpriteBank* getSpriteBank() const;
|
||||||
|
|
||||||
//! Returns a default icon
|
//! Returns a default icon
|
||||||
/** Returns the sprite index within the sprite bank */
|
/** Returns the sprite index within the sprite bank */
|
||||||
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
|
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
|
||||||
|
|
||||||
//! Sets a default icon
|
//! Sets a default icon
|
||||||
/** Sets the sprite index used for drawing icons like arrows,
|
/** Sets the sprite index used for drawing icons like arrows,
|
||||||
close buttons and ticks in checkboxes
|
close buttons and ticks in checkboxes
|
||||||
\param icon: Enum specifying which icon to change
|
\param icon: Enum specifying which icon to change
|
||||||
\param index: The sprite index used to draw this icon */
|
\param index: The sprite index used to draw this icon */
|
||||||
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
|
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
|
||||||
|
|
||||||
//! Returns a default text.
|
//! Returns a default text.
|
||||||
/** For example for Message box button captions:
|
/** For example for Message box button captions:
|
||||||
"OK", "Cancel", "Yes", "No" and so on. */
|
"OK", "Cancel", "Yes", "No" and so on. */
|
||||||
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
|
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
|
||||||
|
|
||||||
//! Sets a default text.
|
//! Sets a default text.
|
||||||
/** For example for Message box button captions:
|
/** For example for Message box button captions:
|
||||||
"OK", "Cancel", "Yes", "No" and so on. */
|
"OK", "Cancel", "Yes", "No" and so on. */
|
||||||
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
|
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
|
||||||
|
|
||||||
//! draws a standard 3d button pane
|
//! draws a standard 3d button pane
|
||||||
/** Used for drawing for example buttons in normal state.
|
/** Used for drawing for example buttons in normal state.
|
||||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area.
|
\param clip: Clip area.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly. */
|
implementations to find out how to draw the part exactly. */
|
||||||
virtual void draw3DButtonPaneStandard(IGUIElement* element,
|
virtual void draw3DButtonPaneStandard(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0)
|
const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColored3DButtonPaneStandard(element, rect,clip);
|
drawColored3DButtonPaneStandard(element, rect,clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
|
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0,
|
const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a pressed 3d button pane
|
//! draws a pressed 3d button pane
|
||||||
/** Used for drawing for example buttons in pressed state.
|
/** Used for drawing for example buttons in pressed state.
|
||||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area.
|
\param clip: Clip area.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly. */
|
implementations to find out how to draw the part exactly. */
|
||||||
virtual void draw3DButtonPanePressed(IGUIElement* element,
|
virtual void draw3DButtonPanePressed(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0)
|
const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColored3DButtonPanePressed(element, rect, clip);
|
drawColored3DButtonPanePressed(element, rect, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
|
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0,
|
const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a sunken 3d pane
|
//! draws a sunken 3d pane
|
||||||
/** Used for drawing the background of edit, combo or check boxes.
|
/** Used for drawing the background of edit, combo or check boxes.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param bgcolor: Background color.
|
\param bgcolor: Background color.
|
||||||
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
|
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
|
||||||
deep into the ground.
|
deep into the ground.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void draw3DSunkenPane(IGUIElement* element,
|
virtual void draw3DSunkenPane(IGUIElement* element,
|
||||||
video::SColor bgcolor, bool flat,
|
video::SColor bgcolor, bool flat,
|
||||||
bool fillBackGround,
|
bool fillBackGround,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0)
|
const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
|
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DSunkenPane(IGUIElement* element,
|
virtual void drawColored3DSunkenPane(IGUIElement* element,
|
||||||
video::SColor bgcolor, bool flat,
|
video::SColor bgcolor, bool flat,
|
||||||
bool fillBackGround,
|
bool fillBackGround,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0,
|
const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a window background
|
//! draws a window background
|
||||||
/** Used for drawing the background of dialogs and windows.
|
/** Used for drawing the background of dialogs and windows.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param titleBarColor: Title color.
|
\param titleBarColor: Title color.
|
||||||
\param drawTitleBar: True to enable title drawing.
|
\param drawTitleBar: True to enable title drawing.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area.
|
\param clip: Clip area.
|
||||||
\param checkClientArea: When set to non-null the function will not draw anything,
|
\param checkClientArea: When set to non-null the function will not draw anything,
|
||||||
but will instead return the clientArea which can be used for drawing by the calling window.
|
but will instead return the clientArea which can be used for drawing by the calling window.
|
||||||
That is the area without borders and without titlebar.
|
That is the area without borders and without titlebar.
|
||||||
\return Returns rect where it would be good to draw title bar text. This will
|
\return Returns rect where it would be good to draw title bar text. This will
|
||||||
work even when checkClientArea is set to a non-null value.*/
|
work even when checkClientArea is set to a non-null value.*/
|
||||||
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
|
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
|
||||||
bool drawTitleBar, video::SColor titleBarColor,
|
bool drawTitleBar, video::SColor titleBarColor,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip,
|
const core::rect<s32>* clip,
|
||||||
core::rect<s32>* checkClientArea)
|
core::rect<s32>* checkClientArea)
|
||||||
{
|
{
|
||||||
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
|
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
|
||||||
rect, clip, checkClientArea);
|
rect, clip, checkClientArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
|
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
|
||||||
bool drawTitleBar, video::SColor titleBarColor,
|
bool drawTitleBar, video::SColor titleBarColor,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip,
|
const core::rect<s32>* clip,
|
||||||
core::rect<s32>* checkClientArea,
|
core::rect<s32>* checkClientArea,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a standard 3d menu pane
|
//! draws a standard 3d menu pane
|
||||||
/** Used for drawing for menus and context menus.
|
/** Used for drawing for menus and context menus.
|
||||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void draw3DMenuPane(IGUIElement* element,
|
virtual void draw3DMenuPane(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0)
|
const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColored3DMenuPane(element, rect, clip);
|
drawColored3DMenuPane(element, rect, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DMenuPane(IGUIElement* element,
|
virtual void drawColored3DMenuPane(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0,
|
const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a standard 3d tool bar
|
//! draws a standard 3d tool bar
|
||||||
/** Used for drawing for toolbars and menus.
|
/** Used for drawing for toolbars and menus.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void draw3DToolBar(IGUIElement* element,
|
virtual void draw3DToolBar(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0)
|
const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColored3DToolBar(element, rect, clip);
|
drawColored3DToolBar(element, rect, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DToolBar(IGUIElement* element,
|
virtual void drawColored3DToolBar(IGUIElement* element,
|
||||||
const core::rect<s32>& rect,
|
const core::rect<s32>& rect,
|
||||||
const core::rect<s32>* clip=0,
|
const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a tab button
|
//! draws a tab button
|
||||||
/** Used for drawing for tab buttons on top of tabs.
|
/** Used for drawing for tab buttons on top of tabs.
|
||||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param active: Specifies if the tab is currently active.
|
\param active: Specifies if the tab is currently active.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void draw3DTabButton(IGUIElement* element, bool active,
|
virtual void draw3DTabButton(IGUIElement* element, bool active,
|
||||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||||
{
|
{
|
||||||
drawColored3DTabButton(element, active, rect, clip, alignment);
|
drawColored3DTabButton(element, active, rect, clip, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
|
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
|
||||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a tab control body
|
//! draws a tab control body
|
||||||
/** \param element: Pointer to the element which wishes to draw this. This parameter
|
/** \param element: Pointer to the element which wishes to draw this. This parameter
|
||||||
is usually not used by ISkin, but can be used for example by more complex
|
is usually not used by ISkin, but can be used for example by more complex
|
||||||
implementations to find out how to draw the part exactly.
|
implementations to find out how to draw the part exactly.
|
||||||
\param border: Specifies if the border should be drawn.
|
\param border: Specifies if the border should be drawn.
|
||||||
\param background: Specifies if the background should be drawn.
|
\param background: Specifies if the background should be drawn.
|
||||||
\param rect: Defining area where to draw.
|
\param rect: Defining area where to draw.
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void draw3DTabBody(IGUIElement* element, bool border, bool background,
|
virtual void draw3DTabBody(IGUIElement* element, bool border, bool background,
|
||||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||||
{
|
{
|
||||||
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
|
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background,
|
virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background,
|
||||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws an icon, usually from the skin's sprite bank
|
//! draws an icon, usually from the skin's sprite bank
|
||||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||||
This parameter is usually not used by IGUISkin, but can be used for example
|
This parameter is usually not used by IGUISkin, but can be used for example
|
||||||
by more complex implementations to find out how to draw the part exactly.
|
by more complex implementations to find out how to draw the part exactly.
|
||||||
\param icon: Specifies the icon to be drawn.
|
\param icon: Specifies the icon to be drawn.
|
||||||
\param position: The position to draw the icon
|
\param position: The position to draw the icon
|
||||||
\param starttime: The time at the start of the animation
|
\param starttime: The time at the start of the animation
|
||||||
\param currenttime: The present time, used to calculate the frame number
|
\param currenttime: The present time, used to calculate the frame number
|
||||||
\param loop: Whether the animation should loop or not
|
\param loop: Whether the animation should loop or not
|
||||||
\param clip: Clip area. */
|
\param clip: Clip area. */
|
||||||
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||||
const core::position2di position,
|
const core::position2di position,
|
||||||
u32 starttime=0, u32 currenttime=0,
|
u32 starttime=0, u32 currenttime=0,
|
||||||
bool loop=false, const core::rect<s32>* clip=0)
|
bool loop=false, const core::rect<s32>* clip=0)
|
||||||
{
|
{
|
||||||
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
|
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||||
const core::position2di position,
|
const core::position2di position,
|
||||||
u32 starttime=0, u32 currenttime=0,
|
u32 starttime=0, u32 currenttime=0,
|
||||||
bool loop=false, const core::rect<s32>* clip=0,
|
bool loop=false, const core::rect<s32>* clip=0,
|
||||||
const video::SColor* colors=0);
|
const video::SColor* colors=0);
|
||||||
|
|
||||||
//! draws a 2d rectangle.
|
//! draws a 2d rectangle.
|
||||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||||
This parameter is usually not used by IGUISkin, but can be used for example
|
This parameter is usually not used by IGUISkin, but can be used for example
|
||||||
by more complex implementations to find out how to draw the part exactly.
|
by more complex implementations to find out how to draw the part exactly.
|
||||||
\param color: Color of the rectangle to draw. The alpha component specifies how
|
\param color: Color of the rectangle to draw. The alpha component specifies how
|
||||||
transparent the rectangle will be.
|
transparent the rectangle will be.
|
||||||
\param pos: Position of the rectangle.
|
\param pos: Position of the rectangle.
|
||||||
\param clip: Pointer to rectangle against which the rectangle will be clipped.
|
\param clip: Pointer to rectangle against which the rectangle will be clipped.
|
||||||
If the pointer is null, no clipping will be performed. */
|
If the pointer is null, no clipping will be performed. */
|
||||||
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
|
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
|
||||||
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
|
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
|
||||||
|
|
||||||
|
|
||||||
//! get the type of this skin
|
//! get the type of this skin
|
||||||
virtual EGUI_SKIN_TYPE getType() const;
|
virtual EGUI_SKIN_TYPE getType() const;
|
||||||
|
|
||||||
//! gets the colors
|
//! gets the colors
|
||||||
virtual void getColors(video::SColor* colors); // ::PATCH:
|
virtual void getColors(video::SColor* colors); // ::PATCH:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
video::SColor Colors[EGDC_COUNT];
|
video::SColor Colors[EGDC_COUNT];
|
||||||
s32 Sizes[EGDS_COUNT];
|
s32 Sizes[EGDS_COUNT];
|
||||||
u32 Icons[EGDI_COUNT];
|
u32 Icons[EGDI_COUNT];
|
||||||
IGUIFont* Fonts[EGDF_COUNT];
|
IGUIFont* Fonts[EGDF_COUNT];
|
||||||
IGUISpriteBank* SpriteBank;
|
IGUISpriteBank* SpriteBank;
|
||||||
core::stringw Texts[EGDT_COUNT];
|
core::stringw Texts[EGDT_COUNT];
|
||||||
video::IVideoDriver* Driver;
|
video::IVideoDriver* Driver;
|
||||||
bool UseGradient;
|
bool UseGradient;
|
||||||
|
|
||||||
EGUI_SKIN_TYPE Type;
|
EGUI_SKIN_TYPE Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define set3DSkinColors(skin, button_color) \
|
#define set3DSkinColors(skin, button_color) \
|
||||||
{ \
|
{ \
|
||||||
skin->setColor(EGDC_3D_FACE, button_color); \
|
skin->setColor(EGDC_3D_FACE, button_color); \
|
||||||
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
|
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
|
||||||
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
|
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
|
||||||
skin->setColor(EGDC_3D_LIGHT, button_color); \
|
skin->setColor(EGDC_3D_LIGHT, button_color); \
|
||||||
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
|
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define getElementSkinColor(color) \
|
#define getElementSkinColor(color) \
|
||||||
{ \
|
{ \
|
||||||
if (!Colors) \
|
if (!Colors) \
|
||||||
{ \
|
{ \
|
||||||
IGUISkin* skin = Environment->getSkin(); \
|
IGUISkin* skin = Environment->getSkin(); \
|
||||||
if (skin) \
|
if (skin) \
|
||||||
return skin->getColor(color); \
|
return skin->getColor(color); \
|
||||||
} \
|
} \
|
||||||
return Colors[color]; \
|
return Colors[color]; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define setElementSkinColor(which, newColor, shading) \
|
#define setElementSkinColor(which, newColor, shading) \
|
||||||
{ \
|
{ \
|
||||||
if (!Colors) \
|
if (!Colors) \
|
||||||
{ \
|
{ \
|
||||||
Colors = new video::SColor[EGDC_COUNT]; \
|
Colors = new video::SColor[EGDC_COUNT]; \
|
||||||
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
|
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
|
||||||
if (skin) \
|
if (skin) \
|
||||||
skin->getColors(Colors); \
|
skin->getColors(Colors); \
|
||||||
} \
|
} \
|
||||||
Colors[which] = newColor; \
|
Colors[which] = newColor; \
|
||||||
setShading(Colors[which],shading); \
|
setShading(Colors[which],shading); \
|
||||||
}
|
}
|
||||||
} // end namespace gui
|
} // end namespace gui
|
||||||
//! Sets the shading
|
//! Sets the shading
|
||||||
inline void setShading(video::SColor &color,f32 s) // :PATCH:
|
inline void setShading(video::SColor &color,f32 s) // :PATCH:
|
||||||
{
|
{
|
||||||
if (s < 1.0f)
|
if (s < 1.0f)
|
||||||
{
|
{
|
||||||
color.setRed(color.getRed() * s);
|
color.setRed(color.getRed() * s);
|
||||||
color.setGreen(color.getGreen() * s);
|
color.setGreen(color.getGreen() * s);
|
||||||
color.setBlue(color.getBlue() * s);
|
color.setBlue(color.getBlue() * s);
|
||||||
}
|
}
|
||||||
else if (s > 1.0f)
|
else if (s > 1.0f)
|
||||||
{
|
{
|
||||||
s -= 1.0f;
|
s -= 1.0f;
|
||||||
|
|
||||||
color.setRed(color.getRed() + (255 - color.getRed()) * s);
|
color.setRed(color.getRed() + (255 - color.getRed()) * s);
|
||||||
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
|
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
|
||||||
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
|
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // end namespace irr
|
} // end namespace irr
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -239,7 +239,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
// display software keyboard when clicking edit boxes
|
// display software keyboard when clicking edit boxes
|
||||||
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
|
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
|
||||||
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
|
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN &&
|
||||||
|
!porting::hasPhysicalKeyboardAndroid()) {
|
||||||
gui::IGUIElement *hovered =
|
gui::IGUIElement *hovered =
|
||||||
Environment->getRootGUIElement()->getElementFromPoint(
|
Environment->getRootGUIElement()->getElementFromPoint(
|
||||||
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
|
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
|
||||||
|
|
|
@ -414,6 +414,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold");
|
m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold");
|
||||||
|
m_long_tap_delay = g_settings->getU16("touch_long_tap_delay");
|
||||||
m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
|
m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
|
||||||
m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1");
|
m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1");
|
||||||
m_screensize = m_device->getVideoDriver()->getScreenSize();
|
m_screensize = m_device->getVideoDriver()->getScreenSize();
|
||||||
|
@ -999,7 +1000,7 @@ void TouchScreenGUI::step(float dtime)
|
||||||
if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
|
if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
|
||||||
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
|
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
|
||||||
|
|
||||||
if (delta > MIN_DIG_TIME_MS) {
|
if (delta > m_long_tap_delay) {
|
||||||
m_tap_state = TapState::LongTap;
|
m_tap_state = TapState::LongTap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1105,11 +1106,11 @@ void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode)
|
||||||
// Since the pointed thing has already been determined when this function
|
// Since the pointed thing has already been determined when this function
|
||||||
// is called, we cannot use this function to update the shootline.
|
// is called, we cannot use this function to update the shootline.
|
||||||
|
|
||||||
|
sanity_check(mode != TouchInteractionMode_USER);
|
||||||
|
u64 now = porting::getTimeMs();
|
||||||
bool target_dig_pressed = false;
|
bool target_dig_pressed = false;
|
||||||
bool target_place_pressed = false;
|
bool target_place_pressed = false;
|
||||||
|
|
||||||
u64 now = porting::getTimeMs();
|
|
||||||
|
|
||||||
// If the meanings of short and long taps have been swapped, abort any ongoing
|
// If the meanings of short and long taps have been swapped, abort any ongoing
|
||||||
// short taps because they would do something else than the player expected.
|
// short taps because they would do something else than the player expected.
|
||||||
// Long taps don't need this, they're adjusted to the swapped meanings instead.
|
// Long taps don't need this, they're adjusted to the swapped meanings instead.
|
||||||
|
|
|
@ -79,7 +79,6 @@ typedef enum
|
||||||
AHBB_Dir_Right_Left
|
AHBB_Dir_Right_Left
|
||||||
} autohide_button_bar_dir;
|
} autohide_button_bar_dir;
|
||||||
|
|
||||||
#define MIN_DIG_TIME_MS 500
|
|
||||||
#define BUTTON_REPEAT_DELAY 0.2f
|
#define BUTTON_REPEAT_DELAY 0.2f
|
||||||
#define SETTINGS_BAR_Y_OFFSET 5
|
#define SETTINGS_BAR_Y_OFFSET 5
|
||||||
#define RARE_CONTROLS_BAR_Y_OFFSET 5
|
#define RARE_CONTROLS_BAR_Y_OFFSET 5
|
||||||
|
@ -225,6 +224,7 @@ private:
|
||||||
v2u32 m_screensize;
|
v2u32 m_screensize;
|
||||||
s32 button_size;
|
s32 button_size;
|
||||||
double m_touchscreen_threshold;
|
double m_touchscreen_threshold;
|
||||||
|
u16 m_long_tap_delay;
|
||||||
bool m_visible; // is the whole touch screen gui visible
|
bool m_visible; // is the whole touch screen gui visible
|
||||||
|
|
||||||
std::unordered_map<u16, rect<s32>> m_hotbar_rects;
|
std::unordered_map<u16, rect<s32>> m_hotbar_rects;
|
||||||
|
|
|
@ -40,24 +40,37 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
TouchInteraction::TouchInteraction()
|
TouchInteraction::TouchInteraction()
|
||||||
{
|
{
|
||||||
pointed_nothing = LONG_DIG_SHORT_PLACE;
|
pointed_nothing = TouchInteractionMode_USER;
|
||||||
pointed_node = LONG_DIG_SHORT_PLACE;
|
pointed_node = TouchInteractionMode_USER;
|
||||||
// Map punching to single tap by default.
|
pointed_object = TouchInteractionMode_USER;
|
||||||
pointed_object = SHORT_DIG_LONG_PLACE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchInteractionMode TouchInteraction::getMode(const PointedThing &pointed) const
|
TouchInteractionMode TouchInteraction::getMode(PointedThingType pointed_type) const
|
||||||
{
|
{
|
||||||
switch (pointed.type) {
|
TouchInteractionMode result;
|
||||||
|
switch (pointed_type) {
|
||||||
case POINTEDTHING_NOTHING:
|
case POINTEDTHING_NOTHING:
|
||||||
return pointed_nothing;
|
result = pointed_nothing;
|
||||||
|
break;
|
||||||
case POINTEDTHING_NODE:
|
case POINTEDTHING_NODE:
|
||||||
return pointed_node;
|
result = pointed_node;
|
||||||
|
break;
|
||||||
case POINTEDTHING_OBJECT:
|
case POINTEDTHING_OBJECT:
|
||||||
return pointed_object;
|
result = pointed_object;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
FATAL_ERROR("Invalid PointedThingType given to TouchInteraction::getMode");
|
FATAL_ERROR("Invalid PointedThingType given to TouchInteraction::getMode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == TouchInteractionMode_USER) {
|
||||||
|
if (pointed_type == POINTEDTHING_OBJECT)
|
||||||
|
result = g_settings->get("touch_punch_gesture") == "long_tap" ?
|
||||||
|
LONG_DIG_SHORT_PLACE : SHORT_DIG_LONG_PLACE;
|
||||||
|
else
|
||||||
|
result = LONG_DIG_SHORT_PLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TouchInteraction::serialize(std::ostream &os) const
|
void TouchInteraction::serialize(std::ostream &os) const
|
||||||
|
|
|
@ -30,10 +30,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "texture_override.h" // TextureOverride
|
#include "texture_override.h" // TextureOverride
|
||||||
#include "tool.h"
|
#include "tool.h"
|
||||||
#include "util/pointabilities.h"
|
#include "util/pointabilities.h"
|
||||||
|
#include "util/pointedthing.h"
|
||||||
|
|
||||||
class IGameDef;
|
class IGameDef;
|
||||||
class Client;
|
class Client;
|
||||||
struct ToolCapabilities;
|
struct ToolCapabilities;
|
||||||
struct PointedThing;
|
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
#include "client/texturesource.h"
|
#include "client/texturesource.h"
|
||||||
struct ItemMesh;
|
struct ItemMesh;
|
||||||
|
@ -57,6 +58,7 @@ enum TouchInteractionMode : u8
|
||||||
{
|
{
|
||||||
LONG_DIG_SHORT_PLACE,
|
LONG_DIG_SHORT_PLACE,
|
||||||
SHORT_DIG_LONG_PLACE,
|
SHORT_DIG_LONG_PLACE,
|
||||||
|
TouchInteractionMode_USER, // Meaning depends on client-side settings
|
||||||
TouchInteractionMode_END, // Dummy for validity check
|
TouchInteractionMode_END, // Dummy for validity check
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,7 +69,9 @@ struct TouchInteraction
|
||||||
TouchInteractionMode pointed_object;
|
TouchInteractionMode pointed_object;
|
||||||
|
|
||||||
TouchInteraction();
|
TouchInteraction();
|
||||||
TouchInteractionMode getMode(const PointedThing &pointed) const;
|
// Returns the right mode for the pointed thing and resolves any occurrence
|
||||||
|
// of TouchInteractionMode_USER into an actual mode.
|
||||||
|
TouchInteractionMode getMode(PointedThingType pointed_type) const;
|
||||||
void serialize(std::ostream &os) const;
|
void serialize(std::ostream &os) const;
|
||||||
void deSerialize(std::istream &is);
|
void deSerialize(std::istream &is);
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <android/log.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
|
@ -103,12 +103,10 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
|
||||||
std::list<SharedBuffer<u8>> *chunks)
|
std::list<SharedBuffer<u8>> *chunks)
|
||||||
{
|
{
|
||||||
// Chunk packets, containing the TYPE_SPLIT header
|
// Chunk packets, containing the TYPE_SPLIT header
|
||||||
u32 chunk_header_size = 7;
|
const u32 chunk_header_size = 7;
|
||||||
u32 maximum_data_size = chunksize_max - chunk_header_size;
|
const u32 maximum_data_size = chunksize_max - chunk_header_size;
|
||||||
u32 start = 0;
|
u32 start = 0, end = 0;
|
||||||
u32 end = 0;
|
u16 chunk_num = 0;
|
||||||
u32 chunk_num = 0;
|
|
||||||
u16 chunk_count = 0;
|
|
||||||
do {
|
do {
|
||||||
end = start + maximum_data_size - 1;
|
end = start + maximum_data_size - 1;
|
||||||
if (end > data.getSize() - 1)
|
if (end > data.getSize() - 1)
|
||||||
|
@ -126,16 +124,16 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
|
||||||
memcpy(&chunk[chunk_header_size], &data[start], payload_size);
|
memcpy(&chunk[chunk_header_size], &data[start], payload_size);
|
||||||
|
|
||||||
chunks->push_back(chunk);
|
chunks->push_back(chunk);
|
||||||
chunk_count++;
|
|
||||||
|
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
|
sanity_check(chunk_num < 0xFFFF); // overflow
|
||||||
chunk_num++;
|
chunk_num++;
|
||||||
}
|
}
|
||||||
while (end != data.getSize() - 1);
|
while (end != data.getSize() - 1);
|
||||||
|
|
||||||
for (SharedBuffer<u8> &chunk : *chunks) {
|
for (auto &chunk : *chunks) {
|
||||||
// Write chunk_count
|
// Write chunk_count
|
||||||
writeU16(&(chunk[3]), chunk_count);
|
writeU16(&chunk[3], chunk_num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1061,22 +1059,22 @@ bool UDPPeer::processReliableSendCommand(
|
||||||
const auto &c = *c_ptr;
|
const auto &c = *c_ptr;
|
||||||
Channel &chan = channels[c.channelnum];
|
Channel &chan = channels[c.channelnum];
|
||||||
|
|
||||||
u32 chunksize_max = max_packet_size
|
const u32 chunksize_max = max_packet_size
|
||||||
- BASE_HEADER_SIZE
|
- BASE_HEADER_SIZE
|
||||||
- RELIABLE_HEADER_SIZE;
|
- RELIABLE_HEADER_SIZE;
|
||||||
|
|
||||||
sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512);
|
|
||||||
|
|
||||||
std::list<SharedBuffer<u8>> originals;
|
std::list<SharedBuffer<u8>> originals;
|
||||||
u16 split_sequence_number = chan.readNextSplitSeqNum();
|
|
||||||
|
|
||||||
if (c.raw) {
|
if (c.raw) {
|
||||||
originals.emplace_back(c.data);
|
originals.emplace_back(c.data);
|
||||||
} else {
|
} else {
|
||||||
makeAutoSplitPacket(c.data, chunksize_max,split_sequence_number, &originals);
|
u16 split_seqnum = chan.readNextSplitSeqNum();
|
||||||
chan.setNextSplitSeqNum(split_sequence_number);
|
makeAutoSplitPacket(c.data, chunksize_max, split_seqnum, &originals);
|
||||||
|
chan.setNextSplitSeqNum(split_seqnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanity_check(originals.size() < MAX_RELIABLE_WINDOW_SIZE);
|
||||||
|
|
||||||
bool have_sequence_number = false;
|
bool have_sequence_number = false;
|
||||||
bool have_initial_sequence_number = false;
|
bool have_initial_sequence_number = false;
|
||||||
std::queue<BufferedPacketPtr> toadd;
|
std::queue<BufferedPacketPtr> toadd;
|
||||||
|
@ -1271,7 +1269,7 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout,
|
||||||
m_udpSocket(ipv6),
|
m_udpSocket(ipv6),
|
||||||
m_protocol_id(protocol_id),
|
m_protocol_id(protocol_id),
|
||||||
m_sendThread(new ConnectionSendThread(max_packet_size, timeout)),
|
m_sendThread(new ConnectionSendThread(max_packet_size, timeout)),
|
||||||
m_receiveThread(new ConnectionReceiveThread(max_packet_size)),
|
m_receiveThread(new ConnectionReceiveThread()),
|
||||||
m_bc_peerhandler(peerhandler)
|
m_bc_peerhandler(peerhandler)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1505,6 +1503,15 @@ void Connection::Send(session_t peer_id, u8 channelnum,
|
||||||
{
|
{
|
||||||
assert(channelnum < CHANNEL_COUNT); // Pre-condition
|
assert(channelnum < CHANNEL_COUNT); // Pre-condition
|
||||||
|
|
||||||
|
// approximate check similar to UDPPeer::processReliableSendCommand()
|
||||||
|
// to get nicer errors / backtraces if this happens.
|
||||||
|
if (reliable && pkt->getSize() > MAX_RELIABLE_WINDOW_SIZE*512) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Packet too big for window, peer_id=" << peer_id
|
||||||
|
<< " command=" << pkt->getCommand() << " size=" << pkt->getSize();
|
||||||
|
FATAL_ERROR(oss.str().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable));
|
putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -796,7 +796,7 @@ void ConnectionSendThread::sendAsPacket(session_t peer_id, u8 channelnum,
|
||||||
m_outgoing_queue.push(packet);
|
m_outgoing_queue.push(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionReceiveThread::ConnectionReceiveThread(unsigned int max_packet_size) :
|
ConnectionReceiveThread::ConnectionReceiveThread() :
|
||||||
Thread("ConnectionReceive")
|
Thread("ConnectionReceive")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ private:
|
||||||
class ConnectionReceiveThread : public Thread
|
class ConnectionReceiveThread : public Thread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ConnectionReceiveThread(unsigned int max_packet_size);
|
ConnectionReceiveThread();
|
||||||
|
|
||||||
void *run();
|
void *run();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "inventory.h"
|
#include "inventory.h"
|
||||||
#include "irrlicht_changes/printing.h"
|
#include "irrlicht_changes/printing.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "debug.h"
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
#include "constants.h" // MAP_BLOCKSIZE
|
#include "constants.h" // MAP_BLOCKSIZE
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
|
@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "irrlicht_changes/printing.h"
|
#include "irrlicht_changes/printing.h"
|
||||||
#include "irrlichttypes_bloated.h"
|
#include "irrlichttypes_bloated.h"
|
||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
|
#include "log.h"
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#define SDL_MAIN_HANDLED 1
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
@ -53,10 +57,8 @@ namespace porting {
|
||||||
bool setSystemPaths(); // used in porting.cpp
|
bool setSystemPaths(); // used in porting.cpp
|
||||||
}
|
}
|
||||||
|
|
||||||
void android_main(android_app *app)
|
extern "C" int SDL_Main(int _argc, char *_argv[])
|
||||||
{
|
{
|
||||||
porting::app_global = app;
|
|
||||||
|
|
||||||
Thread::setName("Main");
|
Thread::setName("Main");
|
||||||
|
|
||||||
char *argv[] = {strdup(PROJECT_NAME), strdup("--verbose"), nullptr};
|
char *argv[] = {strdup(PROJECT_NAME), strdup("--verbose"), nullptr};
|
||||||
|
@ -70,45 +72,15 @@ void android_main(android_app *app)
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace porting {
|
namespace porting {
|
||||||
android_app *app_global = nullptr;
|
|
||||||
JNIEnv *jnienv = nullptr;
|
JNIEnv *jnienv = nullptr;
|
||||||
jclass nativeActivity;
|
jobject activity;
|
||||||
|
jclass activityClass;
|
||||||
jclass findClass(const std::string &classname)
|
|
||||||
{
|
|
||||||
if (jnienv == nullptr)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity");
|
|
||||||
jmethodID getClassLoader = jnienv->GetMethodID(
|
|
||||||
nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
|
|
||||||
jobject cls = jnienv->CallObjectMethod(
|
|
||||||
app_global->activity->clazz, getClassLoader);
|
|
||||||
jclass classLoader = jnienv->FindClass("java/lang/ClassLoader");
|
|
||||||
jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass",
|
|
||||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
|
||||||
jstring strClassName = jnienv->NewStringUTF(classname.c_str());
|
|
||||||
return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void osSpecificInit()
|
void osSpecificInit()
|
||||||
{
|
{
|
||||||
JavaVM *jvm = app_global->activity->vm;
|
jnienv = (JNIEnv*)SDL_AndroidGetJNIEnv();
|
||||||
JavaVMAttachArgs lJavaVMAttachArgs;
|
activity = (jobject)SDL_AndroidGetActivity();
|
||||||
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
|
activityClass = jnienv->GetObjectClass(activity);
|
||||||
lJavaVMAttachArgs.name = PROJECT_NAME_C "NativeThread";
|
|
||||||
lJavaVMAttachArgs.group = nullptr;
|
|
||||||
|
|
||||||
if (jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) {
|
|
||||||
errorstream << "Failed to attach native thread to jvm" << std::endl;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeActivity = findClass("net/minetest/minetest/GameActivity");
|
|
||||||
if (nativeActivity == nullptr)
|
|
||||||
errorstream <<
|
|
||||||
"porting::initAndroid unable to find Java native activity class" <<
|
|
||||||
std::endl;
|
|
||||||
|
|
||||||
// Set default language
|
// Set default language
|
||||||
auto lang = getLanguageAndroid();
|
auto lang = getLanguageAndroid();
|
||||||
|
@ -129,9 +101,6 @@ void cleanupAndroid()
|
||||||
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
|
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
|
||||||
moncleanup();
|
moncleanup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JavaVM *jvm = app_global->activity->vm;
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string readJavaString(jstring j_str)
|
static std::string readJavaString(jstring j_str)
|
||||||
|
@ -149,11 +118,11 @@ bool setSystemPaths()
|
||||||
{
|
{
|
||||||
// Set user and share paths
|
// Set user and share paths
|
||||||
{
|
{
|
||||||
jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity,
|
jmethodID getUserDataPath = jnienv->GetMethodID(activityClass,
|
||||||
"getUserDataPath", "()Ljava/lang/String;");
|
"getUserDataPath", "()Ljava/lang/String;");
|
||||||
FATAL_ERROR_IF(getUserDataPath==nullptr,
|
FATAL_ERROR_IF(getUserDataPath==nullptr,
|
||||||
"porting::initializePathsAndroid unable to find Java getUserDataPath method");
|
"porting::initializePathsAndroid unable to find Java getUserDataPath method");
|
||||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getUserDataPath);
|
jobject result = jnienv->CallObjectMethod(activity, getUserDataPath);
|
||||||
std::string str = readJavaString((jstring) result);
|
std::string str = readJavaString((jstring) result);
|
||||||
path_user = str;
|
path_user = str;
|
||||||
path_share = str;
|
path_share = str;
|
||||||
|
@ -161,11 +130,11 @@ bool setSystemPaths()
|
||||||
|
|
||||||
// Set cache path
|
// Set cache path
|
||||||
{
|
{
|
||||||
jmethodID getCachePath = jnienv->GetMethodID(nativeActivity,
|
jmethodID getCachePath = jnienv->GetMethodID(activityClass,
|
||||||
"getCachePath", "()Ljava/lang/String;");
|
"getCachePath", "()Ljava/lang/String;");
|
||||||
FATAL_ERROR_IF(getCachePath==nullptr,
|
FATAL_ERROR_IF(getCachePath==nullptr,
|
||||||
"porting::initializePathsAndroid unable to find Java getCachePath method");
|
"porting::initializePathsAndroid unable to find Java getCachePath method");
|
||||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getCachePath);
|
jobject result = jnienv->CallObjectMethod(activity, getCachePath);
|
||||||
path_cache = readJavaString((jstring) result);
|
path_cache = readJavaString((jstring) result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +143,7 @@ bool setSystemPaths()
|
||||||
|
|
||||||
void showTextInputDialog(const std::string &hint, const std::string ¤t, int editType)
|
void showTextInputDialog(const std::string &hint, const std::string ¤t, int editType)
|
||||||
{
|
{
|
||||||
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showTextInputDialog",
|
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showTextInputDialog",
|
||||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||||
|
|
||||||
FATAL_ERROR_IF(showdialog == nullptr,
|
FATAL_ERROR_IF(showdialog == nullptr,
|
||||||
|
@ -184,13 +153,13 @@ void showTextInputDialog(const std::string &hint, const std::string ¤t, in
|
||||||
jstring jcurrent = jnienv->NewStringUTF(current.c_str());
|
jstring jcurrent = jnienv->NewStringUTF(current.c_str());
|
||||||
jint jeditType = editType;
|
jint jeditType = editType;
|
||||||
|
|
||||||
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog,
|
jnienv->CallVoidMethod(activity, showdialog,
|
||||||
jhint, jcurrent, jeditType);
|
jhint, jcurrent, jeditType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx)
|
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx)
|
||||||
{
|
{
|
||||||
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showSelectionInputDialog",
|
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showSelectionInputDialog",
|
||||||
"([Ljava/lang/String;I)V");
|
"([Ljava/lang/String;I)V");
|
||||||
|
|
||||||
FATAL_ERROR_IF(showdialog == nullptr,
|
FATAL_ERROR_IF(showdialog == nullptr,
|
||||||
|
@ -205,79 +174,79 @@ void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 select
|
||||||
jnienv->NewStringUTF(optionList[i].c_str()));
|
jnienv->NewStringUTF(optionList[i].c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jOptionList,
|
jnienv->CallVoidMethod(activity, showdialog, jOptionList,
|
||||||
jselectedIdx);
|
jselectedIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openURIAndroid(const char *url)
|
void openURIAndroid(const char *url)
|
||||||
{
|
{
|
||||||
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURI",
|
jmethodID url_open = jnienv->GetMethodID(activityClass, "openURI",
|
||||||
"(Ljava/lang/String;)V");
|
"(Ljava/lang/String;)V");
|
||||||
|
|
||||||
FATAL_ERROR_IF(url_open == nullptr,
|
FATAL_ERROR_IF(url_open == nullptr,
|
||||||
"porting::openURIAndroid unable to find Java openURI method");
|
"porting::openURIAndroid unable to find Java openURI method");
|
||||||
|
|
||||||
jstring jurl = jnienv->NewStringUTF(url);
|
jstring jurl = jnienv->NewStringUTF(url);
|
||||||
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
|
jnienv->CallVoidMethod(activity, url_open, jurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareFileAndroid(const std::string &path)
|
void shareFileAndroid(const std::string &path)
|
||||||
{
|
{
|
||||||
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "shareFile",
|
jmethodID url_open = jnienv->GetMethodID(activityClass, "shareFile",
|
||||||
"(Ljava/lang/String;)V");
|
"(Ljava/lang/String;)V");
|
||||||
|
|
||||||
FATAL_ERROR_IF(url_open == nullptr,
|
FATAL_ERROR_IF(url_open == nullptr,
|
||||||
"porting::shareFileAndroid unable to find Java shareFile method");
|
"porting::shareFileAndroid unable to find Java shareFile method");
|
||||||
|
|
||||||
jstring jurl = jnienv->NewStringUTF(path.c_str());
|
jstring jurl = jnienv->NewStringUTF(path.c_str());
|
||||||
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
|
jnienv->CallVoidMethod(activity, url_open, jurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidDialogType getLastInputDialogType()
|
AndroidDialogType getLastInputDialogType()
|
||||||
{
|
{
|
||||||
jmethodID lastdialogtype = jnienv->GetMethodID(nativeActivity,
|
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,
|
||||||
"getLastDialogType", "()I");
|
"getLastDialogType", "()I");
|
||||||
|
|
||||||
FATAL_ERROR_IF(lastdialogtype == nullptr,
|
FATAL_ERROR_IF(lastdialogtype == nullptr,
|
||||||
"porting::getLastInputDialogType unable to find Java getLastDialogType method");
|
"porting::getLastInputDialogType unable to find Java getLastDialogType method");
|
||||||
|
|
||||||
int dialogType = jnienv->CallIntMethod(app_global->activity->clazz, lastdialogtype);
|
int dialogType = jnienv->CallIntMethod(activity, lastdialogtype);
|
||||||
return static_cast<AndroidDialogType>(dialogType);
|
return static_cast<AndroidDialogType>(dialogType);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidDialogState getInputDialogState()
|
AndroidDialogState getInputDialogState()
|
||||||
{
|
{
|
||||||
jmethodID inputdialogstate = jnienv->GetMethodID(nativeActivity,
|
jmethodID inputdialogstate = jnienv->GetMethodID(activityClass,
|
||||||
"getInputDialogState", "()I");
|
"getInputDialogState", "()I");
|
||||||
|
|
||||||
FATAL_ERROR_IF(inputdialogstate == nullptr,
|
FATAL_ERROR_IF(inputdialogstate == nullptr,
|
||||||
"porting::getInputDialogState unable to find Java getInputDialogState method");
|
"porting::getInputDialogState unable to find Java getInputDialogState method");
|
||||||
|
|
||||||
int dialogState = jnienv->CallIntMethod(app_global->activity->clazz, inputdialogstate);
|
int dialogState = jnienv->CallIntMethod(activity, inputdialogstate);
|
||||||
return static_cast<AndroidDialogState>(dialogState);
|
return static_cast<AndroidDialogState>(dialogState);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getInputDialogMessage()
|
std::string getInputDialogMessage()
|
||||||
{
|
{
|
||||||
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
|
jmethodID dialogvalue = jnienv->GetMethodID(activityClass,
|
||||||
"getDialogMessage", "()Ljava/lang/String;");
|
"getDialogMessage", "()Ljava/lang/String;");
|
||||||
|
|
||||||
FATAL_ERROR_IF(dialogvalue == nullptr,
|
FATAL_ERROR_IF(dialogvalue == nullptr,
|
||||||
"porting::getInputDialogMessage unable to find Java getDialogMessage method");
|
"porting::getInputDialogMessage unable to find Java getDialogMessage method");
|
||||||
|
|
||||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
|
jobject result = jnienv->CallObjectMethod(activity,
|
||||||
dialogvalue);
|
dialogvalue);
|
||||||
return readJavaString((jstring) result);
|
return readJavaString((jstring) result);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getInputDialogSelection()
|
int getInputDialogSelection()
|
||||||
{
|
{
|
||||||
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, "getDialogSelection", "()I");
|
jmethodID dialogvalue = jnienv->GetMethodID(activityClass, "getDialogSelection", "()I");
|
||||||
|
|
||||||
FATAL_ERROR_IF(dialogvalue == nullptr,
|
FATAL_ERROR_IF(dialogvalue == nullptr,
|
||||||
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
|
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
|
||||||
|
|
||||||
return jnienv->CallIntMethod(app_global->activity->clazz, dialogvalue);
|
return jnienv->CallIntMethod(activity, dialogvalue);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
|
@ -287,13 +256,13 @@ float getDisplayDensity()
|
||||||
static float value = 0;
|
static float value = 0;
|
||||||
|
|
||||||
if (firstrun) {
|
if (firstrun) {
|
||||||
jmethodID getDensity = jnienv->GetMethodID(nativeActivity,
|
jmethodID getDensity = jnienv->GetMethodID(activityClass,
|
||||||
"getDensity", "()F");
|
"getDensity", "()F");
|
||||||
|
|
||||||
FATAL_ERROR_IF(getDensity == nullptr,
|
FATAL_ERROR_IF(getDensity == nullptr,
|
||||||
"porting::getDisplayDensity unable to find Java getDensity method");
|
"porting::getDisplayDensity unable to find Java getDensity method");
|
||||||
|
|
||||||
value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity);
|
value = jnienv->CallFloatMethod(activity, getDensity);
|
||||||
firstrun = false;
|
firstrun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,22 +275,22 @@ v2u32 getDisplaySize()
|
||||||
static v2u32 retval;
|
static v2u32 retval;
|
||||||
|
|
||||||
if (firstrun) {
|
if (firstrun) {
|
||||||
jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity,
|
jmethodID getDisplayWidth = jnienv->GetMethodID(activityClass,
|
||||||
"getDisplayWidth", "()I");
|
"getDisplayWidth", "()I");
|
||||||
|
|
||||||
FATAL_ERROR_IF(getDisplayWidth == nullptr,
|
FATAL_ERROR_IF(getDisplayWidth == nullptr,
|
||||||
"porting::getDisplayWidth unable to find Java getDisplayWidth method");
|
"porting::getDisplayWidth unable to find Java getDisplayWidth method");
|
||||||
|
|
||||||
retval.X = jnienv->CallIntMethod(app_global->activity->clazz,
|
retval.X = jnienv->CallIntMethod(activity,
|
||||||
getDisplayWidth);
|
getDisplayWidth);
|
||||||
|
|
||||||
jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity,
|
jmethodID getDisplayHeight = jnienv->GetMethodID(activityClass,
|
||||||
"getDisplayHeight", "()I");
|
"getDisplayHeight", "()I");
|
||||||
|
|
||||||
FATAL_ERROR_IF(getDisplayHeight == nullptr,
|
FATAL_ERROR_IF(getDisplayHeight == nullptr,
|
||||||
"porting::getDisplayHeight unable to find Java getDisplayHeight method");
|
"porting::getDisplayHeight unable to find Java getDisplayHeight method");
|
||||||
|
|
||||||
retval.Y = jnienv->CallIntMethod(app_global->activity->clazz,
|
retval.Y = jnienv->CallIntMethod(activity,
|
||||||
getDisplayHeight);
|
getDisplayHeight);
|
||||||
|
|
||||||
firstrun = false;
|
firstrun = false;
|
||||||
|
@ -332,16 +301,29 @@ v2u32 getDisplaySize()
|
||||||
|
|
||||||
std::string getLanguageAndroid()
|
std::string getLanguageAndroid()
|
||||||
{
|
{
|
||||||
jmethodID getLanguage = jnienv->GetMethodID(nativeActivity,
|
jmethodID getLanguage = jnienv->GetMethodID(activityClass,
|
||||||
"getLanguage", "()Ljava/lang/String;");
|
"getLanguage", "()Ljava/lang/String;");
|
||||||
|
|
||||||
FATAL_ERROR_IF(getLanguage == nullptr,
|
FATAL_ERROR_IF(getLanguage == nullptr,
|
||||||
"porting::getLanguageAndroid unable to find Java getLanguage method");
|
"porting::getLanguageAndroid unable to find Java getLanguage method");
|
||||||
|
|
||||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
|
jobject result = jnienv->CallObjectMethod(activity,
|
||||||
getLanguage);
|
getLanguage);
|
||||||
return readJavaString((jstring) result);
|
return readJavaString((jstring) result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasPhysicalKeyboardAndroid()
|
||||||
|
{
|
||||||
|
jmethodID hasPhysicalKeyboard = jnienv->GetMethodID(activityClass,
|
||||||
|
"hasPhysicalKeyboard", "()Z");
|
||||||
|
|
||||||
|
FATAL_ERROR_IF(hasPhysicalKeyboard == nullptr,
|
||||||
|
"porting::hasPhysicalKeyboardAndroid unable to find Java hasPhysicalKeyboard method");
|
||||||
|
|
||||||
|
jboolean result = jnienv->CallBooleanMethod(activity,
|
||||||
|
hasPhysicalKeyboard);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // ndef SERVER
|
#endif // ndef SERVER
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,21 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#error This header has to be included on Android port only!
|
#error This header has to be included on Android port only!
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#include "irrlichttypes_bloated.h"
|
#include "irrlichttypes_bloated.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace porting {
|
namespace porting {
|
||||||
// Java app
|
|
||||||
extern android_app *app_global;
|
|
||||||
|
|
||||||
// Java <-> C++ interaction interface
|
|
||||||
extern JNIEnv *jnienv;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a text input dialog in Java
|
* Show a text input dialog in Java
|
||||||
* @param hint Hint to be shown
|
* @param hint Hint to be shown
|
||||||
|
@ -105,6 +94,9 @@ std::string getInputDialogMessage();
|
||||||
*/
|
*/
|
||||||
int getInputDialogSelection();
|
int getInputDialogSelection();
|
||||||
|
|
||||||
|
|
||||||
|
bool hasPhysicalKeyboardAndroid();
|
||||||
|
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
float getDisplayDensity();
|
float getDisplayDensity();
|
||||||
v2u32 getDisplaySize();
|
v2u32 getDisplaySize();
|
||||||
|
|
|
@ -156,17 +156,24 @@ void read_item_definition(lua_State* L, int index,
|
||||||
|
|
||||||
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical);
|
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical);
|
||||||
|
|
||||||
|
TouchInteraction &inter = def.touch_interaction;
|
||||||
lua_getfield(L, index, "touch_interaction");
|
lua_getfield(L, index, "touch_interaction");
|
||||||
if (!lua_isnil(L, -1)) {
|
if (lua_istable(L, -1)) {
|
||||||
luaL_checktype(L, -1, LUA_TTABLE);
|
|
||||||
|
|
||||||
TouchInteraction &inter = def.touch_interaction;
|
|
||||||
inter.pointed_nothing = (TouchInteractionMode)getenumfield(L, -1, "pointed_nothing",
|
inter.pointed_nothing = (TouchInteractionMode)getenumfield(L, -1, "pointed_nothing",
|
||||||
es_TouchInteractionMode, inter.pointed_nothing);
|
es_TouchInteractionMode, inter.pointed_nothing);
|
||||||
inter.pointed_node = (TouchInteractionMode)getenumfield(L, -1, "pointed_node",
|
inter.pointed_node = (TouchInteractionMode)getenumfield(L, -1, "pointed_node",
|
||||||
es_TouchInteractionMode, inter.pointed_node);
|
es_TouchInteractionMode, inter.pointed_node);
|
||||||
inter.pointed_object = (TouchInteractionMode)getenumfield(L, -1, "pointed_object",
|
inter.pointed_object = (TouchInteractionMode)getenumfield(L, -1, "pointed_object",
|
||||||
es_TouchInteractionMode, inter.pointed_object);
|
es_TouchInteractionMode, inter.pointed_object);
|
||||||
|
} else if (lua_isstring(L, -1)) {
|
||||||
|
int value;
|
||||||
|
if (string_to_enum(es_TouchInteractionMode, value, lua_tostring(L, -1))) {
|
||||||
|
inter.pointed_nothing = (TouchInteractionMode)value;
|
||||||
|
inter.pointed_node = (TouchInteractionMode)value;
|
||||||
|
inter.pointed_object = (TouchInteractionMode)value;
|
||||||
|
}
|
||||||
|
} else if (!lua_isnil(L, -1)) {
|
||||||
|
throw LuaError("invalid type for 'touch_interaction'");
|
||||||
}
|
}
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ extern "C" {
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
#include "log.h"
|
||||||
#include "common/c_converter.h"
|
#include "common/c_converter.h"
|
||||||
#include "common/c_internal.h"
|
#include "common/c_internal.h"
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue