Merge branch 'master' into Eyecandy-Effects

This commit is contained in:
GefullteTaubenbrust2 2024-04-09 19:46:19 +02:00 committed by GitHub
commit 65d9fcdd49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
116 changed files with 11792 additions and 6762 deletions

3
.gitattributes vendored
View File

@ -1,2 +1,5 @@
# Forces all files which git considers text files to use LF line endings
* text=auto eol=lf
*.cpp diff=cpp
*.h diff=cpp

View File

@ -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)) {

View File

@ -20,7 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package net.minetest.minetest;
import android.app.NativeActivity;
import org.libsdl.app.SDLActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -32,6 +33,7 @@ import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.content.res.Configuration;
import androidx.annotation.Keep;
import androidx.appcompat.app.AlertDialog;
@ -45,12 +47,29 @@ import java.util.Objects;
// This annotation prevents the minifier/Proguard from mangling them.
@Keep
@SuppressWarnings("unused")
public class GameActivity extends NativeActivity {
static {
System.loadLibrary("c++_shared");
System.loadLibrary("minetest");
public class GameActivity extends SDLActivity {
@Override
protected String getMainSharedObject() {
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 DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
@ -59,32 +78,6 @@ public class GameActivity extends NativeActivity {
private String messageReturnValue = "";
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();
@Override
@ -96,11 +89,6 @@ public class GameActivity extends NativeActivity {
saveSettings();
}
@Override
public void onBackPressed() {
// Ignore the back press so Minetest can handle it
}
public void showTextInputDialog(String hint, String current, int editType) {
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
}
@ -265,4 +253,8 @@ public class GameActivity extends NativeActivity {
return langCode;
}
public boolean hasPhysicalKeyboard() {
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}
}
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -490,6 +490,9 @@ end
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
t[#t + 1] = other[i]
end

View File

@ -39,6 +39,7 @@ core.features = {
dynamic_add_media_filepath = true,
lsystem_decoration_type = true,
item_meta_range = true,
node_interaction_actor = true,
}
function core.has_feature(arg)

View File

@ -26,7 +26,15 @@ do
core.print = nil -- don't pollute our namespace
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
-- Load other files

View File

@ -18,6 +18,26 @@
-- Global menu data
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 min_supp_proto, max_supp_proto
@ -27,6 +47,16 @@ function common_update_cached_supp_proto()
end
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
local function render_client_count(n)
@ -140,11 +170,6 @@ function render_serverlist_row(spec)
return table.concat(details, ",")
end
---------------------------------------------------------------------------------
os.tmpname = function()
error('do not use') -- instead use core.get_temp_path()
end
--------------------------------------------------------------------------------
function menu_render_worldlist()
local retval = {}

View File

@ -98,15 +98,12 @@ local function download_and_extract(param)
local tempfolder = core.get_temp_path()
if tempfolder ~= "" then
tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
if not core.extract_zip(filename, tempfolder) then
tempfolder = nil
end
else
tempfolder = nil
end
os.remove(filename)
if not tempfolder then
if not tempfolder or tempfolder == "" then
return {
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
}

View File

@ -26,13 +26,23 @@ if not core.get_http_api then
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 latest_releases
do
local tmp = core.get_once("cdb_latest_releases")
if tmp then
latest_releases = core.deserialize(tmp, true)
has_fetched = latest_releases ~= nil
if check_cache_age("cdb_updates_last_checked", 3 * 3600) then
local f = io.open(cache_file_path, "r")
local data = ""
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
@ -97,7 +107,8 @@ local function fetch()
return
end
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
local maintab = ui.find_by_name("maintab")

View File

@ -15,15 +15,30 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--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()
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
end
local games = core.get_games()
for _, game in ipairs(games) do
if game.id == "minetest" then
core.settings:set_bool("no_mtg_notification", true)
cache_settings:set_bool(SETTING_NAME, true)
return
end
end
@ -37,7 +52,7 @@ function check_reinstall_mtg()
end
end
if not mtg_world_found then
core.settings:set_bool("no_mtg_notification", true)
cache_settings:set_bool(SETTING_NAME, true)
return
end
@ -87,7 +102,7 @@ local function buttonhandler(this, fields)
end
if fields.dismiss then
core.settings:set_bool("no_mtg_notification", true)
cache_settings:set_bool("no_mtg_notification", true)
this:delete()
return true
end

View File

@ -51,12 +51,13 @@ end
local function version_info_buttonhandler(this, fields)
if fields.version_check_remind then
-- 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()
return true
end
if fields.version_check_never then
core.settings:set("update_last_checked", "disabled")
-- clear checked URL
core.settings:set("update_information_url", "")
this:delete()
return true
end
@ -116,7 +117,7 @@ local function on_version_info_received(json)
return
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)
local new_number = type(json.latest) == "table" and json.latest.version_code
@ -135,7 +136,7 @@ local function on_version_info_received(json)
return
end
core.settings:set("update_last_known", tostring(new_number))
cache_settings:set("update_last_known", tostring(new_number))
-- Show version info dialog (once)
maintab:hide()
@ -149,20 +150,20 @@ end
function check_new_version()
local url = core.settings:get("update_information_url")
if core.settings:get("update_last_checked") == "disabled" or
url == "" then
if url == "" then
-- Never show any updates
return
end
local time_now = os.time()
local time_checked = tonumber(core.settings:get("update_last_checked")) or 0
if time_now - time_checked < 2 * 24 * 3600 then
-- Check interval of 2 entire days
-- every 2 days
if check_cache_age("update_last_checked", 2 * 24 * 3600) then
return
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)
local http = core.get_http_api()

View File

@ -28,8 +28,6 @@ local basepath = core.get_builtin_path()
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
dofile(menupath .. DIR_DELIM .. "misc.lua")
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")

View File

@ -1,6 +0,0 @@
-- old non-method sound function
function core.sound_stop(handle, ...)
return handle:stop(...)
end

View File

@ -17,7 +17,7 @@
serverlistmgr = {
-- continent code we detected for ourselves
my_continent = core.get_once("continent"),
my_continent = nil,
-- list of locally favorites servers
favorites = nil,
@ -26,6 +26,15 @@ serverlistmgr = {
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
-- 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
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
local s1 = Normalizer:new()
local s2 = Normalizer:new()
@ -90,28 +100,58 @@ local function order_server_list(list)
s1 = s1:calc()
s2 = s2:calc()
-- make a shallow copy and pre-calculate ordering
local res, order = {}, {}
for i = 1, #list do
local fav = list[i]
res[i] = fav
local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
order[fav] = n
-- pre-calculate ordering
local order = {}
for _, fav in ipairs(list) do
order[fav] = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
end
-- now sort the list
table.sort(res, function(fav1, fav2)
table.sort(list, function(fav1, fav2)
return order[fav1] > order[fav2]
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
local public_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()
if not serverlistmgr.servers then
serverlistmgr.servers = {{
@ -129,37 +169,23 @@ function serverlistmgr.sync()
return
end
-- only fetched once per MT instance
if not serverlistmgr.my_continent and not geoip_downloading then
geoip_downloading = true
core.handle_async(
function(param)
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)
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
core.handle_async(fetch_geoip, nil, function(result)
geoip_downloading = false
if not result then
return
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
if public_downloading then
@ -167,6 +193,7 @@ function serverlistmgr.sync()
end
public_downloading = true
-- note: this isn't cached because it's way too dynamic
core.handle_async(
function(param)
local http = core.get_http_api()

View File

@ -1,5 +1,6 @@
_G.core = {get_once = function(_) end}
_G.core = {}
_G.unpack = table.unpack
_G.check_cache_age = function() return false end
_G.serverlistmgr = {}
dofile("builtin/common/vector.lua")

View File

@ -114,7 +114,11 @@ always_fly_fast (Always fly fast) bool true
# the place button.
#
# 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.
autojump (Automatic jumping) bool false
@ -149,20 +153,23 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen]
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
#
# Requires: !android
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.
#
# Requires: touchscreen_gui
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.
# 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
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]
@ -822,6 +842,7 @@ serverlist_url (Serverlist URL) string servers.minetest.net
enable_split_login_register (Enable split login/register) bool true
# 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
[*Server]
@ -2337,20 +2358,6 @@ show_advanced (Show advanced settings) bool false
# Changing this setting requires a restart.
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.
keymap_forward (Forward key) key KEY_KEY_W

1
client/shaders/Irrlicht Symbolic link
View File

@ -0,0 +1 @@
../../irr/media/Shaders

View File

@ -1,7 +1,7 @@
uniform vec4 fogColor;
uniform lowp vec4 fogColor;
uniform float fogDistance;
uniform float fogShadingParameter;
varying vec3 eyeVec;
varying highp vec3 eyeVec;
varying lowp vec4 varColor;

View File

@ -1,8 +1,8 @@
uniform vec4 emissiveColor;
uniform lowp vec4 emissiveColor;
varying lowp vec4 varColor;
varying vec3 eyeVec;
varying highp vec3 eyeVec;
void main(void)
{

View File

@ -9,13 +9,12 @@ uniform sampler2D baseTexture;
uniform vec2 texelSize0;
uniform vec3 dayLight;
uniform vec4 fogColor;
uniform lowp vec4 fogColor;
uniform float fogDistance;
uniform float fogShadingParameter;
uniform vec3 eyePosition;
// The cameraOffset is the current center of the visible world.
uniform vec3 cameraOffset;
uniform highp vec3 cameraOffset;
uniform float animationTimer;
#ifdef ENABLE_DYNAMIC_SHADOWS
// shadow texture
@ -53,11 +52,8 @@ varying mediump vec2 varTexCoord;
#else
centroid varying vec2 varTexCoord;
#endif
varying vec3 eyeVec;
varying highp vec3 eyeVec;
varying float nightRatio;
varying vec3 tsEyeVec;
varying vec3 lightVec;
varying vec3 tsLightVec;
varying vec3 viewVec;

View File

@ -1,10 +1,9 @@
uniform mat4 mWorld;
// Color of the light emitted by the sun.
uniform vec3 dayLight;
uniform vec3 eyePosition;
// The cameraOffset is the current center of the visible world.
uniform vec3 cameraOffset;
uniform highp vec3 cameraOffset;
uniform float animationTimer;
varying vec3 vNormal;
@ -45,7 +44,7 @@ centroid varying vec2 varTexCoord;
varying float area_enable_parallax;
varying vec3 viewVec;
varying vec3 eyeVec;
varying highp vec3 eyeVec;
varying float nightRatio;
// Color of the light emitted by the light sources.
uniform vec3 artificialLight;

View File

@ -1,13 +1,12 @@
uniform sampler2D baseTexture;
uniform vec3 dayLight;
uniform vec4 fogColor;
uniform lowp vec4 fogColor;
uniform float fogDistance;
uniform float fogShadingParameter;
uniform vec3 eyePosition;
// The cameraOffset is the current center of the visible world.
uniform vec3 cameraOffset;
uniform highp vec3 cameraOffset;
uniform float animationTimer;
#ifdef ENABLE_DYNAMIC_SHADOWS
// shadow texture
@ -45,7 +44,7 @@ varying mediump vec2 varTexCoord;
#else
centroid varying vec2 varTexCoord;
#endif
varying vec3 eyeVec;
varying highp vec3 eyeVec;
varying float nightRatio;
varying float vIDiff;

View File

@ -1,10 +1,7 @@
uniform mat4 mWorld;
uniform vec3 dayLight;
uniform vec3 eyePosition;
uniform float animationTimer;
uniform vec4 emissiveColor;
uniform vec3 cameraOffset;
uniform lowp vec4 emissiveColor;
varying vec3 vNormal;
varying vec3 vPosition;
@ -33,7 +30,7 @@ centroid varying vec2 varTexCoord;
varying float perspective_factor;
#endif
varying vec3 eyeVec;
varying highp vec3 eyeVec;
varying float nightRatio;
// Color of the light emitted by the light sources.
uniform vec3 artificialLight;

View File

@ -1,4 +1,4 @@
uniform vec4 emissiveColor;
uniform lowp vec4 emissiveColor;
void main(void)
{

View File

@ -25,3 +25,5 @@ set(VORBIS_LIBRARY ${DEPS}/Vorbis/libvorbis.a)
set(VORBISFILE_LIBRARY ${DEPS}/Vorbis/libvorbisfile.a)
set(ZSTD_INCLUDE_DIR ${DEPS}/Zstd/include)
set(ZSTD_LIBRARY ${DEPS}/Zstd/libzstd.a)
set(SDL2_INCLUDE_DIRS ${DEPS}/SDL2/include/SDL2)
set(SDL2_LIBRARIES ${DEPS}/SDL2/libSDL2.a)

View File

@ -659,8 +659,9 @@ The mask is applied using binary AND.
#### `[sheet:<w>x<h>:<x>,<y>`
Retrieves a tile at position x,y from the base image
which it assumes to be a tilesheet with dimensions w,h.
Retrieves a tile at position x, y (in tiles, 0-indexed)
from the base image, which it assumes to be a tilesheet
with dimensions w, h (in tiles).
#### `[colorize:<color>:<ratio>`
@ -5431,6 +5432,9 @@ Utilities
lsystem_decoration_type = true,
-- Overrideable pointing range using the itemstack meta key `"range"` (5.9.0)
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`
* Currently it's the same as `math.floor(param1 / 16)`, except that it
ensures compatibility.
* `minetest.place_node(pos, node)`
* `minetest.place_node(pos, node[, placer])`
* 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
* `digger`: The ObjectRef that digs the node (optional)
* 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
* `puncher`: The ObjectRef that punches the node (optional)
* `minetest.spawn_falling_node(pos)`
* Change node into falling node
* 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
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
===============
@ -7944,8 +7984,12 @@ child will follow movement and rotation of that bone.
* Fourth column: subject looking to the right
* Fifth column: subject viewed from above
* 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()`: returns the object's associated luaentity table
* `get_luaentity()`:
* 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)
@ -8666,7 +8710,8 @@ Player properties need to be saved manually.
-- "mesh" uses the defined mesh model.
-- "wielditem" is used for dropped items.
-- (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
-- that, otherwise:
-- 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.
-- "sprite" uses 1 texture.
-- "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)
-- Deprecated usage of "wielditem" expects 'textures = {itemname}' (see 'visual' above).
colors = {},
-- 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
-- upon digging. Server will always update with actual result shortly.
touch_interaction = {
-- Only affects touchscreen clients.
-- Defines the meaning of short and long taps with the item in hand.
-- The fields in this table have two valid values:
-- * "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",
touch_interaction = <TouchInteractionMode> OR {
pointed_nothing = <TouchInteractionMode>,
pointed_node = <TouchInteractionMode>,
pointed_object = <TouchInteractionMode>,
},
-- 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 = {
-- Definition of item sounds to be played at various events.

View File

@ -55,10 +55,6 @@ Functions
* Android only. Shares file using the share popup
* `core.get_version()` (possible in async calls)
* 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)
* `core.get_cache_path()` -> path of cache
* `core.get_temp_path([param])` (possible in async calls)
* `param`=true: returns path to a temporary file
otherwise: returns path to the temporary folder
* `param`=true: returns path to a newly created temporary file
* otherwise: returns path to a newly created temporary folder
HTTP Requests

View File

@ -79,9 +79,6 @@ enum EEVENT_TYPE
*/
EET_USER_EVENT,
//! Pass on raw events from the OS
EET_SYSTEM_EVENT,
//! Application state events like a resume, pause etc.
EET_APPLICATION_EVENT,
@ -187,17 +184,6 @@ enum ETOUCH_INPUT_EVENT
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)
enum EAPPLICATION_EVENT_TYPE
{
@ -528,25 +514,6 @@ struct SEvent
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
struct SApplicationEvent
{
@ -567,7 +534,6 @@ struct SEvent
struct SJoystickEvent JoystickEvent;
struct SLogEvent LogEvent;
struct SUserEvent UserEvent;
struct SSystemEvent SystemEvent;
struct SApplicationEvent ApplicationEvent;
};
};

View File

@ -180,10 +180,7 @@ public:
virtual bool isFullscreen() const = 0;
//! Checks if the window could possibly be visible.
//! Currently, this only returns false when the activity is stopped on
//! 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.
/** If this returns false, you should not do any rendering. */
virtual bool isWindowVisible() const { return true; };
//! Get the current color format of the window

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &params,
io::IFileSystem *io, video::IContextManager *contextManager);
IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters &params,
io::IFileSystem *io, video::IContextManager *contextManager);
}
}
namespace irr
{
CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters &param) :
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

View File

@ -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 &param);
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

View File

@ -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

View File

@ -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

View File

@ -10,10 +10,6 @@
#include "irrArray.h"
#include "os.h"
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
#include <android/native_activity.h>
#endif
namespace irr
{
namespace video
@ -55,9 +51,6 @@ bool CEGLManager::initialize(const SIrrlichtCreationParameters &params, const SE
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window;
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_)
EglWindow = (NativeWindowType)Data.OpenGLFB.Window;
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@ -119,10 +112,6 @@ bool CEGLManager::generateSurface()
// at this time only Android support this feature.
// this needs an update method instead!
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
#endif
#if defined(_IRR_EMSCRIPTEN_PLATFORM_)
// 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.
@ -136,13 +125,6 @@ bool CEGLManager::generateSurface()
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.
EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0);

View File

@ -18,14 +18,20 @@ namespace video
// PNG function for error handling
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);
}
// PNG function for warning handling
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
@ -88,7 +94,7 @@ IImage *CImageLoaderPng::loadImage(io::IReadFile *file) const
// Allocate the png read struct
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) {
os::Printer::log("LOAD PNG: Internal PNG create read struct failure", file->getFileName(), ELL_ERROR);
return 0;

View File

@ -25,14 +25,20 @@ IImageWriter *createImageWriterPNG()
// PNG function for error handling
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);
}
// PNG function for warning handling
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
@ -66,7 +72,7 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param)
// Allocate the png write struct
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) {
os::Printer::log("PNGWriter: Internal PNG create write struct failure", file->getFileName(), ELL_ERROR);
return false;

View File

@ -249,16 +249,34 @@ void CIrrDeviceSDL::resetReceiveTextInputEvents()
//! constructor
CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &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),
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
setDebugName("CIrrDeviceSDL");
#endif
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;
if (CreationParams.DriverType != video::EDT_NULL)
flags |= SDL_INIT_VIDEO;
@ -273,11 +291,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &param) :
}
}
// 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
createKeyMap();
@ -321,19 +334,21 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &param) :
//! destructor
CIrrDeviceSDL::~CIrrDeviceSDL()
{
if (--SDLDeviceInstances == 0) {
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
const u32 numJoysticks = Joysticks.size();
for (u32 i = 0; i < numJoysticks; ++i)
SDL_JoystickClose(Joysticks[i]);
const u32 numJoysticks = Joysticks.size();
for (u32 i = 0; i < numJoysticks; ++i)
SDL_JoystickClose(Joysticks[i]);
#endif
if (Window) {
SDL_GL_MakeCurrent(Window, NULL);
SDL_GL_DeleteContext(Context);
SDL_DestroyWindow(Window);
}
SDL_Quit();
if (Window && Context) {
SDL_GL_MakeCurrent(Window, NULL);
SDL_GL_DeleteContext(Context);
}
if (Window) {
SDL_DestroyWindow(Window);
}
if (--SDLDeviceInstances == 0) {
SDL_Quit();
os::Printer::log("Quit SDL", ELL_INFORMATION);
}
}
@ -367,6 +382,76 @@ void CIrrDeviceSDL::logAttributes()
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) {
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
SDL_Flags |= SDL_WINDOW_FULLSCREEN;
@ -419,9 +504,6 @@ bool CIrrDeviceSDL::createWindow()
return true;
#else // !_IRR_EMSCRIPTEN_PLATFORM_
if (Close)
return false;
switch (CreationParams.DriverType) {
case video::EDT_OPENGL:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
@ -446,9 +528,13 @@ bool CIrrDeviceSDL::createWindow()
default:;
}
/*
Makes context creation fail on some Android devices.
See discussion in https://github.com/minetest/minetest/pull/14498.
#ifdef _DEBUG
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
#endif
*/
if (CreationParams.Bits == 16) {
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
@ -468,46 +554,33 @@ bool CIrrDeviceSDL::createWindow()
if (CreationParams.AntiAlias > 1) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
}
if (!Window)
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
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);
}
} else {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
if (!Window && CreationParams.Doublebuffer) {
// 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);
}
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
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;
}
Context = SDL_GL_CreateContext(Window);
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);
Window = nullptr;
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;
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
}
@ -619,7 +692,17 @@ bool CIrrDeviceSDL::run()
}
#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:
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
@ -770,6 +853,20 @@ bool CIrrDeviceSDL::run()
postEventFromUser(irrevent);
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:
break;
} // end switch
@ -996,11 +1093,6 @@ void CIrrDeviceSDL::setResizable(bool resize)
return;
#else // !_IRR_EMSCRIPTEN_PLATFORM_
if (resize != Resizable) {
if (resize)
SDL_Flags |= SDL_WINDOW_RESIZABLE;
else
SDL_Flags &= ~SDL_WINDOW_RESIZABLE;
if (Window) {
SDL_SetWindowResizable(Window, (SDL_bool)resize);
}
@ -1051,6 +1143,11 @@ bool CIrrDeviceSDL::isFullscreen() const
#endif
}
bool CIrrDeviceSDL::isWindowVisible() const
{
return !IsInBackground;
}
//! returns if window is active. if not, nothing need to be drawn
bool CIrrDeviceSDL::isWindowActive() const
{
@ -1109,6 +1206,8 @@ void CIrrDeviceSDL::createKeyMap()
// buttons missing
KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_CANCEL));
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));

View File

@ -86,6 +86,9 @@ public:
/** \return True if window is fullscreen. */
bool isFullscreen() const override;
//! Checks if the window could possibly be visible.
bool isWindowVisible() const override;
//! Get the position of this window on screen
core::position2di getWindowPosition() override;
@ -277,13 +280,13 @@ private:
void createDriver();
bool createWindow();
bool createWindowWithContext();
void createKeyMap();
void logAttributes();
SDL_GLContext Context;
SDL_Window *Window;
int SDL_Flags;
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
core::array<SDL_Joystick *> Joysticks;
#endif
@ -319,6 +322,7 @@ private:
SDL_SysWMinfo Info;
s32 CurrentTouchCount;
bool IsInBackground;
};
} // end namespace irr

View File

@ -1,4 +1,4 @@
if(NOT ANDROID AND NOT APPLE)
if(NOT APPLE)
set(DEFAULT_SDL2 ON)
endif()
@ -77,10 +77,9 @@ elseif(APPLE)
set(DEVICE "OSX")
elseif(ANDROID)
add_definitions(-D_IRR_ANDROID_PLATFORM_)
if(USE_SDL2)
message(FATAL_ERROR "SDL2 device is not (yet) supported on Android")
if(NOT USE_SDL2)
message(FATAL_ERROR "The Android build requires SDL2")
endif()
set(DEVICE "ANDROID")
elseif(EMSCRIPTEN)
add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_)
set(LINUX_PLATFORM TRUE)
@ -131,7 +130,10 @@ endif()
# OpenGL
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()
set(ENABLE_OPENGL3 FALSE)
endif()
@ -142,13 +144,10 @@ else()
option(ENABLE_OPENGL "Enable OpenGL" TRUE)
endif()
if(EMSCRIPTEN OR APPLE)
if(USE_SDL2 OR EMSCRIPTEN OR APPLE)
set(ENABLE_GLES1 FALSE)
else()
if(ANDROID)
set(DEFAULT_GLES1 TRUE)
endif()
option(ENABLE_GLES1 "Enable OpenGL ES" ${DEFAULT_GLES1})
option(ENABLE_GLES1 "Enable OpenGL ES" FALSE)
endif()
if(APPLE)
@ -188,19 +187,16 @@ if(ENABLE_OPENGL3)
endif()
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_)
set(OPENGLES_DIRECT_LINK TRUE)
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$")
if(DEVICE MATCHES "^(WINDOWS|X11)$")
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
endif()
endif()
if(ENABLE_GLES2)
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_)
endif()
endif()
@ -248,11 +244,15 @@ endif()
if(ENABLE_GLES2)
find_package(OpenGLES2 REQUIRED)
endif()
if(ENABLE_OPENGL OR ENABLE_OPENGL3)
if(ENABLE_OPENGL)
find_package(OpenGL REQUIRED)
endif()
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}")
# unfortunately older SDL does not provide its version to cmake, so check header.
@ -323,7 +323,6 @@ set(link_includes
${OPENGLES2_INCLUDE_DIR}
${EGL_INCLUDE_DIR}
"$<$<PLATFORM_ID:Android>:${ANDROID_NDK}/sources/android/native_app_glue>"
"$<$<BOOL:${USE_X11}>:${X11_INCLUDE_DIR}>"
)
@ -453,14 +452,7 @@ if(ENABLE_OPENGL3)
target_compile_definitions(IRROTHEROBJ PRIVATE ENABLE_OPENGL3)
endif()
if(ANDROID)
target_sources(IRROTHEROBJ PRIVATE
Android/CIrrDeviceAndroid.cpp
Android/CAndroidAssetReader.cpp
Android/CAndroidAssetFileArchive.cpp
Android/CKeyEventWrapper.cpp
)
elseif(APPLE)
if(APPLE)
# Build all IRROTHEROBJ sources as objc++, including the .cpp's
set_target_properties(IRROTHEROBJ PROPERTIES COMPILE_OPTIONS "-xobjective-c++")
target_sources(IRROTHEROBJ PRIVATE
@ -535,7 +527,8 @@ target_link_libraries(IrrlichtMt PRIVATE
"$<$<BOOL:${OPENGLES_DIRECT_LINK}>:${OPENGLES_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}
${IOKIT_LIB}
"$<$<PLATFORM_ID:Windows>:gdi32>"

View File

@ -983,6 +983,11 @@ IImage *CNullDriver::createImageFromFile(io::IReadFile *file)
continue;
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))
return image;
}

View File

@ -19,10 +19,6 @@
#include "CImage.h"
#include "os.h"
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "android_native_app_glue.h"
#endif
namespace irr
{
namespace video

View File

@ -12,7 +12,7 @@
#include "SMaterial.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>
#else
#include <GLES/egl.h>

View File

@ -28,10 +28,6 @@ static const char *const copyright = "Irrlicht Engine (c) 2002-2017 Nikolaus Geb
#include "CIrrDeviceSDL.h"
#endif
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "Android/CIrrDeviceAndroid.h"
#endif
namespace irr
{
//! stub for calling createDeviceEx
@ -74,11 +70,6 @@ extern "C" IRRLICHT_API IrrlichtDevice *IRRCALLCONV createDeviceEx(const SIrrlic
dev = new CIrrDeviceLinux(params);
#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_
if (params.DeviceType == EIDT_SDL || (!dev && params.DeviceType == EIDT_BEST))
dev = new CIrrDeviceSDL(params);

View File

@ -21,10 +21,6 @@
#include "CImage.h"
#include "os.h"
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "android_native_app_glue.h"
#endif
#include "mt_opengl.h"
namespace irr

View File

@ -1,66 +1,66 @@
#include <windows.h>
#include <winuser.h>
#include <commctrl.h>
#include <richedit.h>
#ifndef USE_CMAKE_CONFIG_H
#define USE_CMAKE_CONFIG_H
#endif
#include "config.h"
#undef USE_CMAKE_CONFIG_H
#if RUN_IN_PLACE
#define BUILDMODE "RUN_IN_PLACE=1"
#else
#define BUILDMODE "RUN_IN_PLACE=0"
#endif
#ifdef __MINGW32__
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
#endif
LANGUAGE 0, SUBLANG_NEUTRAL
130 ICON "minetest-icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
1 VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
FILEFLAGSMASK 0x3fL
#ifndef NDEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", ""
VALUE "CompanyName", PROJECT_NAME_C " community"
VALUE "FileDescription", PROJECT_NAME_C " engine"
VALUE "FileVersion", VERSION_STRING
VALUE "InternalName", PROJECT_NAME
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
VALUE "OriginalFilename", "minetest.exe"
VALUE "PrivateBuild", VERSION_EXTRA
VALUE "ProductName", PROJECT_NAME_C
VALUE "ProductVersion", PRODUCT_VERSION_STRING
VALUE "SpecialBuild", BUILDMODE
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#include <windows.h>
#include <winuser.h>
#include <commctrl.h>
#include <richedit.h>
#ifndef USE_CMAKE_CONFIG_H
#define USE_CMAKE_CONFIG_H
#endif
#include "config.h"
#undef USE_CMAKE_CONFIG_H
#if RUN_IN_PLACE
#define BUILDMODE "RUN_IN_PLACE=1"
#else
#define BUILDMODE "RUN_IN_PLACE=0"
#endif
#ifdef __MINGW32__
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
#endif
LANGUAGE 0, SUBLANG_NEUTRAL
130 ICON "minetest-icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
1 VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
FILEFLAGSMASK 0x3fL
#ifndef NDEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", ""
VALUE "CompanyName", PROJECT_NAME_C " community"
VALUE "FileDescription", PROJECT_NAME_C " engine"
VALUE "FileVersion", VERSION_STRING
VALUE "InternalName", PROJECT_NAME
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
VALUE "OriginalFilename", "minetest.exe"
VALUE "PrivateBuild", VERSION_EXTRA
VALUE "ProductName", PROJECT_NAME_C
VALUE "ProductVersion", PRODUCT_VERSION_STRING
VALUE "SpecialBuild", BUILDMODE
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@ -307,10 +307,6 @@ else()
endif()
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)
endif()
endif()
@ -518,6 +514,9 @@ include_directories(SYSTEM
${GMP_INCLUDE_DIR}
${JSON_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)
@ -562,6 +561,9 @@ if(BUILD_CLIENT)
${LUA_BIT_LIBRARY}
${FREETYPE_LIBRARY}
${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)
set_target_properties(${PROJECT_NAME} PROPERTIES
@ -698,11 +700,13 @@ include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR})
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
# 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.
# (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)
check_symbol_exists(luaopen_string_buffer "lualib.h" HAVE_RECENT_LJ)
if(NOT HAVE_RECENT_LJ)

View File

@ -69,6 +69,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/texturepaths.cpp
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/imagesource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp

View File

@ -799,7 +799,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
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.");
@ -1795,6 +1795,11 @@ float Client::mediaReceiveProgress()
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 {
gui::IGUIEnvironment *guienv;
u64 last_time_ms;

View File

@ -356,6 +356,7 @@ public:
float mediaReceiveProgress();
void drawLoadScreen(const std::wstring &text, float dtime, int percent);
void afterContentReceived();
void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);

View File

@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "clientmedia.h"
#include "gettext.h"
#include "httpfetch.h"
#include "client.h"
#include "filecache.h"
@ -184,6 +185,11 @@ void ClientMediaDownloader::step(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
m_uncached_count = m_files.size();
for (auto &file_it : m_files) {
@ -195,6 +201,13 @@ void ClientMediaDownloader::initialStep(Client *client)
filestatus->received = true;
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);

View File

@ -66,6 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "settings.h"
#include "shader.h"
#include "sky.h"
#include "threading/lambda.h"
#include "translation.h"
#include "util/basic_macros.h"
#include "util/directiontables.h"
@ -386,7 +387,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedVertexShaderSetting<float, 3> m_eye_position_vertex{"eyePosition"};
CachedPixelShaderSetting<float, 3> m_minimap_yaw{"yawVec"};
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_texture1{"texture1"};
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_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()) {
v3f minimap_yaw = m_client->getMinimap()->getYawVec();
m_minimap_yaw.set(minimap_yaw, services);
@ -945,6 +942,7 @@ private:
f32 m_cache_mouse_sensitivity;
f32 m_cache_joystick_frustum_sensitivity;
f32 m_repeat_place_time;
f32 m_repeat_dig_time;
f32 m_cache_cam_smoothing;
bool m_invert_mouse;
@ -991,6 +989,8 @@ Game::Game() :
&settingChangedCallback, this);
g_settings->registerChangedCallback("repeat_place_time",
&settingChangedCallback, this);
g_settings->registerChangedCallback("repeat_dig_time",
&settingChangedCallback, this);
g_settings->registerChangedCallback("noclip",
&settingChangedCallback, this);
g_settings->registerChangedCallback("free_move",
@ -1023,12 +1023,6 @@ Game::Game() :
Game::~Game()
{
delete client;
delete soundmaker;
sound_manager.reset();
delete server; // deleted first to stop all server threads
delete hud;
delete camera;
delete quicktune;
@ -1057,6 +1051,8 @@ Game::~Game()
&settingChangedCallback, this);
g_settings->deregisterChangedCallback("repeat_place_time",
&settingChangedCallback, this);
g_settings->deregisterChangedCallback("repeat_dig_time",
&settingChangedCallback, this);
g_settings->deregisterChangedCallback("noclip",
&settingChangedCallback, this);
g_settings->deregisterChangedCallback("free_move",
@ -1275,6 +1271,28 @@ void Game::shutdown()
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,
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()
@ -2226,9 +2266,11 @@ void Game::openConsole(float scale, const wchar_t *line)
assert(scale > 0.0f && scale <= 1.0f);
#ifdef __ANDROID__
porting::showTextInputDialog("", "", 2);
m_android_chat_open = true;
#else
if (!porting::hasPhysicalKeyboardAndroid()) {
porting::showTextInputDialog("", "", 2);
m_android_chat_open = true;
} else {
#endif
if (gui_chat_console->isOpenInhibited())
return;
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->replaceAndAddToHistory(line);
}
#ifdef __ANDROID__
} // else
#endif
}
@ -3282,8 +3326,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
if (g_touchscreengui) {
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,
// 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.dig_time_complete / (float)crack_animation_length;
// We don't want a corresponding delay to very time consuming nodes
// and nodes without digging time (e.g. torches) get a fixed delay.
if (runData.nodig_delay_timer > 0.3)
runData.nodig_delay_timer = 0.3;
else if (runData.dig_instantly)
runData.nodig_delay_timer = 0.15;
// Don't add a corresponding delay to very time consuming nodes.
runData.nodig_delay_timer = std::min(runData.nodig_delay_timer, 0.3f);
// Ensure that the delay between breaking nodes
// (dig_time_complete + nodig_delay_timer) is at least the
// 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() &&
client->getScript()->on_dignode(nodepos, n)) {
@ -4323,7 +4371,8 @@ void Game::readSettings()
m_cache_enable_fog = g_settings->getBool("enable_fog");
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_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_free_move = g_settings->getBool("free_move");

View File

@ -44,6 +44,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define OBJECT_CROSSHAIR_LINE_SIZE 8
#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,
Inventory *inventory)
{
@ -52,12 +57,8 @@ Hud::Hud(Client *client, LocalPlayer *player,
this->player = player;
this->inventory = inventory;
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;
readScalingSetting();
g_settings->registerChangedCallback("hud_scaling", setting_changed_callback, this);
for (auto &hbar_color : hbar_colors)
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);
}
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()
{
g_settings->deregisterChangedCallback("hud_scaling", setting_changed_callback, this);
if (m_selection_mesh)
m_selection_mesh->drop();
}
@ -786,9 +799,9 @@ void Hud::drawCrosshair()
{
auto draw_image_crosshair = [this] (video::ITexture *tex) {
core::dimension2di orig_size(tex->getOriginalSize());
core::dimension2di scaled_size(
core::round32(orig_size.Width * m_scale_factor),
core::round32(orig_size.Height * m_scale_factor));
// Integer scaling to avoid artifacts, floor instead of round since too
// small looks better than too large in this case.
core::dimension2di scaled_size = orig_size * std::max(std::floor(m_scale_factor), 1.0f);
core::rect<s32> src_rect(orig_size);
core::position2d pos(m_displaycenter.X - scaled_size.Width / 2,

View File

@ -57,6 +57,7 @@ public:
Hud(Client *client, LocalPlayer *player,
Inventory *inventory);
void readScalingSetting();
~Hud();
enum BlockBoundsMode toggleBlockBounds();

1976
src/client/imagesource.cpp Normal file

File diff suppressed because it is too large Load Diff

84
src/client/imagesource.h Normal file
View File

@ -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;
};

View File

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "particles.h"
#include <cmath>
#include <array>
#include "client.h"
#include "collision.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 "util/numeric.h"
#include "light.h"
#include "localplayer.h"
#include "environment.h"
#include "clientmap.h"
#include "mapnode.h"
#include "nodedef.h"
#include "client.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(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientParticleTexRef &texture,
v2f texpos,
@ -49,14 +56,10 @@ Particle::Particle(
ParticleSpawner *parent,
std::unique_ptr<ClientParticleTexture> owned_texture
) :
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager()),
m_expiration(p.expirationtime),
m_env(env),
m_gamedef(gamedef),
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
m_base_color(color),
m_texture(texture),
m_texpos(texpos),
m_texsize(texsize),
@ -64,102 +67,30 @@ Particle::Particle(
m_velocity(p.vel),
m_acceleration(p.acc),
m_p(p),
m_player(player),
m_base_color(color),
m_color(color),
m_parent(parent),
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) {
case ParticleParamTypes::BlendMode::add:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_ADD;
break;
Particle::~Particle()
{
if (m_buffer)
m_buffer->release(m_index);
}
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;
}
// 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);
bool Particle::attachToBuffer(ParticleBuffer *buffer)
{
auto index_opt = buffer->allocate();
if (index_opt.has_value()) {
m_index = index_opt.value();
m_buffer = buffer;
return true;
}
// Irrlicht stuff
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
updateLight();
// Init model
updateVertices();
return false;
}
void Particle::OnRegisterSceneNode()
{
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)
void Particle::step(float dtime, ClientEnvironment *env)
{
m_time += dtime;
@ -169,10 +100,10 @@ void Particle::step(float dtime)
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
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_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,
m_p.object_collision);
@ -215,7 +146,7 @@ void Particle::step(float dtime)
m_animation_time += dtime;
int frame_length_i = 0;
m_p.animation.determineParams(
m_material.getTexture(0)->getSize(),
m_texture.ref->getSize(),
NULL, &frame_length_i, NULL);
float frame_length = frame_length_i / 1000.0;
while (m_animation_time > frame_length) {
@ -225,23 +156,19 @@ void Particle::step(float dtime)
}
// animate particle alpha in accordance with settings
float alpha = 1.f;
if (m_texture.tex != nullptr)
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
else
m_alpha = 1.f;
alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
// Update lighting
updateLight();
auto col = updateLight(env);
col.setAlpha(255 * alpha);
// Update model
updateVertices();
// Update position -- see #10398
v3s16 camera_offset = m_env->getCameraOffset();
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
updateVertices(env, col);
}
void Particle::updateLight()
video::SColor Particle::updateLight(ClientEnvironment *env)
{
u8 light = 0;
bool pos_ok;
@ -251,32 +178,37 @@ void Particle::updateLight()
floor(m_pos.Y+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)
light = n.getLightBlend(m_env->getDayNightRatio(),
m_gamedef->ndef()->getLightingFlags(n));
light = n.getLightBlend(env->getDayNightRatio(),
env->getGameDef()->ndef()->getLightingFlags(n));
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);
m_color.set(m_alpha*255,
return video::SColor(255,
m_light * m_base_color.getRed() / 255,
m_light * m_base_color.getGreen() / 255,
m_light * m_base_color.getBlue() / 255);
}
void Particle::updateVertices()
void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
{
f32 tx0, tx1, ty0, ty1;
v2f scale;
if (!m_buffer)
return;
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
if (m_texture.tex != nullptr)
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
else
scale = v2f(1.f, 1.f);
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;
v2u32 framesize;
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
@ -297,31 +229,30 @@ void Particle::updateVertices()
auto half = m_p.size * .5f,
hx = half * scale.X,
hy = half * scale.Y;
m_vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, m_color, tx0, ty1);
m_vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, m_color, tx1, ty1);
m_vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, m_color, tx1, ty0);
m_vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, m_color, tx0, ty0);
vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, color, tx0, ty1);
vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, color, tx1, ty1);
vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, color, tx1, ty0);
vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, color, tx0, ty0);
// Update position -- see #10398
auto *player = env->getLocalPlayer();
v3s16 camera_offset = env->getCameraOffset();
// see #10398
// v3s16 camera_offset = m_env->getCameraOffset();
// particle position is now handled by step()
m_box.reset(v3f());
for (video::S3DVertex &vertex : m_vertices) {
for (u16 i = 0; i < 4; i++) {
video::S3DVertex &vertex = vertices[i];
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) /
core::DEGTORAD + 90);
} else {
vertex.Pos.rotateYZBy(m_player->getPitch());
vertex.Pos.rotateXZBy(m_player->getYaw());
vertex.Pos.rotateYZBy(player->getPitch());
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(
IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &params,
u16 attached_id,
@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner(
m_active(0),
m_particlemanager(p_manager),
m_time(0.0f),
m_gamedef(gamedef),
m_player(player),
p(params),
m_texpool(std::move(texpool)),
@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
++m_active;
m_particlemanager->addParticle(std::make_unique<Particle>(
m_gamedef,
m_player,
env,
pp,
texture,
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
*/
@ -639,8 +668,9 @@ ParticleManager::~ParticleManager()
void ParticleManager::step(float dtime)
{
stepParticles (dtime);
stepSpawners (dtime);
stepParticles(dtime);
stepSpawners(dtime);
stepBuffers(dtime);
}
void ParticleManager::stepSpawners(float dtime)
@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime)
assert(parent->hasActive());
parent->decrActive();
}
// remove scene node
p.remove();
// delete
m_particles[i] = std::move(m_particles.back());
m_particles.pop_back();
} else {
p.step(dtime);
p.step(dtime, m_env);
++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()
{
MutexAutoLock lock(m_spawner_list_lock);
MutexAutoLock lock2(m_particle_list_lock);
// clear particle spawners
m_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();
// 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,
@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
addParticleSpawner(event->add_particlespawner.id,
std::make_unique<ParticleSpawner>(
client,
player,
p,
event->add_particlespawner.attached_id,
@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
p.size = oldsize;
if (texture.ref) {
addParticle(std::make_unique<Particle>(client, player, m_env,
addParticle(std::make_unique<Particle>(
p, texture, texpos, texsize, color, nullptr,
std::move(texstore)));
}
@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
);
addParticle(std::make_unique<Particle>(
gamedef,
player,
m_env,
p,
ClientParticleTexRef(ref),
texpos,
@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t 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);
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)
{

View File

@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <iostream>
#include <vector>
#include <unordered_map>
#include "irrlichttypes_extrabloated.h"
#include "localplayer.h"
#include "irr_ptr.h"
#include "../particles.h"
struct ClientEvent;
@ -29,6 +30,10 @@ class ParticleManager;
class ClientEnvironment;
struct MapNode;
struct ContentFeatures;
class LocalPlayer;
class ITextureSource;
class IGameDef;
class Client;
struct ClientParticleTexture
{
@ -38,9 +43,7 @@ struct ClientParticleTexture
video::ITexture *ref = nullptr;
ClientParticleTexture() = default;
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t):
tex(p),
ref(t->getTextureForMesh(p.string)) {};
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc);
};
struct ClientParticleTexRef
@ -61,14 +64,12 @@ struct ClientParticleTexRef
};
class ParticleSpawner;
class ParticleBuffer;
class Particle : public scene::ISceneNode
class Particle
{
public:
Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientParticleTexRef &texture,
v2f texpos,
@ -78,61 +79,46 @@ public:
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
);
virtual const aabb3f &getBoundingBox() const
{
return m_box;
}
~Particle();
virtual u32 getMaterialCount() const
{
return 1;
}
DISABLE_CLASS_COPY(Particle)
virtual video::SMaterial& getMaterial(u32 i)
{
return m_material;
}
void step(float dtime, ClientEnvironment *env);
virtual void OnRegisterSceneNode();
virtual void render();
void step(float dtime);
bool isExpired ()
bool isExpired () const
{ 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:
void updateLight();
void updateVertices();
void setVertexAlpha(float a);
video::SColor updateLight(ClientEnvironment *env);
void updateVertices(ClientEnvironment *env, video::SColor color);
ParticleBuffer *m_buffer = nullptr;
u16 m_index; // index in m_buffer
video::S3DVertex m_vertices[4];
float m_time = 0.0f;
float m_expiration;
ClientEnvironment *m_env;
IGameDef *m_gamedef;
aabb3f m_box;
aabb3f m_collisionbox;
// Color without lighting
video::SColor m_base_color;
ClientParticleTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos;
v2f m_texsize;
v3f m_pos;
v3f m_velocity;
v3f m_acceleration;
const ParticleParameters m_p;
LocalPlayer *m_player;
//! Color without lighting
video::SColor m_base_color;
//! Final rendered color
video::SColor m_color;
const ParticleParameters m_p;
float m_animation_time = 0.0f;
int m_animation_frame = 0;
float m_alpha = 0.0f;
ParticleSpawner *m_parent = nullptr;
// Used if not spawned from a particlespawner
@ -142,8 +128,7 @@ private:
class ParticleSpawner
{
public:
ParticleSpawner(IGameDef *gamedef,
LocalPlayer *player,
ParticleSpawner(LocalPlayer *player,
const ParticleSpawnerParameters &params,
u16 attached_id,
std::vector<ClientParticleTexture> &&texpool,
@ -164,7 +149,6 @@ private:
size_t m_active;
ParticleManager *m_particlemanager;
float m_time;
IGameDef *m_gamedef;
LocalPlayer *m_player;
ParticleSpawnerParameters p;
std::vector<ClientParticleTexture> m_texpool;
@ -172,12 +156,61 @@ private:
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 ParticleManager
{
friend class ParticleSpawner;
friend class ParticleSpawner;
public:
ParticleManager(ClientEnvironment* env);
DISABLE_CLASS_COPY(ParticleManager)
@ -213,7 +246,9 @@ protected:
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
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:
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
@ -221,17 +256,23 @@ private:
void stepParticles(float dtime);
void stepSpawners(float dtime);
void stepBuffers(float dtime);
void clearAll();
std::vector<std::unique_ptr<Particle>> m_particles;
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_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
// for server sent spawners.
u64 m_next_particle_spawner_id = U32_MAX + 1;
std::vector<irr_ptr<ParticleBuffer>> m_particle_buffers;
// 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;
IntervalLimiter m_buffer_gc;
std::mutex m_particle_list_lock;
std::mutex m_spawner_list_lock;
};

View File

@ -164,13 +164,19 @@ static std::optional<video::E_DRIVER_TYPE> chooseVideoDriver()
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)
{
if (requested_driver) {
params.DriverType = *requested_driver;
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
if (auto *device = createDeviceEx(params))
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);
@ -179,6 +185,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std
if (fallback_driver == video::EDT_NULL || fallback_driver == requested_driver)
continue;
params.DriverType = fallback_driver;
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
if (auto *device = createDeviceEx(params))
return device;
}
@ -222,9 +229,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
params.Stencilbuffer = false;
params.Vsync = vsync;
params.EventReceiver = receiver;
#ifdef __ANDROID__
params.PrivateData = porting::app_global;
#endif
// there is no standardized path for these on desktop
std::string rel_path = std::string("client") + DIR_DELIM
+ "shaders" + DIR_DELIM + "Irrlicht";
@ -232,7 +237,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
m_device = createDevice(params, driverType);
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.
driver->setMinHardwareBufferVertexCount(4);

View File

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "al_extensions.h"
#include "debug.h"
#include "sound_constants.h"
#include <cassert>
#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");
} else {
// Start with 2 buffers
ALuint buf_ids[2];
// Start with first buffer
// If m_next_sample_pos >= len_samples (happens only if not looped), one
// or both of buf_ids will be 0. Queuing 0 is a NOP.
// If m_next_sample_pos >= len_samples (happens only if not looped), buf0
// will be 0. Queuing 0 is a NOP.
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
buf_ids[0] = buf0;
m_next_sample_pos = buf0_end;
if (m_looping && m_next_sample_pos == len_samples)
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);
alSourceQueueBuffers(m_source_id, 1, &buf0);
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
// We can't use AL_LOOPING because more buffers are queued later
// looping is therefore done manually
// We can't use AL_LOOPING because more buffers are queued later.
// Looping is therefore done manually.
// Sound is not dead if queue runs empty prematurely
m_stopped_means_dead = false;
warn_if_al_error("when creating streaming sound");
// Enqueue more buffers
stepStream(true);
}
// Set initial pos, volume, pitch
@ -129,23 +124,44 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
setPitch(pitch);
}
bool PlayingSound::stepStream()
bool PlayingSound::stepStream(bool playback_speed_changed)
{
if (isDead())
return false;
// unqueue finished buffers
ALint num_unqueued_bufs = 0;
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_unqueued_bufs);
if (num_unqueued_bufs == 0)
return true;
// We always have 2 buffers enqueued at most
SANITY_CHECK(num_unqueued_bufs <= 2);
ALuint unqueued_buffer_ids[2];
alSourceUnqueueBuffers(m_source_id, num_unqueued_bufs, unqueued_buffer_ids);
// Unqueue finished buffers
ALint num_processed_bufs = 0;
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_processed_bufs);
if (num_processed_bufs == 0 && !playback_speed_changed)
return true; // Nothing to do
if (num_processed_bufs > 0) {
ALint num_to_unqueue = num_processed_bufs;
ALuint unqueued_buffer_ids[8];
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
for (ALint i = 0; i < num_unqueued_bufs; ++i) {
// Find out how many buffers we want to enqueue
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) {
// Reached end
if (m_looping) {
@ -256,4 +272,11 @@ f32 PlayingSound::getGain() noexcept
return gain;
}
void PlayingSound::setPitch(f32 pitch)
{
alSourcef(m_source_id, AL_PITCH, pitch);
if (isStreaming())
stepStream(true);
}
} // namespace sound

View File

@ -63,7 +63,7 @@ public:
DISABLE_CLASS_COPY(PlayingSound)
// return false means streaming finished
bool stepStream();
bool stepStream(bool playback_speed_changed = false);
// retruns true if it wasn't fading already
bool fade(f32 step, f32 target_gain) noexcept;
@ -77,7 +77,7 @@ public:
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(); }

View File

@ -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
* end of the next bigstep. So between two stepStream()-calls lie at most
* 2 * STREAM_BIGSTEP_TIME seconds.
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
* is still available after the first 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.
* We ensure that there are always enough untouched full buffers left such that
* we do not run into an empty queue in this time period, see stepStream().
*
*/
@ -115,8 +109,6 @@ constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
// step duration for the OpenALSoundManager thread, in seconds
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,
"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

View File

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "nodetimer.h"
#include "inventory.h"
#include "log.h"
#include "debug.h"
#include "serialization.h"
#include "util/serialize.h"
#include "util/string.h"

View File

@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <iostream>
#include <exception>
#include <cassert>
#include "gettime.h"

View File

@ -310,6 +310,7 @@ void set_default_settings()
settings->setDefault("invert_hotbar_mouse_wheel", "false");
settings->setDefault("mouse_sensitivity", "0.2");
settings->setDefault("repeat_place_time", "0.25");
settings->setDefault("repeat_dig_time", "0.15");
settings->setDefault("safe_dig_and_place", "false");
settings->setDefault("random_input", "false");
settings->setDefault("aux1_descends", "false");
@ -364,11 +365,10 @@ void set_default_settings()
settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default");
#endif
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
#if ENABLE_UPDATE_CHECKER
settings->setDefault("update_last_checked", "");
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
#else
settings->setDefault("update_last_checked", "disabled");
settings->setDefault("update_information_url", "");
#endif
// Server
@ -494,11 +494,13 @@ void set_default_settings()
settings->setDefault("keymap_sneak", "KEY_SHIFT");
#endif
settings->setDefault("touchscreen_threshold", "20");
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("fixed_virtual_joystick", "false");
settings->setDefault("virtual_joystick_triggers_aux1", "false");
settings->setDefault("touch_punch_gesture", "short_tap");
#ifdef ENABLE_TOUCH
settings->setDefault("clickable_chat_weblinks", "false");
#else

View File

@ -223,6 +223,16 @@ std::string CreateTempFile()
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 ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr,
@ -446,6 +456,15 @@ std::string CreateTempFile()
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 {
struct FileDeleter {
void operator()(FILE *stream) {

View File

@ -75,13 +75,19 @@ bool RecursiveDelete(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();
// Returns path to securely-created temporary file (will already exist when this function returns)
// can return "" on error
/// Returns path to securely-created temporary file (will already exist when this function returns).
/// @return path or "" on error
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
hidden directories (whose names start with . or _)
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,280 +1,280 @@
// Copyright (C) 2002-2012 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 <IGUIStaticText.h>
#include "irrlicht_changes/static_text.h"
#include "IGUIButton.h"
#include "IGUISpriteBank.h"
#include "ITexture.h"
#include "SColor.h"
#include "guiSkin.h"
#include "StyleSpec.h"
using namespace irr;
class ISimpleTextureSource;
class GUIButton : public gui::IGUIButton
{
public:
//! constructor
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
bool noclip=false);
//! destructor
virtual ~GUIButton();
//! called if an event happened.
virtual bool OnEvent(const SEvent& event) override;
//! draws the element and its children
virtual void draw() override;
//! 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;
//! Gets the override font (if any)
virtual gui::IGUIFont* getOverrideFont() const override;
//! Get the font which is used right now for drawing
virtual gui::IGUIFont* getActiveFont() const override;
//! Sets another color for the button text.
virtual void setOverrideColor(video::SColor color) override;
//! Gets the override color
virtual video::SColor getOverrideColor() const override;
//! Gets the currently used text color
virtual video::SColor getActiveColor() const override;
//! Sets if the button text should use the override color or the color in the gui skin.
virtual void enableOverrideColor(bool enable) override;
//! Checks if an override color is enabled
virtual bool isOverrideColorEnabled(void) const override;
// PATCH
//! 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,
video::ITexture* image=nullptr,
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.
virtual void setImage(video::ITexture* image=nullptr) override;
//! 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;
//! Sets an image which should be displayed on the button when it is in pressed state.
virtual void setPressedImage(video::ITexture* image=nullptr) override;
//! 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;
//! Sets the text displayed by the button
virtual void setText(const wchar_t* text) override;
// END PATCH
//! Sets the sprite bank used by the button
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
//! 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 state: State of the button to set the sprite for
\param index: The sprite number from the current sprite bank
\param color: The color of the sprite
*/
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
video::SColor color=video::SColor(255,255,255,255),
bool loop=false, bool scale=false) override;
//! 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;
//! 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;
//! Returns if the sprite in the given state does loop
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
//! Returns if the sprite in the given state is scaled
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
//! 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,
//! the user can change the state of the button.
virtual void setIsPushButton(bool isPushButton=true) override;
//! Checks whether the button is a push button
virtual bool isPushButton() const override;
//! Sets the pressed state of the button if this is a pushbutton
virtual void setPressed(bool pressed=true) override;
//! Returns if the button is currently pressed
virtual bool isPressed() const override;
// PATCH
//! Returns if this element (or one of its direct children) is hovered
bool isHovered() const;
//! Returns if this element (or one of its direct children) is focused
bool isFocused() const;
// END PATCH
//! Sets if the button should use the skin to draw its border
virtual void setDrawBorder(bool border=true) override;
//! Checks if the button face and border are being drawn
virtual bool isDrawingBorder() const override;
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
//! Checks if the alpha channel should be used for drawing images on the button
virtual bool isAlphaChannelUsed() const override;
//! Sets if the button should scale the button images to fit
virtual void setScaleImage(bool scaleImage=true) override;
//! Checks whether the button scales the used images
virtual bool isScalingImage() const override;
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
virtual bool getClickShiftState() const override
{
return ClickShiftState;
}
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
virtual bool getClickControlState() const override
{
return ClickControlState;
}
void setColor(video::SColor color);
// PATCH
//! Set element properties from a StyleSpec corresponding to the button state
void setFromState();
//! Set element properties from a StyleSpec
virtual void setFromStyle(const StyleSpec& style);
//! Set the styles used for each state
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
// END PATCH
//! Do not drop returned handle
static GUIButton* addButton(gui::IGUIEnvironment *environment,
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
IGUIElement* parent, s32 id, const wchar_t* text,
const wchar_t *tooltiptext=L"");
protected:
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
ISimpleTextureSource *getTextureSource() { return TSrc; }
struct ButtonImage
{
ButtonImage() = default;
ButtonImage(const ButtonImage& other)
{
*this = other;
}
~ButtonImage()
{
if ( Texture )
Texture->drop();
}
ButtonImage& operator=(const ButtonImage& other)
{
if ( this == &other )
return *this;
if (other.Texture)
other.Texture->grab();
if ( Texture )
Texture->drop();
Texture = other.Texture;
SourceRect = other.SourceRect;
return *this;
}
bool operator==(const ButtonImage& other) const
{
return Texture == other.Texture && SourceRect == other.SourceRect;
}
video::ITexture* Texture = nullptr;
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
};
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
private:
struct ButtonSprite
{
bool operator==(const ButtonSprite &other) const
{
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
}
s32 Index = -1;
video::SColor Color;
bool Loop = false;
bool Scale = false;
};
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
gui::IGUISpriteBank* SpriteBank = nullptr;
ButtonImage ButtonImages[gui::EGBIS_COUNT];
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
gui::IGUIFont* OverrideFont = nullptr;
bool OverrideColorEnabled = false;
video::SColor OverrideColor = video::SColor(101,255,255,255);
u32 ClickTime = 0;
u32 HoverTime = 0;
u32 FocusTime = 0;
bool ClickShiftState = false;
bool ClickControlState = false;
bool IsPushButton = false;
bool Pressed = false;
bool UseAlphaChannel = false;
bool DrawBorder = true;
bool ScaleImage = false;
video::SColor Colors[4];
// PATCH
bool WasHovered = false;
bool WasFocused = false;
ISimpleTextureSource *TSrc;
gui::IGUIStaticText *StaticText;
core::rect<s32> BgMiddle;
core::rect<s32> Padding;
core::vector2d<s32> ContentOffset;
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
// END PATCH
};
// Copyright (C) 2002-2012 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 <IGUIStaticText.h>
#include "irrlicht_changes/static_text.h"
#include "IGUIButton.h"
#include "IGUISpriteBank.h"
#include "ITexture.h"
#include "SColor.h"
#include "guiSkin.h"
#include "StyleSpec.h"
using namespace irr;
class ISimpleTextureSource;
class GUIButton : public gui::IGUIButton
{
public:
//! constructor
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
bool noclip=false);
//! destructor
virtual ~GUIButton();
//! called if an event happened.
virtual bool OnEvent(const SEvent& event) override;
//! draws the element and its children
virtual void draw() override;
//! 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;
//! Gets the override font (if any)
virtual gui::IGUIFont* getOverrideFont() const override;
//! Get the font which is used right now for drawing
virtual gui::IGUIFont* getActiveFont() const override;
//! Sets another color for the button text.
virtual void setOverrideColor(video::SColor color) override;
//! Gets the override color
virtual video::SColor getOverrideColor() const override;
//! Gets the currently used text color
virtual video::SColor getActiveColor() const override;
//! Sets if the button text should use the override color or the color in the gui skin.
virtual void enableOverrideColor(bool enable) override;
//! Checks if an override color is enabled
virtual bool isOverrideColorEnabled(void) const override;
// PATCH
//! 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,
video::ITexture* image=nullptr,
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.
virtual void setImage(video::ITexture* image=nullptr) override;
//! 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;
//! Sets an image which should be displayed on the button when it is in pressed state.
virtual void setPressedImage(video::ITexture* image=nullptr) override;
//! 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;
//! Sets the text displayed by the button
virtual void setText(const wchar_t* text) override;
// END PATCH
//! Sets the sprite bank used by the button
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
//! 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 state: State of the button to set the sprite for
\param index: The sprite number from the current sprite bank
\param color: The color of the sprite
*/
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
video::SColor color=video::SColor(255,255,255,255),
bool loop=false, bool scale=false) override;
//! 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;
//! 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;
//! Returns if the sprite in the given state does loop
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
//! Returns if the sprite in the given state is scaled
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
//! 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,
//! the user can change the state of the button.
virtual void setIsPushButton(bool isPushButton=true) override;
//! Checks whether the button is a push button
virtual bool isPushButton() const override;
//! Sets the pressed state of the button if this is a pushbutton
virtual void setPressed(bool pressed=true) override;
//! Returns if the button is currently pressed
virtual bool isPressed() const override;
// PATCH
//! Returns if this element (or one of its direct children) is hovered
bool isHovered() const;
//! Returns if this element (or one of its direct children) is focused
bool isFocused() const;
// END PATCH
//! Sets if the button should use the skin to draw its border
virtual void setDrawBorder(bool border=true) override;
//! Checks if the button face and border are being drawn
virtual bool isDrawingBorder() const override;
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
//! Checks if the alpha channel should be used for drawing images on the button
virtual bool isAlphaChannelUsed() const override;
//! Sets if the button should scale the button images to fit
virtual void setScaleImage(bool scaleImage=true) override;
//! Checks whether the button scales the used images
virtual bool isScalingImage() const override;
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
virtual bool getClickShiftState() const override
{
return ClickShiftState;
}
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
virtual bool getClickControlState() const override
{
return ClickControlState;
}
void setColor(video::SColor color);
// PATCH
//! Set element properties from a StyleSpec corresponding to the button state
void setFromState();
//! Set element properties from a StyleSpec
virtual void setFromStyle(const StyleSpec& style);
//! Set the styles used for each state
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
// END PATCH
//! Do not drop returned handle
static GUIButton* addButton(gui::IGUIEnvironment *environment,
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
IGUIElement* parent, s32 id, const wchar_t* text,
const wchar_t *tooltiptext=L"");
protected:
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
ISimpleTextureSource *getTextureSource() { return TSrc; }
struct ButtonImage
{
ButtonImage() = default;
ButtonImage(const ButtonImage& other)
{
*this = other;
}
~ButtonImage()
{
if ( Texture )
Texture->drop();
}
ButtonImage& operator=(const ButtonImage& other)
{
if ( this == &other )
return *this;
if (other.Texture)
other.Texture->grab();
if ( Texture )
Texture->drop();
Texture = other.Texture;
SourceRect = other.SourceRect;
return *this;
}
bool operator==(const ButtonImage& other) const
{
return Texture == other.Texture && SourceRect == other.SourceRect;
}
video::ITexture* Texture = nullptr;
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
};
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
private:
struct ButtonSprite
{
bool operator==(const ButtonSprite &other) const
{
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
}
s32 Index = -1;
video::SColor Color;
bool Loop = false;
bool Scale = false;
};
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
gui::IGUISpriteBank* SpriteBank = nullptr;
ButtonImage ButtonImages[gui::EGBIS_COUNT];
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
gui::IGUIFont* OverrideFont = nullptr;
bool OverrideColorEnabled = false;
video::SColor OverrideColor = video::SColor(101,255,255,255);
u32 ClickTime = 0;
u32 HoverTime = 0;
u32 FocusTime = 0;
bool ClickShiftState = false;
bool ClickControlState = false;
bool IsPushButton = false;
bool Pressed = false;
bool UseAlphaChannel = false;
bool DrawBorder = true;
bool ScaleImage = false;
video::SColor Colors[4];
// PATCH
bool WasHovered = false;
bool WasFocused = false;
ISimpleTextureSource *TSrc;
gui::IGUIStaticText *StaticText;
core::rect<s32> BgMiddle;
core::rect<s32> Padding;
core::vector2d<s32> ContentOffset;
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
// END PATCH
};

View File

@ -369,6 +369,8 @@ void GUIEngine::run()
#endif
}
m_script->beforeClose();
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,360 +1,360 @@
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#ifndef __GUI_SKIN_H_INCLUDED__
#define __GUI_SKIN_H_INCLUDED__
#include "IGUISkin.h"
#include "irrString.h"
#include <string>
#include "ITexture.h"
namespace irr
{
namespace video
{
class IVideoDriver;
}
namespace gui
{
class GUISkin : public IGUISkin
{
public:
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
//! destructor
virtual ~GUISkin();
//! returns default color
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
//! sets a default color
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
//! returns size for the given size type
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
//! sets a default size
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
//! returns the default font
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
//! sets a default font
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
//! sets the sprite bank used for drawing icons
virtual void setSpriteBank(IGUISpriteBank* bank);
//! gets the sprite bank used for drawing icons
virtual IGUISpriteBank* getSpriteBank() const;
//! Returns a default icon
/** Returns the sprite index within the sprite bank */
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
//! Sets a default icon
/** Sets the sprite index used for drawing icons like arrows,
close buttons and ticks in checkboxes
\param icon: Enum specifying which icon to change
\param index: The sprite index used to draw this icon */
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
//! Returns a default text.
/** For example for Message box button captions:
"OK", "Cancel", "Yes", "No" and so on. */
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
//! Sets a default text.
/** For example for Message box button captions:
"OK", "Cancel", "Yes", "No" and so on. */
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
//! draws a standard 3d button pane
/** 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
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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
implementations to find out how to draw the part exactly. */
virtual void draw3DButtonPaneStandard(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DButtonPaneStandard(element, rect,clip);
}
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a pressed 3d button pane
/** 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
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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
implementations to find out how to draw the part exactly. */
virtual void draw3DButtonPanePressed(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DButtonPanePressed(element, rect, clip);
}
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a sunken 3d pane
/** Used for drawing the background of edit, combo or check boxes.
\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
implementations to find out how to draw the part exactly.
\param bgcolor: Background color.
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
deep into the ground.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DSunkenPane(IGUIElement* element,
video::SColor bgcolor, bool flat,
bool fillBackGround,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
}
virtual void drawColored3DSunkenPane(IGUIElement* element,
video::SColor bgcolor, bool flat,
bool fillBackGround,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a window background
/** Used for drawing the background of dialogs and windows.
\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
implementations to find out how to draw the part exactly.
\param titleBarColor: Title color.
\param drawTitleBar: True to enable title drawing.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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.
That is the area without borders and without titlebar.
\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.*/
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
bool drawTitleBar, video::SColor titleBarColor,
const core::rect<s32>& rect,
const core::rect<s32>* clip,
core::rect<s32>* checkClientArea)
{
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
rect, clip, checkClientArea);
}
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
bool drawTitleBar, video::SColor titleBarColor,
const core::rect<s32>& rect,
const core::rect<s32>* clip,
core::rect<s32>* checkClientArea,
const video::SColor* colors=0);
//! draws a standard 3d menu pane
/** Used for drawing for menus and context menus.
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.
\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
implementations to find out how to draw the part exactly.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DMenuPane(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DMenuPane(element, rect, clip);
}
virtual void drawColored3DMenuPane(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a standard 3d tool bar
/** Used for drawing for toolbars and menus.
\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
implementations to find out how to draw the part exactly.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DToolBar(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DToolBar(element, rect, clip);
}
virtual void drawColored3DToolBar(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a tab button
/** Used for drawing for tab buttons on top of tabs.
\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
implementations to find out how to draw the part exactly.
\param active: Specifies if the tab is currently active.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DTabButton(IGUIElement* element, bool active,
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
{
drawColored3DTabButton(element, active, rect, clip, alignment);
}
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
const video::SColor* colors=0);
//! draws a tab control body
/** \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
implementations to find out how to draw the part exactly.
\param border: Specifies if the border should be drawn.
\param background: Specifies if the background should be drawn.
\param rect: Defining area where to draw.
\param clip: Clip area. */
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)
{
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
}
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 video::SColor* colors=0);
//! draws an icon, usually from the skin's sprite bank
/** \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
by more complex implementations to find out how to draw the part exactly.
\param icon: Specifies the icon to be drawn.
\param position: The position to draw the icon
\param starttime: The time at the start of the animation
\param currenttime: The present time, used to calculate the frame number
\param loop: Whether the animation should loop or not
\param clip: Clip area. */
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
const core::position2di position,
u32 starttime=0, u32 currenttime=0,
bool loop=false, const core::rect<s32>* clip=0)
{
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
}
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
const core::position2di position,
u32 starttime=0, u32 currenttime=0,
bool loop=false, const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a 2d rectangle.
/** \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
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
transparent the rectangle will be.
\param pos: Position of the rectangle.
\param clip: Pointer to rectangle against which the rectangle will be clipped.
If the pointer is null, no clipping will be performed. */
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
//! get the type of this skin
virtual EGUI_SKIN_TYPE getType() const;
//! gets the colors
virtual void getColors(video::SColor* colors); // ::PATCH:
private:
video::SColor Colors[EGDC_COUNT];
s32 Sizes[EGDS_COUNT];
u32 Icons[EGDI_COUNT];
IGUIFont* Fonts[EGDF_COUNT];
IGUISpriteBank* SpriteBank;
core::stringw Texts[EGDT_COUNT];
video::IVideoDriver* Driver;
bool UseGradient;
EGUI_SKIN_TYPE Type;
};
#define set3DSkinColors(skin, button_color) \
{ \
skin->setColor(EGDC_3D_FACE, button_color); \
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
skin->setColor(EGDC_3D_LIGHT, button_color); \
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
}
#define getElementSkinColor(color) \
{ \
if (!Colors) \
{ \
IGUISkin* skin = Environment->getSkin(); \
if (skin) \
return skin->getColor(color); \
} \
return Colors[color]; \
}
#define setElementSkinColor(which, newColor, shading) \
{ \
if (!Colors) \
{ \
Colors = new video::SColor[EGDC_COUNT]; \
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
if (skin) \
skin->getColors(Colors); \
} \
Colors[which] = newColor; \
setShading(Colors[which],shading); \
}
} // end namespace gui
//! Sets the shading
inline void setShading(video::SColor &color,f32 s) // :PATCH:
{
if (s < 1.0f)
{
color.setRed(color.getRed() * s);
color.setGreen(color.getGreen() * s);
color.setBlue(color.getBlue() * s);
}
else if (s > 1.0f)
{
s -= 1.0f;
color.setRed(color.getRed() + (255 - color.getRed()) * s);
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
}
}
} // end namespace irr
#endif
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#ifndef __GUI_SKIN_H_INCLUDED__
#define __GUI_SKIN_H_INCLUDED__
#include "IGUISkin.h"
#include "irrString.h"
#include <string>
#include "ITexture.h"
namespace irr
{
namespace video
{
class IVideoDriver;
}
namespace gui
{
class GUISkin : public IGUISkin
{
public:
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
//! destructor
virtual ~GUISkin();
//! returns default color
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
//! sets a default color
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
//! returns size for the given size type
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
//! sets a default size
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
//! returns the default font
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
//! sets a default font
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
//! sets the sprite bank used for drawing icons
virtual void setSpriteBank(IGUISpriteBank* bank);
//! gets the sprite bank used for drawing icons
virtual IGUISpriteBank* getSpriteBank() const;
//! Returns a default icon
/** Returns the sprite index within the sprite bank */
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
//! Sets a default icon
/** Sets the sprite index used for drawing icons like arrows,
close buttons and ticks in checkboxes
\param icon: Enum specifying which icon to change
\param index: The sprite index used to draw this icon */
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
//! Returns a default text.
/** For example for Message box button captions:
"OK", "Cancel", "Yes", "No" and so on. */
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
//! Sets a default text.
/** For example for Message box button captions:
"OK", "Cancel", "Yes", "No" and so on. */
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
//! draws a standard 3d button pane
/** 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
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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
implementations to find out how to draw the part exactly. */
virtual void draw3DButtonPaneStandard(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DButtonPaneStandard(element, rect,clip);
}
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a pressed 3d button pane
/** 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
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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
implementations to find out how to draw the part exactly. */
virtual void draw3DButtonPanePressed(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DButtonPanePressed(element, rect, clip);
}
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a sunken 3d pane
/** Used for drawing the background of edit, combo or check boxes.
\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
implementations to find out how to draw the part exactly.
\param bgcolor: Background color.
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
deep into the ground.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DSunkenPane(IGUIElement* element,
video::SColor bgcolor, bool flat,
bool fillBackGround,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
}
virtual void drawColored3DSunkenPane(IGUIElement* element,
video::SColor bgcolor, bool flat,
bool fillBackGround,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a window background
/** Used for drawing the background of dialogs and windows.
\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
implementations to find out how to draw the part exactly.
\param titleBarColor: Title color.
\param drawTitleBar: True to enable title drawing.
\param rect: Defining area where to draw.
\param clip: Clip area.
\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.
That is the area without borders and without titlebar.
\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.*/
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
bool drawTitleBar, video::SColor titleBarColor,
const core::rect<s32>& rect,
const core::rect<s32>* clip,
core::rect<s32>* checkClientArea)
{
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
rect, clip, checkClientArea);
}
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
bool drawTitleBar, video::SColor titleBarColor,
const core::rect<s32>& rect,
const core::rect<s32>* clip,
core::rect<s32>* checkClientArea,
const video::SColor* colors=0);
//! draws a standard 3d menu pane
/** Used for drawing for menus and context menus.
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.
\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
implementations to find out how to draw the part exactly.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DMenuPane(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DMenuPane(element, rect, clip);
}
virtual void drawColored3DMenuPane(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a standard 3d tool bar
/** Used for drawing for toolbars and menus.
\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
implementations to find out how to draw the part exactly.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DToolBar(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0)
{
drawColored3DToolBar(element, rect, clip);
}
virtual void drawColored3DToolBar(IGUIElement* element,
const core::rect<s32>& rect,
const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a tab button
/** Used for drawing for tab buttons on top of tabs.
\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
implementations to find out how to draw the part exactly.
\param active: Specifies if the tab is currently active.
\param rect: Defining area where to draw.
\param clip: Clip area. */
virtual void draw3DTabButton(IGUIElement* element, bool active,
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
{
drawColored3DTabButton(element, active, rect, clip, alignment);
}
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
const video::SColor* colors=0);
//! draws a tab control body
/** \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
implementations to find out how to draw the part exactly.
\param border: Specifies if the border should be drawn.
\param background: Specifies if the background should be drawn.
\param rect: Defining area where to draw.
\param clip: Clip area. */
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)
{
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
}
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 video::SColor* colors=0);
//! draws an icon, usually from the skin's sprite bank
/** \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
by more complex implementations to find out how to draw the part exactly.
\param icon: Specifies the icon to be drawn.
\param position: The position to draw the icon
\param starttime: The time at the start of the animation
\param currenttime: The present time, used to calculate the frame number
\param loop: Whether the animation should loop or not
\param clip: Clip area. */
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
const core::position2di position,
u32 starttime=0, u32 currenttime=0,
bool loop=false, const core::rect<s32>* clip=0)
{
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
}
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
const core::position2di position,
u32 starttime=0, u32 currenttime=0,
bool loop=false, const core::rect<s32>* clip=0,
const video::SColor* colors=0);
//! draws a 2d rectangle.
/** \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
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
transparent the rectangle will be.
\param pos: Position of the rectangle.
\param clip: Pointer to rectangle against which the rectangle will be clipped.
If the pointer is null, no clipping will be performed. */
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
//! get the type of this skin
virtual EGUI_SKIN_TYPE getType() const;
//! gets the colors
virtual void getColors(video::SColor* colors); // ::PATCH:
private:
video::SColor Colors[EGDC_COUNT];
s32 Sizes[EGDS_COUNT];
u32 Icons[EGDI_COUNT];
IGUIFont* Fonts[EGDF_COUNT];
IGUISpriteBank* SpriteBank;
core::stringw Texts[EGDT_COUNT];
video::IVideoDriver* Driver;
bool UseGradient;
EGUI_SKIN_TYPE Type;
};
#define set3DSkinColors(skin, button_color) \
{ \
skin->setColor(EGDC_3D_FACE, button_color); \
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
skin->setColor(EGDC_3D_LIGHT, button_color); \
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
}
#define getElementSkinColor(color) \
{ \
if (!Colors) \
{ \
IGUISkin* skin = Environment->getSkin(); \
if (skin) \
return skin->getColor(color); \
} \
return Colors[color]; \
}
#define setElementSkinColor(which, newColor, shading) \
{ \
if (!Colors) \
{ \
Colors = new video::SColor[EGDC_COUNT]; \
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
if (skin) \
skin->getColors(Colors); \
} \
Colors[which] = newColor; \
setShading(Colors[which],shading); \
}
} // end namespace gui
//! Sets the shading
inline void setShading(video::SColor &color,f32 s) // :PATCH:
{
if (s < 1.0f)
{
color.setRed(color.getRed() * s);
color.setGreen(color.getGreen() * s);
color.setBlue(color.getBlue() * s);
}
else if (s > 1.0f)
{
s -= 1.0f;
color.setRed(color.getRed() + (255 - color.getRed()) * s);
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
}
}
} // end namespace irr
#endif

View File

@ -239,7 +239,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
#ifdef __ANDROID__
// display software keyboard when clicking edit boxes
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 =
Environment->getRootGUIElement()->getElementFromPoint(
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));

View File

@ -414,6 +414,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver)
}
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_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1");
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) {
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;
}
}
@ -1105,11 +1106,11 @@ void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode)
// Since the pointed thing has already been determined when this function
// 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_place_pressed = false;
u64 now = porting::getTimeMs();
// 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.
// Long taps don't need this, they're adjusted to the swapped meanings instead.

View File

@ -79,7 +79,6 @@ typedef enum
AHBB_Dir_Right_Left
} autohide_button_bar_dir;
#define MIN_DIG_TIME_MS 500
#define BUTTON_REPEAT_DELAY 0.2f
#define SETTINGS_BAR_Y_OFFSET 5
#define RARE_CONTROLS_BAR_Y_OFFSET 5
@ -225,6 +224,7 @@ private:
v2u32 m_screensize;
s32 button_size;
double m_touchscreen_threshold;
u16 m_long_tap_delay;
bool m_visible; // is the whole touch screen gui visible
std::unordered_map<u16, rect<s32>> m_hotbar_rects;

View File

@ -40,24 +40,37 @@ with this program; if not, write to the Free Software Foundation, Inc.,
TouchInteraction::TouchInteraction()
{
pointed_nothing = LONG_DIG_SHORT_PLACE;
pointed_node = LONG_DIG_SHORT_PLACE;
// Map punching to single tap by default.
pointed_object = SHORT_DIG_LONG_PLACE;
pointed_nothing = TouchInteractionMode_USER;
pointed_node = TouchInteractionMode_USER;
pointed_object = TouchInteractionMode_USER;
}
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:
return pointed_nothing;
result = pointed_nothing;
break;
case POINTEDTHING_NODE:
return pointed_node;
result = pointed_node;
break;
case POINTEDTHING_OBJECT:
return pointed_object;
result = pointed_object;
break;
default:
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

View File

@ -30,10 +30,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "texture_override.h" // TextureOverride
#include "tool.h"
#include "util/pointabilities.h"
#include "util/pointedthing.h"
class IGameDef;
class Client;
struct ToolCapabilities;
struct PointedThing;
#ifndef SERVER
#include "client/texturesource.h"
struct ItemMesh;
@ -57,6 +58,7 @@ enum TouchInteractionMode : u8
{
LONG_DIG_SHORT_PLACE,
SHORT_DIG_LONG_PLACE,
TouchInteractionMode_USER, // Meaning depends on client-side settings
TouchInteractionMode_END, // Dummy for validity check
};
@ -67,7 +69,9 @@ struct TouchInteraction
TouchInteractionMode pointed_object;
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 deSerialize(std::istream &is);
};

View File

@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include "log.h"
#ifdef __ANDROID__
#include <android/log.h>
#endif
#include <sstream>
#include <iostream>
#include <algorithm>

View File

@ -103,12 +103,10 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
std::list<SharedBuffer<u8>> *chunks)
{
// Chunk packets, containing the TYPE_SPLIT header
u32 chunk_header_size = 7;
u32 maximum_data_size = chunksize_max - chunk_header_size;
u32 start = 0;
u32 end = 0;
u32 chunk_num = 0;
u16 chunk_count = 0;
const u32 chunk_header_size = 7;
const u32 maximum_data_size = chunksize_max - chunk_header_size;
u32 start = 0, end = 0;
u16 chunk_num = 0;
do {
end = start + maximum_data_size - 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);
chunks->push_back(chunk);
chunk_count++;
start = end + 1;
sanity_check(chunk_num < 0xFFFF); // overflow
chunk_num++;
}
while (end != data.getSize() - 1);
for (SharedBuffer<u8> &chunk : *chunks) {
for (auto &chunk : *chunks) {
// 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;
Channel &chan = channels[c.channelnum];
u32 chunksize_max = max_packet_size
const u32 chunksize_max = max_packet_size
- BASE_HEADER_SIZE
- RELIABLE_HEADER_SIZE;
sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512);
std::list<SharedBuffer<u8>> originals;
u16 split_sequence_number = chan.readNextSplitSeqNum();
if (c.raw) {
originals.emplace_back(c.data);
} else {
makeAutoSplitPacket(c.data, chunksize_max,split_sequence_number, &originals);
chan.setNextSplitSeqNum(split_sequence_number);
u16 split_seqnum = chan.readNextSplitSeqNum();
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_initial_sequence_number = false;
std::queue<BufferedPacketPtr> toadd;
@ -1271,7 +1269,7 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout,
m_udpSocket(ipv6),
m_protocol_id(protocol_id),
m_sendThread(new ConnectionSendThread(max_packet_size, timeout)),
m_receiveThread(new ConnectionReceiveThread(max_packet_size)),
m_receiveThread(new ConnectionReceiveThread()),
m_bc_peerhandler(peerhandler)
{
@ -1505,6 +1503,15 @@ void Connection::Send(session_t peer_id, u8 channelnum,
{
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));
}

View File

@ -796,7 +796,7 @@ void ConnectionSendThread::sendAsPacket(session_t peer_id, u8 channelnum,
m_outgoing_queue.push(packet);
}
ConnectionReceiveThread::ConnectionReceiveThread(unsigned int max_packet_size) :
ConnectionReceiveThread::ConnectionReceiveThread() :
Thread("ConnectionReceive")
{
}

View File

@ -113,7 +113,7 @@ private:
class ConnectionReceiveThread : public Thread
{
public:
ConnectionReceiveThread(unsigned int max_packet_size);
ConnectionReceiveThread();
void *run();

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "inventory.h"
#include "irrlicht_changes/printing.h"
#include "log.h"
#include "debug.h"
#include "util/serialize.h"
#include "constants.h" // MAP_BLOCKSIZE
#include <sstream>

View File

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlicht_changes/printing.h"
#include "irrlichttypes_bloated.h"
#include "exceptions.h"
#include "log.h"
#include "util/serialize.h"
#include <sstream>

View File

@ -30,6 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "settings.h"
#include <jni.h>
#define SDL_MAIN_HANDLED 1
#include <SDL.h>
#include <sstream>
#include <exception>
#include <cstdlib>
@ -53,10 +57,8 @@ namespace porting {
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");
char *argv[] = {strdup(PROJECT_NAME), strdup("--verbose"), nullptr};
@ -70,45 +72,15 @@ void android_main(android_app *app)
}
namespace porting {
android_app *app_global = nullptr;
JNIEnv *jnienv = nullptr;
jclass nativeActivity;
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);
}
jobject activity;
jclass activityClass;
void osSpecificInit()
{
JavaVM *jvm = app_global->activity->vm;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
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;
jnienv = (JNIEnv*)SDL_AndroidGetJNIEnv();
activity = (jobject)SDL_AndroidGetActivity();
activityClass = jnienv->GetObjectClass(activity);
// Set default language
auto lang = getLanguageAndroid();
@ -129,9 +101,6 @@ void cleanupAndroid()
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
moncleanup();
#endif
JavaVM *jvm = app_global->activity->vm;
jvm->DetachCurrentThread();
}
static std::string readJavaString(jstring j_str)
@ -149,11 +118,11 @@ bool setSystemPaths()
{
// Set user and share paths
{
jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity,
jmethodID getUserDataPath = jnienv->GetMethodID(activityClass,
"getUserDataPath", "()Ljava/lang/String;");
FATAL_ERROR_IF(getUserDataPath==nullptr,
"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);
path_user = str;
path_share = str;
@ -161,11 +130,11 @@ bool setSystemPaths()
// Set cache path
{
jmethodID getCachePath = jnienv->GetMethodID(nativeActivity,
jmethodID getCachePath = jnienv->GetMethodID(activityClass,
"getCachePath", "()Ljava/lang/String;");
FATAL_ERROR_IF(getCachePath==nullptr,
"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);
}
@ -174,7 +143,7 @@ bool setSystemPaths()
void showTextInputDialog(const std::string &hint, const std::string &current, int editType)
{
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showTextInputDialog",
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showTextInputDialog",
"(Ljava/lang/String;Ljava/lang/String;I)V");
FATAL_ERROR_IF(showdialog == nullptr,
@ -184,13 +153,13 @@ void showTextInputDialog(const std::string &hint, const std::string &current, in
jstring jcurrent = jnienv->NewStringUTF(current.c_str());
jint jeditType = editType;
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog,
jnienv->CallVoidMethod(activity, showdialog,
jhint, jcurrent, jeditType);
}
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");
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->CallVoidMethod(app_global->activity->clazz, showdialog, jOptionList,
jnienv->CallVoidMethod(activity, showdialog, jOptionList,
jselectedIdx);
}
void openURIAndroid(const char *url)
{
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURI",
jmethodID url_open = jnienv->GetMethodID(activityClass, "openURI",
"(Ljava/lang/String;)V");
FATAL_ERROR_IF(url_open == nullptr,
"porting::openURIAndroid unable to find Java openURI method");
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)
{
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "shareFile",
jmethodID url_open = jnienv->GetMethodID(activityClass, "shareFile",
"(Ljava/lang/String;)V");
FATAL_ERROR_IF(url_open == nullptr,
"porting::shareFileAndroid unable to find Java shareFile method");
jstring jurl = jnienv->NewStringUTF(path.c_str());
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
jnienv->CallVoidMethod(activity, url_open, jurl);
}
AndroidDialogType getLastInputDialogType()
{
jmethodID lastdialogtype = jnienv->GetMethodID(nativeActivity,
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,
"getLastDialogType", "()I");
FATAL_ERROR_IF(lastdialogtype == nullptr,
"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);
}
AndroidDialogState getInputDialogState()
{
jmethodID inputdialogstate = jnienv->GetMethodID(nativeActivity,
jmethodID inputdialogstate = jnienv->GetMethodID(activityClass,
"getInputDialogState", "()I");
FATAL_ERROR_IF(inputdialogstate == nullptr,
"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);
}
std::string getInputDialogMessage()
{
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
jmethodID dialogvalue = jnienv->GetMethodID(activityClass,
"getDialogMessage", "()Ljava/lang/String;");
FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogMessage unable to find Java getDialogMessage method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
jobject result = jnienv->CallObjectMethod(activity,
dialogvalue);
return readJavaString((jstring) result);
}
int getInputDialogSelection()
{
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, "getDialogSelection", "()I");
jmethodID dialogvalue = jnienv->GetMethodID(activityClass, "getDialogSelection", "()I");
FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
return jnienv->CallIntMethod(app_global->activity->clazz, dialogvalue);
return jnienv->CallIntMethod(activity, dialogvalue);
}
#ifndef SERVER
@ -287,13 +256,13 @@ float getDisplayDensity()
static float value = 0;
if (firstrun) {
jmethodID getDensity = jnienv->GetMethodID(nativeActivity,
jmethodID getDensity = jnienv->GetMethodID(activityClass,
"getDensity", "()F");
FATAL_ERROR_IF(getDensity == nullptr,
"porting::getDisplayDensity unable to find Java getDensity method");
value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity);
value = jnienv->CallFloatMethod(activity, getDensity);
firstrun = false;
}
@ -306,22 +275,22 @@ v2u32 getDisplaySize()
static v2u32 retval;
if (firstrun) {
jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity,
jmethodID getDisplayWidth = jnienv->GetMethodID(activityClass,
"getDisplayWidth", "()I");
FATAL_ERROR_IF(getDisplayWidth == nullptr,
"porting::getDisplayWidth unable to find Java getDisplayWidth method");
retval.X = jnienv->CallIntMethod(app_global->activity->clazz,
retval.X = jnienv->CallIntMethod(activity,
getDisplayWidth);
jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity,
jmethodID getDisplayHeight = jnienv->GetMethodID(activityClass,
"getDisplayHeight", "()I");
FATAL_ERROR_IF(getDisplayHeight == nullptr,
"porting::getDisplayHeight unable to find Java getDisplayHeight method");
retval.Y = jnienv->CallIntMethod(app_global->activity->clazz,
retval.Y = jnienv->CallIntMethod(activity,
getDisplayHeight);
firstrun = false;
@ -332,16 +301,29 @@ v2u32 getDisplaySize()
std::string getLanguageAndroid()
{
jmethodID getLanguage = jnienv->GetMethodID(nativeActivity,
jmethodID getLanguage = jnienv->GetMethodID(activityClass,
"getLanguage", "()Ljava/lang/String;");
FATAL_ERROR_IF(getLanguage == nullptr,
"porting::getLanguageAndroid unable to find Java getLanguage method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
jobject result = jnienv->CallObjectMethod(activity,
getLanguage);
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
}

View File

@ -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!
#endif
#include <jni.h>
#include <android_native_app_glue.h>
#include <android/log.h>
#include "irrlichttypes_bloated.h"
#include <string>
namespace porting {
// Java app
extern android_app *app_global;
// Java <-> C++ interaction interface
extern JNIEnv *jnienv;
/**
* Show a text input dialog in Java
* @param hint Hint to be shown
@ -105,6 +94,9 @@ std::string getInputDialogMessage();
*/
int getInputDialogSelection();
bool hasPhysicalKeyboardAndroid();
#ifndef SERVER
float getDisplayDensity();
v2u32 getDisplaySize();

View File

@ -156,17 +156,24 @@ void read_item_definition(lua_State* L, int index,
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical);
TouchInteraction &inter = def.touch_interaction;
lua_getfield(L, index, "touch_interaction");
if (!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
TouchInteraction &inter = def.touch_interaction;
if (lua_istable(L, -1)) {
inter.pointed_nothing = (TouchInteractionMode)getenumfield(L, -1, "pointed_nothing",
es_TouchInteractionMode, inter.pointed_nothing);
inter.pointed_node = (TouchInteractionMode)getenumfield(L, -1, "pointed_node",
es_TouchInteractionMode, inter.pointed_node);
inter.pointed_object = (TouchInteractionMode)getenumfield(L, -1, "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);
}

View File

@ -25,6 +25,7 @@ extern "C" {
#include "util/numeric.h"
#include "util/serialize.h"
#include "util/string.h"
#include "log.h"
#include "common/c_converter.h"
#include "common/c_internal.h"
#include "constants.h"

Some files were not shown because too many files have changed in this diff Show More