significant work

This commit is contained in:
itsmattkc committed 2025-02-17 11:06:12 -08:00
1 parent 9b370947e1
commit 234b2604a8
113 files changed
+9810 -1072

No files matched your search

+5
View File
@@ -7,3 +7,8 @@ squashfs-root/
buildpi/
rpi-ffmpeg/
.gradle/
.cxx/
.idea/
android/local.properties
+10 -3
View File
@@ -1,23 +1,30 @@
cmake_minimum_required(VERSION 3.21)
project(Vanilla C CXX)
project(Vanilla C)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
SET(CMAKE_MODULE_PATH "../cmake")
set(CMAKE_C_STANDARD 23)
if(UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE AND NOT ANDROID)
set(LINUX TRUE)
endif()
if(ANDROID OR MSVC)
set(vendored_default TRUE)
else()
set(vendored_default FALSE)
endif()
OPTION(VANILLA_BUILD_DESKTOP "Build Qt app for Linux desktop" OFF)
OPTION(VANILLA_BUILD_RPI "Build SDL2 app for Raspberry Pi" ON)
OPTION(VANILLA_BUILD_TESTS "Build unit tests for Vanilla" OFF)
OPTION(VANILLA_BUILD_PIPE "Build vanilla-pipe for connecting to Wii U (Linux only)" ${LINUX})
OPTION(VANILLA_BUILD_VENDORED "Build Vanilla with \"vendored\" third-party libraries" ${vendored_default})
add_subdirectory(lib)
if (VANILLA_BUILD_PIPE)
+75
View File
@@ -0,0 +1,75 @@
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
def buildAsApplication = !buildAsLibrary
if (buildAsApplication) {
apply plugin: 'com.android.application'
}
else {
apply plugin: 'com.android.library'
}
android {
if (buildAsApplication) {
namespace "org.libsdl.app"
}
compileSdkVersion 34
defaultConfig {
minSdkVersion 28
targetSdkVersion 34
versionCode 1
versionName "1.0"
externalNativeBuild {
// ndkBuild {
// arguments "APP_PLATFORM=android-19"
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
// }
cmake {
arguments "-DANDROID_APP_PLATFORM=android-28", "-DANDROID_STL=c++_static"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
// abiFilters 'arm64-v8a'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
applicationVariants.all { variant ->
tasks["merge${variant.name.capitalize()}Assets"]
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
}
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
sourceSets.main {
jniLibs.srcDir 'libs'
}
externalNativeBuild {
// ndkBuild {
// path 'jni/Android.mk'
// }
cmake {
path 'jni/CMakeLists.txt'
}
}
}
lint {
abortOnError false
}
if (buildAsLibrary) {
libraryVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith(".aar")) {
def fileName = "org.libsdl.app.aar";
output.outputFile = new File(outputFile.parent, fileName);
}
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
+1
View File
@@ -0,0 +1 @@
include $(call all-subdir-makefiles)
+10
View File
@@ -0,0 +1,10 @@
# Uncomment this if you're using STL in your project
# You can find more information here:
# https://developer.android.com/ndk/guides/cpp-support
# APP_STL := c++_shared
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# Min runtime API level
APP_PLATFORM=android-28
+13
View File
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.11)
project(GAME)
# armeabi-v7a requires cpufeatures library
# include(AndroidNdkModules)
# android_ndk_import_module_cpufeatures()
# Your game and its CMakeLists.txt are in a subfolder named "src"
add_subdirectory(../../.. "${CMAKE_SOURCE_DIR}/build")
# add_subdirectory(src)
+18
View File
@@ -0,0 +1,18 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := main
SDL_PATH := ../SDL
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
# Add your application source files here...
LOCAL_SRC_FILES := YourSourceHere.c
LOCAL_SHARED_LIBRARIES := SDL2
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
include $(BUILD_SHARED_LIBRARY)
+9
View File
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.26)
add_custom_command(
TARGET vanilla-pi
POST_BUILD
COMMAND ${CMAKE_COMMAND}
ARGS -E copy_directory_if_different "${CMAKE_SOURCE_DIR}/../../folderA" "${PROJECT_SOURCE_DIR}/release/Debug/${PROJECT_NAME}.app/Contents/"
COMMENT "Copying Assets"
)
+98
View File
@@ -0,0 +1,98 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in [sdk]/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
void nativeCommitText(java.lang.String, int);
void nativeGenerateScancodeForUnichar(char);
}
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
# for some reason these aren't compatible with allowoptimization modifier
boolean supportsRelativeMouse();
void setWindowStyle(boolean);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
boolean onNativeSoftReturnKey();
void onNativeKeyboardFocusLost();
boolean isScreenKeyboardShown();
android.util.DisplayMetrics getDisplayDPI();
java.lang.String clipboardGetText();
boolean clipboardHasText();
void clipboardSetText(java.lang.String);
int createCustomCursor(int[], int, int, int, int);
void destroyCustomCursor(int);
android.content.Context getContext();
boolean getManifestEnvironmentVariables();
android.view.Surface getNativeSurface();
void initTouch();
boolean isAndroidTV();
boolean isChromebook();
boolean isDeXMode();
boolean isTablet();
void manualBackButton();
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
void minimizeWindow();
int openURL(java.lang.String);
void requestPermission(java.lang.String, int);
int showToast(java.lang.String, int, int, int, int);
boolean sendMessage(int, int);
boolean setActivityTitle(java.lang.String);
boolean setCustomCursor(int);
void setOrientation(int, int, boolean, java.lang.String);
boolean setRelativeMouseEnabled(boolean);
boolean setSystemCursor(int);
boolean shouldMinimizeOnFocusLoss();
boolean showTextInput(int, int, int, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
boolean initialize(boolean, boolean);
boolean openDevice(int);
int sendOutputReport(int, byte[]);
int sendFeatureReport(int, byte[]);
boolean getFeatureReport(int, byte[]);
void closeDevice(int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
int[] getAudioOutputDevices();
int[] getAudioInputDevices();
int[] audioOpen(int, int, int, int, int);
void audioWriteFloatBuffer(float[]);
void audioWriteShortBuffer(short[]);
void audioWriteByteBuffer(byte[]);
void audioClose();
int[] captureOpen(int, int, int, int, int);
int captureReadFloatBuffer(float[], boolean);
int captureReadShortBuffer(short[], boolean);
int captureReadByteBuffer(byte[], boolean);
void captureClose();
void audioSetThreadPriority(boolean, int);
native int nativeSetupJNI();
native void removeAudioDevice(boolean, int);
native void addAudioDevice(boolean, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
void pollInputDevices();
void pollHapticDevices();
void hapticRun(int, float, int);
void hapticStop(int);
}
+103
View File
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Replace com.test.game with the identifier of your game below, e.g.
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Audio recording support -->
<!-- if you want to capture audio, uncomment this. -->
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
<!-- Allow access to Bluetooth devices -->
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- if you want to capture audio, uncomment this. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- We need INTERNET permissions to open UDP sockets. -->
<uses-permission android:name="android.permission.INTERNET" android:required="true" />
<!-- Create a Java class extending SDLActivity and place it in a
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.md
-->
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true" >
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
-->
<activity android:name="SDLActivity"
android:label="@string/app_name"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:exported="true"
android:screenOrientation="landscape"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Let Android know that we can handle some USB devices and should receive this event -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!-- Drop file event -->
<!--
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
-->
</activity>
</application>
</manifest>
@@ -0,0 +1,22 @@
package org.libsdl.app;
import android.hardware.usb.UsbDevice;
interface HIDDevice
{
public int getId();
public int getVendorId();
public int getProductId();
public String getSerialNumber();
public int getVersion();
public String getManufacturerName();
public String getProductName();
public UsbDevice getDevice();
public boolean open();
public int sendFeatureReport(byte[] report);
public int sendOutputReport(byte[] report);
public boolean getFeatureReport(byte[] report);
public void setFrozen(boolean frozen);
public void close();
public void shutdown();
}
@@ -0,0 +1,650 @@
package org.libsdl.app;
import android.content.Context;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattService;
import android.hardware.usb.UsbDevice;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.os.*;
//import com.android.internal.util.HexDump;
import java.lang.Runnable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.UUID;
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
private static final String TAG = "hidapi";
private HIDDeviceManager mManager;
private BluetoothDevice mDevice;
private int mDeviceId;
private BluetoothGatt mGatt;
private boolean mIsRegistered = false;
private boolean mIsConnected = false;
private boolean mIsChromebook = false;
private boolean mIsReconnecting = false;
private boolean mFrozen = false;
private LinkedList<GattOperation> mOperations;
GattOperation mCurrentOperation = null;
private Handler mHandler;
private static final int TRANSPORT_AUTO = 0;
private static final int TRANSPORT_BREDR = 1;
private static final int TRANSPORT_LE = 2;
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
static class GattOperation {
private enum Operation {
CHR_READ,
CHR_WRITE,
ENABLE_NOTIFICATION
}
Operation mOp;
UUID mUuid;
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
}
public void run() {
// This is executed in main thread
BluetoothGattCharacteristic chr;
switch (mOp) {
case CHR_READ:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
if (!mGatt.readCharacteristic(chr)) {
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case CHR_WRITE:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
chr.setValue(mValue);
if (!mGatt.writeCharacteristic(chr)) {
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case ENABLE_NOTIFICATION:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
if (chr != null) {
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
int properties = chr.getProperties();
byte[] value;
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
} else {
Log.e(TAG, "Unable to start notifications on input characteristic");
mResult = false;
return;
}
mGatt.setCharacteristicNotification(chr, true);
cccd.setValue(value);
if (!mGatt.writeDescriptor(cccd)) {
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
mResult = false;
return;
}
mResult = true;
}
}
}
}
public boolean finish() {
return mResult;
}
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
return null;
return valveService.getCharacteristic(uuid);
}
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.CHR_READ, uuid);
}
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
}
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
}
}
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
mManager = manager;
mDevice = device;
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
mIsRegistered = false;
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
mOperations = new LinkedList<GattOperation>();
mHandler = new Handler(Looper.getMainLooper());
mGatt = connectGatt();
// final HIDDeviceBLESteamController finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.checkConnectionForChromebookIssue();
// }
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
public String getIdentifier() {
return String.format("SteamController.%s", mDevice.getAddress());
}
public BluetoothGatt getGatt() {
return mGatt;
}
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
private BluetoothGatt connectGatt(boolean managed) {
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
try {
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
} catch (Exception e) {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
} else {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
}
private BluetoothGatt connectGatt() {
return connectGatt(false);
}
protected int getConnectionState() {
Context context = mManager.getContext();
if (context == null) {
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
return BluetoothProfile.STATE_DISCONNECTED;
}
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
if (btManager == null) {
// This device doesn't support Bluetooth. We should never be here, because how did
// we instantiate a device to start with?
return BluetoothProfile.STATE_DISCONNECTED;
}
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
}
public void reconnect() {
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
mGatt.disconnect();
mGatt = connectGatt();
}
}
protected void checkConnectionForChromebookIssue() {
if (!mIsChromebook) {
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
// over and over.
return;
}
int connectionState = getConnectionState();
switch (connectionState) {
case BluetoothProfile.STATE_CONNECTED:
if (!mIsConnected) {
// We are in the Bad Chromebook Place. We can force a disconnect
// to try to recover.
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
else if (!isRegistered()) {
if (mGatt.getServices().size() > 0) {
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
probeService(this);
}
else {
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
}
else {
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
return;
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
case BluetoothProfile.STATE_CONNECTING:
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
break;
}
final HIDDeviceBLESteamController finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.checkConnectionForChromebookIssue();
}
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
private boolean isRegistered() {
return mIsRegistered;
}
private void setRegistered() {
mIsRegistered = true;
}
private boolean probeService(HIDDeviceBLESteamController controller) {
if (isRegistered()) {
return true;
}
if (!mIsConnected) {
return false;
}
Log.v(TAG, "probeService controller=" + controller);
for (BluetoothGattService service : mGatt.getServices()) {
if (service.getUuid().equals(steamControllerService)) {
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(inputCharacteristic)) {
Log.v(TAG, "Found input characteristic");
// Start notifications
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
enableNotification(chr.getUuid());
}
}
}
return true;
}
}
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
mIsConnected = false;
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
private void finishCurrentGattOperation() {
GattOperation op = null;
synchronized (mOperations) {
if (mCurrentOperation != null) {
op = mCurrentOperation;
mCurrentOperation = null;
}
}
if (op != null) {
boolean result = op.finish(); // TODO: Maybe in main thread as well?
// Our operation failed, let's add it back to the beginning of our queue.
if (!result) {
mOperations.addFirst(op);
}
}
executeNextGattOperation();
}
private void executeNextGattOperation() {
synchronized (mOperations) {
if (mCurrentOperation != null)
return;
if (mOperations.isEmpty())
return;
mCurrentOperation = mOperations.removeFirst();
}
// Run in main thread
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
mCurrentOperation.run();
// now wait for the GATT callback and when it comes, finish this operation
}
}
});
}
private void queueGattOperation(GattOperation op) {
synchronized (mOperations) {
mOperations.add(op);
}
executeNextGattOperation();
}
private void enableNotification(UUID chrUuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
queueGattOperation(op);
}
public void writeCharacteristic(UUID uuid, byte[] value) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
queueGattOperation(op);
}
public void readCharacteristic(UUID uuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
queueGattOperation(op);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////////// BluetoothGattCallback overridden methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
mIsReconnecting = false;
if (newState == 2) {
mIsConnected = true;
// Run directly, without GattOperation
if (!isRegistered()) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGatt.discoverServices();
}
});
}
}
else if (newState == 0) {
mIsConnected = false;
}
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onServicesDiscovered status=" + status);
if (status == 0) {
if (gatt.getServices().size() == 0) {
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
mIsReconnecting = true;
mIsConnected = false;
gatt.disconnect();
mGatt = connectGatt(false);
}
else {
probeService(this);
}
}
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
}
finishCurrentGattOperation();
}
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic)) {
// Only register controller with the native side once it has been fully configured
if (!isRegistered()) {
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
setRegistered();
}
}
finishCurrentGattOperation();
}
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// Enable this for verbose logging of controller input reports
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
}
}
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//Log.v(TAG, "onDescriptorRead status=" + status);
}
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
if (chr.getUuid().equals(inputCharacteristic)) {
boolean hasWrittenInputDescriptor = true;
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
if (reportChr != null) {
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
gatt.writeCharacteristic(reportChr);
}
}
finishCurrentGattOperation();
}
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
}
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
//Log.v(TAG, "onReadRemoteRssi status=" + status);
}
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
//Log.v(TAG, "onMtuChanged status=" + status);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////// Public API
//////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
// Valve Corporation
final int VALVE_USB_VID = 0x28DE;
return VALVE_USB_VID;
}
@Override
public int getProductId() {
// We don't have an easy way to query from the Bluetooth device, but we know what it is
final int D0G_BLE2_PID = 0x1106;
return D0G_BLE2_PID;
}
@Override
public String getSerialNumber() {
// This will be read later via feature report by Steam
return "12345";
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
return "Valve Corporation";
}
@Override
public String getProductName() {
return "Steam Controller";
}
@Override
public UsbDevice getDevice() {
return null;
}
@Override
public boolean open() {
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
// We need to skip the first byte, as that doesn't go over the air
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
}
@Override
public int sendOutputReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
}
@Override
public boolean getFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return false;
}
//Log.v(TAG, "getFeatureReport");
readCharacteristic(reportCharacteristic);
return true;
}
@Override
public void close() {
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
@Override
public void shutdown() {
close();
BluetoothGatt g = mGatt;
if (g != null) {
g.disconnect();
g.close();
mGatt = null;
}
mManager = null;
mIsRegistered = false;
mIsConnected = false;
mOperations.clear();
}
}
@@ -0,0 +1,702 @@
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);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mContext.registerReceiver(mUsbBroadcast, filter, Context.RECEIVER_EXPORTED);
} else {
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
0x0b05, // ASUS
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;
}
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
intent.setPackage(mContext.getPackageName());
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
} else {
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
}
} catch (Exception e) {
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
HIDDeviceOpenResult(deviceID, false);
}
return false;
}
try {
return device.open();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public int sendOutputReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendOutputReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public int sendFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public boolean getFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
return device.getFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public void closeDevice(int deviceID) {
try {
Log.v(TAG, "closeDevice deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return;
}
device.close();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////// Native methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
native void HIDDeviceOpenPending(int deviceID);
native void HIDDeviceOpenResult(int deviceID, boolean opened);
native void HIDDeviceDisconnected(int deviceID);
native void HIDDeviceInputReport(int deviceID, byte[] report);
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
}
@@ -0,0 +1,309 @@
package org.libsdl.app;
import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
class HIDDeviceUSB implements HIDDevice {
private static final String TAG = "hidapi";
protected HIDDeviceManager mManager;
protected UsbDevice mDevice;
protected int mInterfaceIndex;
protected int mInterface;
protected int mDeviceId;
protected UsbDeviceConnection mConnection;
protected UsbEndpoint mInputEndpoint;
protected UsbEndpoint mOutputEndpoint;
protected InputThread mInputThread;
protected boolean mRunning;
protected boolean mFrozen;
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
mManager = manager;
mDevice = usbDevice;
mInterfaceIndex = interface_index;
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
mRunning = false;
}
public String getIdentifier() {
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
}
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
return mDevice.getVendorId();
}
@Override
public int getProductId() {
return mDevice.getProductId();
}
@Override
public String getSerialNumber() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
try {
result = mDevice.getSerialNumber();
}
catch (SecurityException exception) {
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
}
}
if (result == null) {
result = "";
}
return result;
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getManufacturerName();
}
if (result == null) {
result = String.format("%x", getVendorId());
}
return result;
}
@Override
public String getProductName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getProductName();
}
if (result == null) {
result = String.format("%x", getProductId());
}
return result;
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
public String getDeviceName() {
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
}
@Override
public boolean open() {
mConnection = mManager.getUSBManager().openDevice(mDevice);
if (mConnection == null) {
Log.w(TAG, "Unable to open USB device " + getDeviceName());
return false;
}
// Force claim our interface
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
if (!mConnection.claimInterface(iface, true)) {
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
close();
return false;
}
// Find the endpoints
for (int j = 0; j < iface.getEndpointCount(); j++) {
UsbEndpoint endpt = iface.getEndpoint(j);
switch (endpt.getDirection()) {
case UsbConstants.USB_DIR_IN:
if (mInputEndpoint == null) {
mInputEndpoint = endpt;
}
break;
case UsbConstants.USB_DIR_OUT:
if (mOutputEndpoint == null) {
mOutputEndpoint = endpt;
}
break;
}
}
// Make sure the required endpoints were present
if (mInputEndpoint == null || mOutputEndpoint == null) {
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
close();
return false;
}
// Start listening for input
mRunning = true;
mInputThread = new InputThread();
mInputThread.start();
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
0x09/*HID set_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
return -1;
}
if (skipped_report_id) {
++length;
}
return length;
}
@Override
public int sendOutputReport(byte[] report) {
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (r != report.length) {
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
}
return r;
}
@Override
public boolean getFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
will remain in byte 0. */
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
return false;
}
if (skipped_report_id) {
++res;
++length;
}
byte[] data;
if (res == length) {
data = report;
} else {
data = Arrays.copyOfRange(report, 0, res);
}
mManager.HIDDeviceFeatureReport(mDeviceId, data);
return true;
}
@Override
public void close() {
mRunning = false;
if (mInputThread != null) {
while (mInputThread.isAlive()) {
mInputThread.interrupt();
try {
mInputThread.join();
} catch (InterruptedException e) {
// Keep trying until we're done
}
}
mInputThread = null;
}
if (mConnection != null) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
mConnection.close();
mConnection = null;
}
}
@Override
public void shutdown() {
close();
mManager = null;
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
protected class InputThread extends Thread {
@Override
public void run() {
int packetSize = mInputEndpoint.getMaxPacketSize();
byte[] packet = new byte[packetSize];
while (mRunning) {
int r;
try
{
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
}
catch (Exception e)
{
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
break;
}
if (r < 0) {
// Could be a timeout or an I/O error
}
if (r > 0) {
byte[] data;
if (r == packetSize) {
data = packet;
} else {
data = Arrays.copyOfRange(packet, 0, r);
}
if (!mFrozen) {
mManager.HIDDeviceInputReport(mDeviceId, data);
}
}
}
}
}
}
@@ -0,0 +1,90 @@
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 {
loadLibrary(libraryName, mContext);
}
public static void loadLibrary(String libraryName, Context context) 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 = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
Class<?> stringClass = context.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, context, libraryName, null, null);
}
catch (final Throwable e) {
// Fall back
try {
System.loadLibrary(libraryName);
}
catch (final UnsatisfiedLinkError ule) {
throw ule;
}
catch (final SecurityException se) {
throw se;
}
}
}
protected static Context mContext;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,514 @@
package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
public class SDLAudioManager {
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
protected static Context mContext;
private static final int[] NO_DEVICES = {};
private static AudioDeviceCallback mAudioDeviceCallback;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
{
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
};
}
}
public static void setContext(Context context) {
mContext = context;
if (context != null) {
registerAudioDeviceCallback();
}
}
public static void release(Context context) {
unregisterAudioDeviceCallback(context);
}
// Audio
protected static String getAudioFormatString(int audioFormat) {
switch (audioFormat) {
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
}
}
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
int channelConfig;
int sampleSize;
int frameSize;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
/* On older devices let's use known good settings */
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
sampleRate = 48000;
}
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
}
switch (audioFormat)
{
case AudioFormat.ENCODING_PCM_8BIT:
sampleSize = 1;
break;
case AudioFormat.ENCODING_PCM_16BIT:
sampleSize = 2;
break;
case AudioFormat.ENCODING_PCM_FLOAT:
sampleSize = 4;
break;
default:
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
sampleSize = 2;
break;
}
if (isCapture) {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_IN_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
}
} else {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
case 3:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 4:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
break;
case 5:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
desiredChannels = 6;
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
}
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
}
/*
Log.v(TAG, "Speaker configuration (and order of channels):");
if ((channelConfig & 0x00000004) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
}
if ((channelConfig & 0x00000008) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
}
if ((channelConfig & 0x00000010) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
}
if ((channelConfig & 0x00000020) != 0) {
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
}
if ((channelConfig & 0x00000040) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
}
if ((channelConfig & 0x00000080) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
}
if ((channelConfig & 0x00000100) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
}
if ((channelConfig & 0x00000200) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
}
if ((channelConfig & 0x00000400) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
}
if ((channelConfig & 0x00000800) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
}
if ((channelConfig & 0x00001000) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
}
*/
}
frameSize = (sampleSize * desiredChannels);
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
int minBufferSize;
if (isCapture) {
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
} else {
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
}
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
int[] results = new int[4];
if (isCapture) {
if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
}
mAudioRecord.startRecording();
}
results[0] = mAudioRecord.getSampleRate();
results[1] = mAudioRecord.getAudioFormat();
results[2] = mAudioRecord.getChannelCount();
} else {
if (mAudioTrack == null) {
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
/* Try again, with safer values */
Log.e(TAG, "Failed during initialization of Audio Track");
mAudioTrack.release();
mAudioTrack = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
}
mAudioTrack.play();
}
results[0] = mAudioTrack.getSampleRate();
results[1] = mAudioTrack.getAudioFormat();
results[2] = mAudioTrack.getChannelCount();
}
results[3] = desiredFrames;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
return results;
}
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static void registerAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
}
}
private static void unregisterAudioDeviceCallback(Context context) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioOutputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioInputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteFloatBuffer(float[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(float)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteShortBuffer(short[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(short)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteByteBuffer(byte[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(byte)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return 0;
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
/** This method is called by SDL using JNI. */
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
try {
/* Set thread name */
if (iscapture) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
}
/* Set thread priority */
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
} catch (Exception e) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
}
public static native int nativeSetupJNI();
public static native void removeAudioDevice(boolean isCapture, int deviceId);
public static native void addAudioDevice(boolean isCapture, int deviceId);
}
@@ -0,0 +1,856 @@
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 != null) {
if (vib.hasVibrator()) {
haptic = new SDLHaptic();
haptic.device_id = deviceIds[i];
haptic.name = device.getName();
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
}
/* Check VIBRATOR_SERVICE */
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vib != null) {
hasVibratorService = vib.hasVibrator();
if (hasVibratorService) {
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
if (haptic == null) {
haptic = new SDLHaptic();
haptic.device_id = deviceId_VIBRATOR_SERVICE;
haptic.name = "VIBRATOR_SERVICE";
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = null;
for (SDLHaptic haptic : mHaptics) {
int device_id = haptic.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
}
} // else: don't remove the vibrator if it is still present
}
if (removedDevices != null) {
for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveHaptic(device_id);
for (int i = 0; i < mHaptics.size(); i++) {
if (mHaptics.get(i).device_id == device_id) {
mHaptics.remove(i);
break;
}
}
}
}
}
protected SDLHaptic getHaptic(int device_id) {
for (SDLHaptic haptic : mHaptics) {
if (haptic.device_id == device_id) {
return haptic;
}
}
return null;
}
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
// Generic Motion (mouse hover, joystick...) events go here
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
public boolean supportsRelativeMouse() {
return false;
}
public boolean inRelativeMode() {
return false;
}
public boolean setRelativeMouseEnabled(boolean enabled) {
return false;
}
public void reclaimRelativeMouseModeIfNeeded()
{
}
public float getEventX(MotionEvent event) {
return event.getX(0);
}
public float getEventY(MotionEvent event) {
return event.getY(0);
}
}
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
// Handle relative mouse mode
if (mRelativeModeEnabled) {
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_HOVER_MOVE) {
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
}
}
}
// Event was not managed, call SDLGenericMotionListener_API12 method
return super.onGenericMotion(v, event);
}
@Override
public boolean supportsRelativeMouse() {
return true;
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
mRelativeModeEnabled = enabled;
return true;
}
@Override
public float getEventX(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
} else {
return event.getX(0);
}
}
@Override
public float getEventY(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
} else {
return event.getY(0);
}
}
}
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
// DeX desktop mouse cursor is a separate non-standard input type.
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
case InputDevice.SOURCE_MOUSE_RELATIVE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
@Override
public boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
} else {
SDLActivity.getContentView().releasePointerCapture();
}
mRelativeModeEnabled = enabled;
return true;
} else {
return false;
}
}
@Override
public void reclaimRelativeMouseModeIfNeeded()
{
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
SDLActivity.getContentView().requestPointerCapture();
}
}
@Override
public float getEventX(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getX(0);
}
@Override
public float getEventY(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getY(0);
}
}
@@ -0,0 +1,405 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady;
// Startup
public SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
public void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
try
{
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
}
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip in MultiWindow.
if (skip) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
Log.v("SDL", "Don't skip in Multi-Window");
skip = false;
}
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int i = -1;
float x,y,p;
/*
* Prevent id to be -1, since it's used in SDL internal for synthetic events
* Appears when using Android emulator, eg:
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
*/
if (touchDevId < 0) {
touchDevId -= 1;
}
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
int mouseButton = 1;
try {
Object object = event.getClass().getMethod("getButtonState").invoke(event);
if (object != null) {
mouseButton = (Integer) object;
}
} catch(Exception ignored) {
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event);
y = motionListener.getEventY(event);
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
/* fallthrough */
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
return true;
}
// Sensor events
public void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
} else {
mSensorManager.unregisterListener(this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newOrientation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
break;
}
if (newOrientation != SDLActivity.mCurrentOrientation) {
SDLActivity.mCurrentOrientation = newOrientation;
SDLActivity.onNativeOrientationChanged(newOrientation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Captured pointer events for API 26.
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(0);
y = event.getY(0);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
return false;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Game</string>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
<!-- Customize your theme here. -->
</style>
</resources>
+25
View File
@@ -0,0 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
google()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+17
View File
@@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+252
View File
@@ -0,0 +1,252 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1
View File
@@ -0,0 +1 @@
include ':app'
+25
View File
@@ -0,0 +1,25 @@
FROM ubuntu:24.04
ENV NDK_VERSION="android-ndk-r28"
# Installing packages and glibc
RUN apt update && \
apt -y install wget unzip git && \
apt-get clean && \
apt-get autoremove --yes && \
rm -rf /var/lib/{apt,dpkg,cache,log}/
# Download and unzip the Android NDK
RUN wget -O /${NDK_VERSION}.zip https://dl.google.com/android/repository/${NDK_VERSION}-linux.zip && \
unzip /${NDK_VERSION}.zip && \
rm /${NDK_VERSION}.zip
# Build SDL2
RUN git clone --depth 1 --branch release-2.30.12 https://github.com/libsdl-org/SDL.git
# Build FFmpeg
# Build
# Add ndk-build to the search path
ENV PATH="/${NDK_VERSION}/:${PATH}"
+6 -2
View File
@@ -1,7 +1,11 @@
#include "audio.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <byteswap.h>
#endif
#include <pthread.h>
#include <stdint.h>
@@ -48,7 +52,7 @@ void handle_audio_packet(gamepad_context_t *ctx, unsigned char *data, size_t len
// ap->format = reverse_bits(ap->format, 3);
// ap->seq_id = reverse_bits(ap->seq_id, 10);
ap->payload_size = bswap_16(ap->payload_size);
ap->payload_size = ntohs(ap->payload_size);
// ap->timestamp = reverse_bits(ap->timestamp, 32);
if (ap->type == TYPE_VIDEO) {
+20 -4
View File
@@ -1,6 +1,11 @@
#include "command.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
@@ -209,6 +214,7 @@ enum MethodIDSoftware
enum MethodIDSystem
{
METHOD_ID_SYSTEM_GET_INFO = 0x4,
METHOD_ID_SYSTEM_POWER = 0x1A,
};
enum MethodIDPeripheral
@@ -245,7 +251,7 @@ void send_generic_response(int skt, CmdHeader *response)
send_to_console(skt, response, response->payload_size + sizeof(CmdHeader), PORT_CMD);
}
void handle_generic_packet(int skt, GenericPacket *request)
void handle_generic_packet(gamepad_context_t *info, int skt, GenericPacket *request)
{
GenericCmdHeader *gen_cmd = &request->generic_cmd_header;
print_info("magic: %x, flags: %x, service ID: %u, method ID: %u", gen_cmd->magic_0x7E, gen_cmd->flags, gen_cmd->service_id, gen_cmd->method_id);
@@ -311,6 +317,15 @@ void handle_generic_packet(int skt, GenericPacket *request)
response.generic_cmd_header.error_code = 0;
break;
}
case METHOD_ID_SYSTEM_POWER:
{
if (gen_cmd->flags == 0x42) {
// Console told us to poweroff
int err = VANILLA_ERR_SHUTDOWN;
push_event(info->event_loop, VANILLA_EVENT_ERROR, &err, sizeof(int));
}
break;
}
}
break;
case SERVICE_ID_PERIPHERAL:
@@ -376,7 +391,7 @@ void handle_time_packet(int skt, TimePacket *request)
send_quick_response(skt, &request->cmd_header);
}
void handle_command_packet(int skt, CmdHeader *request)
void handle_command_packet(gamepad_context_t *info, int skt, CmdHeader *request)
{
switch (request->packet_type)
{
@@ -386,7 +401,7 @@ void handle_command_packet(int skt, CmdHeader *request)
{
case CMD_GENERIC:
{
handle_generic_packet(skt, (GenericPacket *)request);
handle_generic_packet(info, skt, (GenericPacket *)request);
break;
}
case CMD_UVC_UAC:
@@ -409,6 +424,7 @@ void handle_command_packet(int skt, CmdHeader *request)
{
default:
// print_info("[Command] Unhandled request command: %u", request->query_type);
break;
}
break;
case PACKET_TYPE_REQUEST_ACK:
@@ -432,7 +448,7 @@ void *listen_command(void *x)
size = recv(info->socket_cmd, data, sizeof(data), 0);
if (size > 0) {
CmdHeader *header = (CmdHeader *)data;
handle_command_packet(info->socket_cmd, header);
handle_command_packet(info, info->socket_cmd, header);
}
} while (!is_interrupted());
+109 -52
View File
@@ -2,17 +2,22 @@
#include "gamepad.h"
#ifdef _WIN32
#include <winsock2.h>
typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;
#else
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/un.h>
#endif
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <unistd.h>
#include "audio.h"
@@ -34,9 +39,20 @@ pthread_mutex_t event_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef union {
struct sockaddr_in in;
#ifndef _WIN32
struct sockaddr_un un;
#endif
} sockaddr_u;
static inline int skterr()
{
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
in_addr_t get_real_server_address()
{
if (SERVER_ADDRESS == VANILLA_ADDRESS_DIRECT) {
@@ -44,29 +60,35 @@ in_addr_t get_real_server_address()
return inet_addr("192.168.1.10");
} else if (SERVER_ADDRESS != VANILLA_ADDRESS_LOCAL) {
// If there's a remote pipe, we send to that
return htonl(SERVER_ADDRESS);
return SERVER_ADDRESS;
}
// Must be local, in which case we don't care
return INADDR_ANY;
}
void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port)
void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int delete)
{
#ifndef _WIN32
if (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) {
memset(&addr->un, 0, sizeof(addr->un));
addr->un.sun_family = AF_UNIX;
snprintf(addr->un.sun_path, sizeof(addr->un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port);
if (delete)
unlink(addr->un.sun_path);
if (size) *size = sizeof(struct sockaddr_un);
} else {
#endif
memset(&addr->in, 0, sizeof(addr->in));
addr->in.sin_family = AF_INET;
addr->in.sin_port = htons(port);
addr->in.sin_addr.s_addr = inaddr;
if (size) *size = sizeof(struct sockaddr_in);
#ifndef _WIN32
}
#endif
}
void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
@@ -76,42 +98,53 @@ void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
in_port_t console_port = port - 100;
create_sockaddr(&addr, &addr_size, get_real_server_address(), console_port);
create_sockaddr(&addr, &addr_size, get_real_server_address(), console_port, 0);
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) &addr, addr_size);
if (sent == -1) {
print_info("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, errno);
print_info("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, skterr());
}
}
void set_socket_rcvtimeo(int skt, uint64_t microseconds)
{
#ifdef _WIN32
DWORD millis = microseconds / 1000;
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, (const char *) &millis, sizeof(millis));
#else
struct timeval tv = {0};
tv.tv_sec = microseconds / 1000000;
tv.tv_usec = microseconds % 1000000;
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif // _WIN32
}
int create_socket(int *socket_out, in_port_t port)
{
sockaddr_u addr;
size_t addr_size;
create_sockaddr(&addr, &addr_size, INADDR_ANY, port);
create_sockaddr(&addr, &addr_size, INADDR_ANY, port, 1);
int domain = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? AF_UNIX : AF_INET;
int skt = socket(domain, SOCK_DGRAM, 0);
if (skt == -1) {
print_info("FAILED TO CREATE SOCKET: %i", errno);
print_info("FAILED TO CREATE SOCKET: %i", skterr());
return VANILLA_ERR_BAD_SOCKET;
}
if (bind(skt, (const struct sockaddr *) &addr, addr_size) == -1) {
print_info("FAILED TO BIND PORT %u: %i", port, errno);
print_info("FAILED TO BIND PORT %u: %i", port, skterr());
close(skt);
return VANILLA_ERR_BAD_SOCKET;
}
print_info("SUCCESSFULLY BOUND SOCKET %i ON PORT %i", skt, port);
// print_info("SUCCESSFULLY BOUND SOCKET %i ON PORT %i", skt, port);
(*socket_out) = skt;
struct timeval tv = {0};
tv.tv_usec = 250000;
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
set_socket_rcvtimeo(skt, 250000);
return VANILLA_SUCCESS;
}
@@ -121,13 +154,13 @@ int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait
sockaddr_u addr;
size_t addr_size;
create_sockaddr(&addr, &addr_size, get_real_server_address(), VANILLA_PIPE_CMD_SERVER_PORT);
create_sockaddr(&addr, &addr_size, get_real_server_address(), VANILLA_PIPE_CMD_SERVER_PORT, 0);
ssize_t read_size;
uint8_t recv_cc;
for (int retries = 0; retries < MAX_PIPE_RETRY; retries++) {
if (sendto(skt, cmd, cmd_size, 0, (const struct sockaddr *) &addr, addr_size) == -1) {
if (sendto(skt, (const char *) cmd, cmd_size, 0, (const struct sockaddr *) &addr, addr_size) == -1) {
print_info("Failed to write control code to socket");
return 0;
}
@@ -165,9 +198,7 @@ int connect_to_backend(int *socket, vanilla_pipe_command_t *cmd, size_t cmd_size
return ret;
}
struct timeval tv = {0};
tv.tv_sec = 2;
setsockopt(pipe_cc_skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
set_socket_rcvtimeo(pipe_cc_skt, 2000000);
if (!send_pipe_cc(pipe_cc_skt, cmd, cmd_size, 1)) {
print_info("FAILED TO BIND TO PIPE");
@@ -210,7 +241,7 @@ void sync_internal(thread_data_t *data)
vanilla_pipe_command_t recv_cmd;
syncdata.status = VANILLA_ERR_PIPE_UNRESPONSIVE;
for (int retries = 0; retries < MAX_PIPE_RETRY; retries++) {
ssize_t read_size = recv(skt, &recv_cmd, sizeof(recv_cmd), 0);
ssize_t read_size = recv(skt, (char *) &recv_cmd, sizeof(recv_cmd), 0);
if (recv_cmd.control_code == VANILLA_PIPE_CC_STATUS) {
syncdata.status = (int32_t) ntohl(recv_cmd.status.status);
@@ -257,6 +288,13 @@ void connect_as_gamepad_internal(thread_data_t *data)
int ret = VANILLA_SUCCESS;
// Open all required sockets
if (create_socket(&info.socket_vid, PORT_VID) != VANILLA_SUCCESS) goto exit_pipe;
if (create_socket(&info.socket_msg, PORT_MSG) != VANILLA_SUCCESS) goto exit_vid;
if (create_socket(&info.socket_hid, PORT_HID) != VANILLA_SUCCESS) goto exit_msg;
if (create_socket(&info.socket_aud, PORT_AUD) != VANILLA_SUCCESS) goto exit_hid;
if (create_socket(&info.socket_cmd, PORT_CMD) != VANILLA_SUCCESS) goto exit_aud;
int pipe_cc_skt = -1;
if (SERVER_ADDRESS != VANILLA_ADDRESS_DIRECT) {
vanilla_pipe_command_t cmd;
@@ -267,40 +305,40 @@ void connect_as_gamepad_internal(thread_data_t *data)
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
// if (ret == VANILLA_SUCCESS) {
// // Wait for backend to be available
// vanilla_pipe_command_t connected_state;
// ret = VANILLA_ERR_NO_CONNECTION;
// while (!is_interrupted()) {
// ssize_t read_size = recv(pipe_cc_skt, &connected_state, sizeof(connected_state), 0);
// if (read_size < 0) {
// if (errno != EAGAIN) {
// ret = VANILLA_ERR_PIPE_UNRESPONSIVE;
// break;
// }
// } else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) {
// ret = VANILLA_SUCCESS;
// sleep(1);
// break;
// }
if (ret == VANILLA_SUCCESS) {
// Wait for backend to be available
vanilla_pipe_command_t connected_state;
ret = VANILLA_ERR_NO_CONNECTION;
while (!is_interrupted()) {
ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0);
if (read_size < 0) {
int r = skterr();
#ifdef _WIN32
if (r != WSAETIMEDOUT) {
#else
if (r != EAGAIN) {
#endif
print_info("FAILED TO GET CONNECTED STATE: %i", r);
ret = VANILLA_ERR_PIPE_UNRESPONSIVE;
break;
}
} else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) {
ret = VANILLA_SUCCESS;
sleep(1);
break;
}
// print_info("STILL WAITING FOR CONNECTED STATE");
// sleep(1);
// }
// }
print_info("STILL WAITING FOR CONNECTED STATE");
}
}
}
if (ret == VANILLA_SUCCESS) {
// Open all required sockets
if (create_socket(&info.socket_vid, PORT_VID) != VANILLA_SUCCESS) goto exit_pipe;
if (create_socket(&info.socket_msg, PORT_MSG) != VANILLA_SUCCESS) goto exit_vid;
if (create_socket(&info.socket_hid, PORT_HID) != VANILLA_SUCCESS) goto exit_msg;
if (create_socket(&info.socket_aud, PORT_AUD) != VANILLA_SUCCESS) goto exit_hid;
if (create_socket(&info.socket_cmd, PORT_CMD) != VANILLA_SUCCESS) goto exit_aud;
pthread_t video_thread, audio_thread, input_thread, msg_thread, cmd_thread;
int cnn = VANILLA_ERR_CONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
pthread_create(&video_thread, NULL, listen_video, &info);
pthread_setname_np(video_thread, "vanilla-video");
@@ -314,8 +352,20 @@ void connect_as_gamepad_internal(thread_data_t *data)
pthread_setname_np(cmd_thread, "vanilla-cmd");
while (!is_interrupted()) {
// TODO: Should use a wake condition instead
usleep(250 * 1000);
vanilla_pipe_command_t pipe_state;
ssize_t read_size = recv(pipe_cc_skt, (char *) &pipe_state, sizeof(pipe_state), 0);
if (read_size > 0) {
switch (pipe_state.control_code) {
case VANILLA_PIPE_CC_DISCONNECTED:
cnn = VANILLA_ERR_DISCONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
break;
case VANILLA_PIPE_CC_CONNECTED:
cnn = VANILLA_ERR_CONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
break;
}
}
}
pthread_join(video_thread, NULL);
@@ -357,6 +407,8 @@ exit:
int push_event(event_loop_t *loop, int type, const void *data, size_t size)
{
int ret = VANILLA_SUCCESS;
pthread_mutex_lock(&loop->mutex);
if (size <= EVENT_BUFFER_SIZE) {
@@ -374,7 +426,8 @@ int push_event(event_loop_t *loop, int type, const void *data, size_t size)
ev->data = get_event_buffer();
if (!ev->data) {
print_info("OUT OF MEMORY FOR NEW EVENTS");
return VANILLA_ERR_OUT_OF_MEMORY;
ret = VANILLA_ERR_OUT_OF_MEMORY;
goto exit;
}
ev->type = type;
@@ -386,9 +439,13 @@ int push_event(event_loop_t *loop, int type, const void *data, size_t size)
pthread_cond_broadcast(&loop->waitcond);
} else {
print_info("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.\n", size, EVENT_BUFFER_SIZE);
ret = VANILLA_ERR_INVALID_ARGUMENT;
}
exit:
pthread_mutex_unlock(&loop->mutex);
return ret;
}
int get_event(event_loop_t *loop, vanilla_event_t *event, int wait)
+7 -2
View File
@@ -1,6 +1,11 @@
#include "input.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <math.h>
#include <pthread.h>
#include <stdint.h>
@@ -96,8 +101,8 @@ uint16_t resolve_axis_value(float axis, float neg, float pos, int flip)
{
float val = axis < 0 ? axis / 32768.0f : axis / 32767.0f;
neg = abs(neg);
pos = abs(pos);
neg = fabs(neg);
pos = fabs(pos);
neg /= 32767.0f;
pos /= 32767.0f;
+24 -1
View File
@@ -1,12 +1,17 @@
#include "video.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif // _WIN32
#include <assert.h>
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
@@ -42,6 +47,10 @@ static uint8_t video_packet[100000];
static size_t generated_sps_params_size = 0;
static const uint8_t VANILLA_PPS_PARAMS[] = {
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x06, 0x0c, 0xe8
};
void request_idr()
{
pthread_mutex_lock(&video_mutex);
@@ -80,7 +89,15 @@ void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, i
int is_idr = 0;
for (int i = 0; i < sizeof(vp->extended_header); i++) {
if (vp->extended_header[i] == 0x80) {
// print_info("GOT IDR");
is_idr = 1;
// pthread_mutex_lock(&video_mutex);
// if (idr_is_queued) {
// idr_is_queued = 0;
// }
// pthread_mutex_unlock(&video_mutex);
break;
}
}
@@ -484,3 +501,9 @@ size_t generate_sps_params(void *data, size_t size)
return bit_index / align;
}
size_t generate_pps_params(void *data, size_t size)
{
memcpy(data, VANILLA_PPS_PARAMS, MIN(sizeof(VANILLA_PPS_PARAMS), size));
return sizeof(VANILLA_PPS_PARAMS);
}
+1
View File
@@ -7,6 +7,7 @@
void *listen_video(void *x);
void request_idr();
size_t generate_sps_params(void *data, size_t size);
size_t generate_pps_params(void *data, size_t size);
void write_bits(void *data, size_t size, size_t *bit_index, uint8_t value, size_t bit_width);
void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_t value);
-5
View File
@@ -14,11 +14,6 @@ static const char *VANILLA_STATUS_STRINGS[] = {
"INVALID_ARGUMENT"
};
void print_status(int errno)
{
vanilla_log("%s", VANILLA_STATUS_STRINGS[-errno]);
}
void print_info(const char *errstr, ...)
{
va_list args;
-1
View File
@@ -1,7 +1,6 @@
#ifndef VANILLA_SERVER_STATUS_H
#define VANILLA_SERVER_STATUS_H
void print_status(int errno);
void print_info(const char *str, ...);
#endif // VANILLA_SERVER_STATUS_H
+34
View File
@@ -2,6 +2,10 @@
#include "vanilla.h"
#ifdef _WIN32
#include <winsock2.h>
#endif // _WIN32
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
@@ -24,6 +28,19 @@ void *start_event_loop(void *arg)
{
thread_data_t *data = (thread_data_t *) arg;
#ifdef _WIN32
{
WSADATA wsaData;
int r = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (r != 0) {
char buf[100];
print_info("Failed to WSAStartup: %i\n", r);
pthread_mutex_unlock(&main_mutex);
goto exit;
}
}
#endif // _WIN32
pthread_mutex_lock(&event_loop.mutex);
init_event_buffer_arena();
event_loop.active = 1;
@@ -38,6 +55,7 @@ void *start_event_loop(void *arg)
data->event_loop = &event_loop;
data->thread_start(data);
free(data);
pthread_mutex_lock(&event_loop.mutex);
@@ -55,6 +73,11 @@ void *start_event_loop(void *arg)
pthread_cond_broadcast(&event_loop.waitcond);
pthread_mutex_unlock(&event_loop.mutex);
#ifdef _WIN32
WSACleanup();
#endif // _WIN32
exit:
pthread_mutex_unlock(&main_mutex);
return 0;
}
@@ -193,4 +216,15 @@ int vanilla_free_event(vanilla_event_t *event)
release_event_buffer(event->data);
event->data = NULL;
}
return VANILLA_SUCCESS;
}
size_t vanilla_generate_sps_params(void *data, size_t data_size)
{
return generate_sps_params(data, data_size);
}
size_t vanilla_generate_pps_params(void *data, size_t data_size)
{
return generate_pps_params(data, data_size);
}
+19 -18
View File
@@ -9,15 +9,18 @@ extern "C" {
#include <stddef.h>
#include <stdint.h>
static const int VANILLA_SUCCESS = 0;
static const int VANILLA_ERR_GENERIC = -1;
static const int VANILLA_ERR_UNKNOWN_COMMAND = -2;
static const int VANILLA_ERR_INVALID_ARGUMENT = -3;
static const int VANILLA_ERR_PIPE_UNRESPONSIVE = -4;
static const int VANILLA_ERR_OUT_OF_MEMORY = -5;
static const int VANILLA_ERR_BUSY = -6;
static const int VANILLA_ERR_BAD_SOCKET = -7;
static const int VANILLA_ERR_NO_CONNECTION = -8;
#define VANILLA_SUCCESS 0
#define VANILLA_ERR_GENERIC -1
#define VANILLA_ERR_UNKNOWN_COMMAND -2
#define VANILLA_ERR_INVALID_ARGUMENT -3
#define VANILLA_ERR_PIPE_UNRESPONSIVE -4
#define VANILLA_ERR_OUT_OF_MEMORY -5
#define VANILLA_ERR_BUSY -6
#define VANILLA_ERR_BAD_SOCKET -7
#define VANILLA_ERR_NO_CONNECTION -8
#define VANILLA_ERR_SHUTDOWN -9
#define VANILLA_ERR_CONNECTED -10
#define VANILLA_ERR_DISCONNECTED -11
static const uint32_t VANILLA_ADDRESS_LOCAL = 0xFFFFFFFF;
static const uint32_t VANILLA_ADDRESS_DIRECT = 0x0;
@@ -71,7 +74,7 @@ enum VanillaEvent
VANILLA_EVENT_AUDIO,
VANILLA_EVENT_VIBRATE,
VANILLA_EVENT_SYNC,
VANILLA_EVENT_ERROR,
VANILLA_EVENT_ERROR
};
enum VanillaRegion
@@ -95,14 +98,6 @@ enum VanillaBatteryStatus {
VANILLA_BATTERY_STATUS_FULL = 6
};
static const uint8_t VANILLA_SPS_PARAMS[] = {
0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x20, 0xac, 0x2b, 0x40, 0x6c, 0x1e, 0xf3, 0x68,
};
static const uint8_t VANILLA_PPS_PARAMS[] = {
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x06, 0x0c, 0xe8
};
typedef struct
{
int type;
@@ -193,6 +188,12 @@ void vanilla_set_region(int region);
*/
void vanilla_set_battery_status(int battery_status);
/**
* Retrieve SPS/PPS parameters for H.264 packeting
*/
size_t vanilla_generate_sps_params(void *data, size_t data_size);
size_t vanilla_generate_pps_params(void *data, size_t data_size);
#if defined(__cplusplus)
}
#endif
+2
View File
@@ -15,6 +15,8 @@
#define VANILLA_PIPE_CC_UNBIND 0x86
#define VANILLA_PIPE_CC_SYNC_SUCCESS 0x87
#define VANILLA_PIPE_CC_CONNECTED 0x88
#define VANILLA_PIPE_CC_DISCONNECTED 0x89
#define VANILLA_PIPE_CC_QUIT 0x90
#define VANILLA_PIPE_LOCAL_SOCKET "/tmp/vanilla-pipe_%i.sock"
+85 -39
View File
@@ -30,8 +30,10 @@ const char *wpa_ctrl_interface = "/var/run/wpa_supplicant_drc";
pthread_mutex_t running_mutex;
pthread_mutex_t main_loop_mutex;
pthread_mutex_t action_mutex;
pthread_mutex_t relay_mutex;
int running = 0;
int main_loop = 0;
int relay_running = 0;
typedef union {
struct sockaddr_in in;
@@ -101,6 +103,14 @@ int is_interrupted()
return r;
}
int are_relays_running()
{
pthread_mutex_lock(&relay_mutex);
int r = relay_running;
pthread_mutex_unlock(&relay_mutex);
return r;
}
void wpa_msg(char *msg, size_t len)
{
print_info("%.*s", len, msg);
@@ -323,6 +333,13 @@ void interrupt()
pthread_mutex_unlock(&running_mutex);
}
void interrupt_relays()
{
pthread_mutex_lock(&relay_mutex);
relay_running = 0;
pthread_mutex_unlock(&relay_mutex);
}
void sigint_handler(int signum)
{
if (signum == SIGINT) {
@@ -540,7 +557,7 @@ void *do_relay(void *data)
relay_ports *ports = (relay_ports *) data;
char buf[4096];
ssize_t read_size;
while (!is_interrupted()) {
while (are_relays_running()) {
read_size = recv(ports->from_socket, buf, sizeof(buf), 0);
if (read_size <= 0) {
continue;
@@ -581,6 +598,7 @@ int open_socket(int local, in_port_t port)
skt = socket(AF_UNIX, SOCK_DGRAM, 0);
sa.un.sun_family = AF_UNIX;
snprintf(sa.un.sun_path, sizeof(sa.un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port);
unlink(sa.un.sun_path);
sa_size = sizeof(struct sockaddr_un);
} else {
skt = socket(AF_INET, SOCK_DGRAM, 0);
@@ -601,6 +619,8 @@ int open_socket(int local, in_port_t port)
print_info("FAILED TO BIND PORT %u: %i", port, errno);
close(skt);
return -1;
} else {
// print_info("BOUND PORT %u", port);
}
return skt;
@@ -647,7 +667,7 @@ void *open_relay(void *data)
}
// print_info("ENTERING MAIN LOOP");
while (!is_interrupted()) {
while (are_relays_running()) {
print_info("STARTED RELAYS");
relay_ports console_to_frontend = create_ports(from_console, from_frontend, &frontend_addr, frontend_addr_size);
relay_ports frontend_to_console = create_ports(from_frontend, from_console, (sockaddr_u *) &console_addr, sizeof(struct sockaddr_in));
@@ -676,16 +696,16 @@ close:
return THREADRESULT(ret);
}
void create_all_relays(int skt, int local, const char *wireless_interface, sockaddr_u client, size_t client_size)
void create_all_relays(struct sync_args *args)
{
pthread_t vid_thread, aud_thread, msg_thread, cmd_thread, hid_thread;
struct relay_info vid_info, aud_info, msg_info, cmd_info, hid_info;
// Set common info for all
vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = wireless_interface;
vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = local;
vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = client;
vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = client_size;
vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface;
vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local;
vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client;
vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size;
vid_info.port = PORT_VID;
aud_info.port = PORT_AUD;
@@ -693,6 +713,9 @@ void create_all_relays(int skt, int local, const char *wireless_interface, socka
cmd_info.port = PORT_CMD;
hid_info.port = PORT_HID;
// Enable relays
relay_running = 1;
pthread_create(&vid_thread, NULL, open_relay, &vid_info);
pthread_create(&aud_thread, NULL, open_relay, &aud_info);
pthread_create(&msg_thread, NULL, open_relay, &msg_info);
@@ -702,7 +725,26 @@ void create_all_relays(int skt, int local, const char *wireless_interface, socka
// Notify client that we are connected
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_CONNECTED;
sendto(skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &client, client_size);
sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, args->client_size);
while (!is_interrupted()) {
char buf[1024];
size_t buf_len = sizeof(buf);
if (wpa_ctrl_recv(args->ctrl, buf, &buf_len) == 0) {
if (!memcmp(buf, "<3>CTRL-EVENT-DISCONNECTED", 26)) {
print_info("Wii U disconnected, attempting to re-connect...");
// Let client know we lost connection
cmd.control_code = VANILLA_PIPE_CC_DISCONNECTED;
sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, args->client_size);
break;
}
}
}
// Stop all relays
interrupt_relays();
pthread_join(vid_thread, NULL);
pthread_join(aud_thread, NULL);
@@ -994,46 +1036,48 @@ void *do_connect(void *data)
{
struct sync_args *args = (struct sync_args *) data;
while (1) {
while (!wpa_ctrl_pending(args->ctrl)) {
sleep(2);
print_info("WAITING FOR CONNECTION");
while (!is_interrupted()) {
while (1) {
while (!wpa_ctrl_pending(args->ctrl)) {
sleep(2);
print_info("WAITING FOR CONNECTION");
if (is_interrupted()) return THREADRESULT(VANILLA_ERR_GENERIC);
}
char buf[1024];
size_t actual_buf_len = sizeof(buf);
wpa_ctrl_recv(args->ctrl, buf, &actual_buf_len);
if (!strstr(buf, "CTRL-EVENT-BSS-ADDED")
&& !strstr(buf, "CTRL-EVENT-BSS-REMOVED")) {
print_info("CONN RECV: %.*s", actual_buf_len, buf);
}
if (memcmp(buf, "<3>CTRL-EVENT-CONNECTED", 23) == 0) {
break;
}
if (is_interrupted()) return THREADRESULT(VANILLA_ERR_GENERIC);
}
char buf[1024];
size_t actual_buf_len = sizeof(buf);
wpa_ctrl_recv(args->ctrl, buf, &actual_buf_len);
if (!strstr(buf, "CTRL-EVENT-BSS-ADDED")
&& !strstr(buf, "CTRL-EVENT-BSS-REMOVED")) {
print_info("CONN RECV: %.*s", actual_buf_len, buf);
print_info("CONNECTED TO CONSOLE");
// Use DHCP on interface
pid_t dhclient_pid;
int r = call_dhcp(args->wireless_interface, &dhclient_pid);
if (r != VANILLA_SUCCESS) {
print_info("FAILED TO RUN DHCP ON %s", args->wireless_interface);
return THREADRESULT(r);
} else {
print_info("DHCP ESTABLISHED");
}
if (memcmp(buf, "<3>CTRL-EVENT-CONNECTED", 23) == 0) {
break;
}
create_all_relays(args);
if (is_interrupted()) return THREADRESULT(VANILLA_ERR_GENERIC);
int kill_ret = kill(dhclient_pid, SIGTERM);
print_info("KILLING DHCLIENT %i", dhclient_pid);
}
print_info("CONNECTED TO CONSOLE");
// Use DHCP on interface
pid_t dhclient_pid;
int r = call_dhcp(args->wireless_interface, &dhclient_pid);
if (r != VANILLA_SUCCESS) {
print_info("FAILED TO RUN DHCP ON %s", args->wireless_interface);
return THREADRESULT(r);
} else {
print_info("DHCP ESTABLISHED");
}
create_all_relays(args->skt, args->local, args->wireless_interface, args->client, args->client_size);
int kill_ret = kill(dhclient_pid, SIGTERM);
print_info("KILLING DHCLIENT %i", dhclient_pid);
return THREADRESULT(VANILLA_SUCCESS);
}
@@ -1153,6 +1197,8 @@ void vanilla_listen(int local, const char *wireless_interface)
} else if (cmd.control_code == VANILLA_PIPE_CC_UNBIND) {
print_info("RECEIVED UNBIND SIGNAL");
interrupt();
} else if (cmd.control_code == VANILLA_PIPE_CC_QUIT) {
quit_loop();
}
repeat_loop:
+193 -27
View File
@@ -1,26 +1,138 @@
find_package(SDL2 REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(FFmpeg REQUIRED COMPONENTS avformat avcodec avutil avfilter swscale)
find_package(libxml2 REQUIRED)
if (VANILLA_BUILD_VENDORED)
include(FetchContent)
include(ExternalProject)
add_executable(vanilla-pi
FetchContent_Declare(
SDL2
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-2.32.0
)
set(SDL2TTF_VENDORED ON)
FetchContent_Declare(
SDL2_ttf
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
GIT_TAG release-2.24.0
)
FetchContent_Declare(
SDL2_image
GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git
GIT_TAG release-2.8.5
)
set(LIBXML2_WITH_PYTHON OFF)
set(LIBXML2_WITH_ICONV OFF)
FetchContent_Declare(
libxml2
GIT_REPOSITORY https://gitlab.gnome.org/GNOME/libxml2.git
GIT_TAG v2.13.5
)
FetchContent_MakeAvailable(SDL2 SDL2_ttf SDL2_image libxml2)
set(FFMPEG_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg/install")
set(FFMPEG_CONFIGURE_ARGS
--enable-gpl
--enable-shared
--disable-programs
--prefix=${FFMPEG_INSTALL_DIR}
--disable-doc
--disable-swresample
--disable-postproc
--disable-avfilter
--disable-encoders
--disable-decoders
--disable-demuxers
--disable-muxers
--disable-network
--enable-muxer=mp4
--enable-muxer=image2
--enable-decoder=h264
--enable-encoder=png
)
if (CMAKE_CROSSCOMPILING)
set(FFMPEG_TARGET_OS ${CMAKE_SYSTEM_NAME})
if (MINGW)
set(FFMPEG_TARGET_OS "mingw32")
list(APPEND FFMPEG_CONFIGURE_ARGS
--cross-prefix=${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-
)
elseif(MSVC)
set(FFMPEG_TARGET_OS "win32")
elseif(APPLE)
set(FFMPEG_TARGET_OS "darwin")
elseif(ANDROID)
set(FFMPEG_TARGET_OS "android")
list(APPEND FFMPEG_CONFIGURE_ARGS
--cross-prefix=${CMAKE_SYSTEM_PROCESSOR}-linux-android35-
--cc=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/${CMAKE_SYSTEM_PROCESSOR}-linux-android35-clang
--extra-cflags=${CMAKE_C_FLAGS}
--ar=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
--ranlib=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib
--disable-asm
--nm=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm
--strip=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
)
endif()
list(APPEND FFMPEG_CONFIGURE_ARGS
--enable-cross-compile
--arch=${CMAKE_SYSTEM_PROCESSOR}
--target-os=${FFMPEG_TARGET_OS}
)
endif()
set(FFMPEG_FILES "")
set(FFMPEG_INCLUDE_DIR "${FFMPEG_INSTALL_DIR}/include")
foreach(_component avformat avcodec avutil swscale)
add_library("FFmpeg::${_component}" UNKNOWN IMPORTED)
if (WIN32)
set(_component_lib "${FFMPEG_INSTALL_DIR}/bin/${_component}.lib")
else()
set(_component_lib "${FFMPEG_INSTALL_DIR}/lib/lib${_component}.so")
endif()
set_target_properties("FFmpeg::${_component}" PROPERTIES
IMPORTED_LOCATION ${_component_lib})
list(APPEND FFMPEG_FILES ${_component_lib})
add_dependencies("FFmpeg::${_component}" FFmpeg)
endforeach()
list(APPEND FFMPEG_FILES ${FFMPEG_INCLUDE_DIR})
include_directories(${FFMPEG_INCLUDE_DIR})
ExternalProject_Add(FFmpeg
DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY https://git.ffmpeg.org/ffmpeg.git
GIT_TAG n7.1
CONFIGURE_COMMAND ../FFmpeg/configure ${FFMPEG_CONFIGURE_ARGS}
# BUILD_COMMAND "make -j${CMAKE_BUILD_PARALLEL_LEVEL}"
UPDATE_COMMAND ""
BUILD_BYPRODUCTS
${FFMPEG_FILES}
)
endif()
list(APPEND VANILLA_PI_SRC
config.c
drm.c
def.c
# drm.c
lang.c
#game/game_decode_ffmpeg.c
game/game_decode.c
game/game_main.c
main2.c
main.c
menu/menu.c
menu/menu_common.c
menu/menu_connection.c
menu/menu_delete.c
menu/menu_edit.c
menu/menu_game.c
menu/menu_gamepad.c
menu/menu_main.c
menu/menu_rename.c
menu/menu_settings.c
menu/menu_sudo.c
menu/menu_sync.c
pipemgmt.c
platform.c
ui/ui.c
ui/ui_anim.c
ui/ui_sdl.c
@@ -28,29 +140,83 @@ add_executable(vanilla-pi
ui/ui_util.h
)
# Declare executable
if (ANDROID)
add_library(vanilla-pi SHARED ${VANILLA_PI_SRC})
else()
add_executable(vanilla-pi ${VANILLA_PI_SRC})
endif()
if (UNIX AND NOT APPLE)
# Assume this platform is capable of running vanilla-pipe
target_compile_definitions(vanilla-pi PRIVATE VANILLA_PIPE_AVAILABLE)
endif()
# Ensure executable is installed
install(TARGETS vanilla-pi)
# Find packages (unless using vendored build)
if (NOT VANILLA_BUILD_VENDORED)
find_package(SDL2 REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(SDL2_image REQUIRED)
endif()
target_link_libraries(vanilla-pi PRIVATE
SDL2::SDL2
SDL2::SDL2main
SDL2_ttf::SDL2_ttf
SDL2_image::SDL2_image
vanilla
# drm
m
)
if (ANDROID)
target_link_libraries(vanilla-pi PRIVATE
SDL2main
log
)
endif()
# Find FFmpeg
#
# On Android, we use MediaCodec directly
if (NOT VANILLA_BUILD_VENDORED)
find_package(FFmpeg REQUIRED COMPONENTS avformat avcodec avutil swscale)
endif()
# if (NOT ANDROID)
target_link_libraries(vanilla-pi PRIVATE
FFmpeg::avformat
FFmpeg::avcodec
FFmpeg::avutil
FFmpeg::swscale
)
# endif()
# Find libxml2 (used for storing/parsing user config)
#
# On Android, we compile as part of the build, so we don't need to find it
if (NOT VANILLA_BUILD_VENDORED)
find_package(libxml2 REQUIRED)
endif()
target_link_libraries(vanilla-pi PRIVATE LibXml2::LibXml2)
# Link with winsock2 on Windows
if (WIN32)
set_target_properties(vanilla-pi PROPERTIES WIN32_EXECUTABLE true)
target_link_libraries(vanilla-pi PRIVATE wsock32 ws2_32)
endif()
# Set up include directories around the repository
target_include_directories(vanilla-pi PRIVATE
"${CMAKE_SOURCE_DIR}/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/../lib"
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/.."
# xf86drm.h needs this
/usr/include/libdrm
)
target_link_libraries(vanilla-pi PRIVATE
SDL2::SDL2
SDL2_ttf::SDL2_ttf
SDL2_image::SDL2_image
vanilla
FFmpeg::avfilter
FFmpeg::avformat
FFmpeg::avcodec
FFmpeg::avutil
FFmpeg::swscale
LibXml2::LibXml2
drm
m
)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+126
View File
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 22 22"
version="1.1"
id="svg1"
sodipodi:docname="Antu_mail-mark-notjunk_yellow copy.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="40.064776"
inkscape:cx="8.3240202"
inkscape:cy="9.8964738"
inkscape:window-width="1528"
inkscape:window-height="916"
inkscape:window-x="0"
inkscape:window-y="44"
inkscape:window-maximized="0"
inkscape:current-layer="svg1" />
<path
d="m 121.36,2.114 -11.08,-11.08 c -1.52,-1.521 -3.368,-2.281 -5.54,-2.281 -2.173,0 -4.02,0.76 -5.541,2.281 l -53.45,53.53 -23.953,-24.04 c -1.521,-1.521 -3.368,-2.281 -5.54,-2.281 -2.173,0 -4.02,0.76 -5.541,2.281 l -11.08,11.08 c -1.521,1.521 -2.281,3.368 -2.281,5.541 0,2.172 0.76,4.02 2.281,5.54 l 29.493,29.493 11.08,11.08 c 1.52,1.521 3.367,2.281 5.54,2.281 2.172,0 4.02,-0.761 5.54,-2.281 l 11.08,-11.08 58.986,-58.986 c 1.52,-1.521 2.281,-3.368 2.281,-5.541 10e-5,-2.172 -0.761,-4.02 -2.281,-5.54"
fill="#43be80"
transform="matrix(0.14464,0,0,0.14464,2.25,5.618)"
id="path1"
style="display:inline;fill:#000000" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0932203"
d="M 5.2509434,14.758074 C 3.5210614,13.030946 2.0570993,11.500501 1.9976942,11.357085 1.7809892,10.833913 1.9363978,10.551894 3.0573672,9.4341046 4.2106308,8.2841127 4.5033726,8.1346326 5.0427741,8.4203141 5.191706,8.4991925 6.1098434,9.363249 7.0830794,10.34044 l 1.7695203,1.77671 4.0101413,-3.9979473 c 4.292154,-4.2791033 4.27007,-4.261167 4.909382,-3.9874547 0.370928,0.1588079 2.169538,1.972 2.266875,2.2852548 0.182189,0.5863244 0.195193,0.5714757 -5.382069,6.1456082 L 9.3182401,17.898305 H 8.8572117 8.3961834 Z"
id="path2" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00999301"
d="m 12.48688,8.447395 c 1.261705,-1.2631831 2.707443,-2.7119697 3.212753,-3.2195258 0.50531,-0.5075562 0.953423,-0.9507201 0.995808,-0.9848088 0.20102,-0.1616742 0.44149,-0.2459195 0.701654,-0.2458157 0.25286,1.01e-4 0.481709,0.074578 0.671364,0.2184917 0.04589,0.034823 0.442926,0.4245763 0.949861,0.9324425 0.785691,0.7871327 0.876968,0.8818762 0.931813,0.9672006 0.10222,0.1590275 0.151585,0.3034389 0.171368,0.5013197 0.01878,0.1878224 -0.01068,0.3822533 -0.0844,0.5570383 -0.08135,0.1928885 -0.0077,0.1148314 -1.804352,1.9130762 l -1.655514,1.6570093 -0.04317,-0.0032 -0.04317,-0.0032 0.837372,-0.8444098 c 2.085867,-2.1033954 2.615205,-2.6954951 2.738043,-3.06268 0.02393,-0.071528 0.02622,-0.090621 0.02223,-0.1848707 C 20.08254,6.5048359 20.04997,6.4051485 19.971605,6.2880437 19.688679,5.8652281 18.462067,4.6228298 17.949777,4.2401945 17.843118,4.1605295 17.74298,4.1079352 17.607679,4.0605185 c -0.06778,-0.023754 -0.09591,-0.028046 -0.184871,-0.028212 -0.117951,-2.191e-4 -0.163552,0.011679 -0.293958,0.076697 -0.286012,0.1426007 -0.734414,0.5252345 -1.613791,1.3770914 -0.506049,0.4902124 -1.232733,1.2104444 -3.307073,3.2777077 -1.089206,1.0854909 -1.988193,1.9751054 -1.997748,1.9769204 -0.0096,0.0018 1.01493,-1.0302132 2.276635,-2.2933963 z"
id="path3" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00856419"
d="M 2.5034653,12.08622 C 2.2977184,11.880144 2.1201261,11.699975 2.1088159,11.685844 2.0021619,11.552591 1.9186403,11.366997 1.8872063,11.193403 c -0.019006,-0.104959 -0.01439,-0.336257 0.00889,-0.445339 0.037781,-0.17705 0.1161129,-0.335995 0.2356822,-0.478224 0.08075,-0.09605 1.7301346,-1.7391858 1.7906263,-1.7838392 0.1402699,-0.1035435 0.2481839,-0.1542915 0.418823,-0.1969573 0.1028277,-0.025711 0.3325156,-0.031857 0.4514353,-0.012081 0.1612028,0.026808 0.2935953,0.078621 0.4357814,0.1705473 0.052531,0.033963 0.3898662,0.3677884 1.8518886,1.8326252 L 8.86799,12.071233 9.5386239,11.400738 c 0.3688487,-0.368773 0.6735161,-0.667614 0.6770371,-0.664092 0.0035,0.0035 -0.3010339,0.313857 -0.67679,0.689636 L 8.8556747,12.109514 7.5623326,10.818094 C 6.2250407,9.4827886 5.7854114,9.0547849 5.3695548,8.6833015 5.1490012,8.4862816 5.0846569,8.4369666 4.9843614,8.38808 4.4700182,8.137376 4.1531408,8.3200718 2.8946978,9.5928764 2.2711981,10.223492 2.0354222,10.521896 1.9430478,10.797311 c -0.030543,0.09106 -0.037988,0.251694 -0.016224,0.350058 0.025322,0.114445 0.067345,0.219947 0.1093261,0.27447 0.083355,0.108259 0.373334,0.428577 0.6964231,0.769288 0.080403,0.08479 0.1704473,0.180174 0.2000995,0.211968 l 0.053913,0.05781 h -0.054518 -0.054517 z"
id="path4" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="M 6.2766735,15.772722 C 3.9937367,13.487721 3.4481332,12.934205 2.7152178,12.159616 l -0.1259421,-0.133103 -1.025e-4,-0.77071 -1.025e-4,-0.770711 h 2.3170578 2.3170577 l 0.80951,0.815794 c 0.4452305,0.448686 0.8139922,0.815793 0.8194703,0.815793 0.00548,0 0.377052,-0.367107 0.8257198,-0.815793 l 0.8157597,-0.815794 h 3.117203 3.117202 l -3.705412,3.70542 -3.7054118,3.70542 H 8.8575962 8.3979651 Z"
id="path5" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="M 6.2638252,15.759841 C 4.0009843,13.4949 3.493808,12.980843 2.7966145,12.245592 L 2.5890707,12.026719 v -0.770813 -0.770814 h 2.3175102 2.3175102 l 0.8093274,0.815794 c 0.44513,0.448686 0.8135536,0.815793 0.818719,0.815793 0.00517,0 0.3766916,-0.367107 0.8256139,-0.815793 l 0.8162226,-0.815794 h 3.117039 3.117038 l -3.705412,3.70542 -3.7054118,3.70542 H 8.8575814 8.3979358 Z"
id="path6" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="M 6.2637336,15.759841 C 3.9742639,13.468345 3.3533747,12.838457 2.7022445,12.146735 l -0.1131671,-0.120222 -3.3e-6,-0.77071 -3.4e-6,-0.770711 h 2.3178157 2.3178155 l 0.8048441,0.811033 c 0.4426643,0.446068 0.8105325,0.813215 0.8174849,0.815883 0.00854,0.0033 0.278075,-0.260046 0.8301803,-0.811032 l 0.8175398,-0.815884 h 3.11665 3.11665 l -3.705412,3.70542 -3.7054118,3.70542 H 8.8575814 8.3979358 Z"
id="path7" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="m 8.7701336,17.9804 c -0.088769,-0.0055 -0.2428159,-0.03782 -0.3011463,-0.06318 -0.024213,-0.01053 0.025465,-0.01203 0.3973992,-0.01203 0.3858299,0 0.4223224,0.0012 0.3952935,0.01292 -0.1136519,0.0493 -0.3066216,0.07375 -0.4915464,0.06229 z"
id="path8" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="m 12.914366,14.310427 3.571384,-3.567716 h 0.03692 c 0.02031,0 0.03886,0.0031 0.04124,0.007 0.0058,0.0095 -6.9506399,6.961921 -7.0113908,7.007338 -0.058516,0.04374 -0.1872677,0.121842 -0.2001721,0.121419 -0.00515,-1.69e-4 1.5977599,-1.60578 3.5620209,-3.568023 z"
id="path9" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="m 8.8127159,17.90673 c 0.034242,-0.0016 0.090274,-0.0016 0.1245158,0 0.034242,0.0016 0.00623,0.003 -0.062258,0.003 -0.068484,0 -0.0965,-0.0013 -0.062258,-0.003 z"
id="path10" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0085873"
d="M 6.2551463,15.751254 C 3.9528049,13.446885 3.350299,12.835675 2.6979668,12.142687 L 2.5890707,12.027004 v -0.770956 -0.770956 h 2.3175145 2.3175146 l 0.8125837,0.816799 0.8125839,0.816799 0.2554444,-0.249488 C 9.2452062,11.731983 9.615188,11.364424 9.9268936,11.052403 l 0.5667374,-0.567311 h 3.11721 3.11721 l -3.705412,3.70542 -3.7054118,3.70542 H 8.8575823 8.3979374 Z"
id="path11" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="m 8.3303766,17.857204 c -0.041903,-0.0225 -0.1007971,-0.06105 -0.1506891,-0.09866 -0.030232,-0.02279 -0.2887659,-0.278983 -1.1437009,-1.133365 l -1.1046383,-1.103924 0.00112,-0.03946 0.00112,-0.03946 1.2179271,1.216328 c 0.6698599,0.668981 1.2165396,1.2163 1.2148437,1.216264 -0.0017,-3.6e-5 -0.01789,-0.008 -0.035988,-0.01773 z"
id="path12" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="m 7.1657862,16.666992 -1.2338857,-1.231445 -1.3e-4,-0.438477 -1.299e-4,-0.438476 h 3.3261719 3.3261715 v 0.03613 0.03613 l -1.63379,1.633787 -1.6337912,1.633787 H 8.8580373 8.3996719 Z"
id="path13" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="M 7.1650391,16.666707 5.9316406,15.435408 v -0.438407 -0.438407 h 3.3261719 3.3261715 v 0.03613 0.03613 l -1.63379,1.633787 -1.6337912,1.633787 -0.4589827,-2.15e-4 -0.4589826,-2.16e-4 z"
id="path14" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="m 7.1649425,16.666791 -1.2334949,-1.231244 9.65e-5,-0.438477 9.65e-5,-0.438476 h 3.3261719 3.3261715 v 0.03613 0.03613 l -1.63379,1.633787 -1.6337912,1.633787 -0.4589827,-2.01e-4 -0.4589826,-2.02e-4 z"
id="path15" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="m 7.1648916,16.666818 -1.2335458,-1.231271 1.474e-4,-0.438477 1.474e-4,-0.438476 h 3.3261719 3.3261715 v 0.03613 0.03613 l -1.63379,1.633787 -1.6337912,1.633787 -0.4589827,-1.74e-4 -0.4589826,-1.74e-4 z"
id="path16" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00390625"
d="m 9.3990343,17.833737 c 0.017516,-0.0192 0.7412017,-0.745065 1.6081907,-1.613034 l 1.576343,-1.578125 2.08e-4,0.04711 2.08e-4,0.04711 -1.049387,1.046638 c -0.577163,0.57565 -1.257876,1.253733 -1.512695,1.506851 -0.455015,0.451975 -0.4642172,0.460827 -0.5140895,0.494515 -0.050007,0.03378 -0.118895,0.07569 -0.133275,0.08108 -0.00404,0.0015 0.00698,-0.01295 0.024497,-0.03215 z"
id="path17" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00432733"
d="m 4.0938957,13.683251 -1.2200555,-1.220061 0.058854,-0.0013 0.058854,-0.0013 0.072639,0.07573 c 0.515484,0.537406 0.9482245,0.979077 1.7860287,1.822886 l 0.5402884,0.544162 h -0.038277 -0.038277 z"
id="path18" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.00432733"
d=""
id="path19" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0259649"
d="m 8.6073618,17.942955 -0.064912,-0.01957 0.077895,-0.0129 c 0.047915,-0.0079 0.016907,-0.01439 -0.080567,-0.01678 L 8.3813162,17.889811 6.0082006,15.506699 C 3.0178784,12.503781 2.1630045,11.609788 2.0132622,11.328954 1.9715642,11.250751 1.9459147,11.153977 1.9395035,11.050665 1.9171664,10.690722 2.1183417,10.406276 3.0622516,9.4631844 4.1382432,8.388126 4.4490671,8.1992301 4.8765321,8.3605983 c 0.2592499,0.097867 0.7014079,0.50449 2.5956641,2.3870607 0.7394648,0.734903 1.3602345,1.3385 1.3794883,1.341326 0.019254,0.0028 1.5477385,-1.498215 3.3966335,-3.3356465 3.885165,-3.8610764 4.358778,-4.3122837 4.807719,-4.5802728 0.338294,-0.2019397 0.580212,-0.1565945 1.015528,0.1903511 0.275278,0.2193951 1.432122,1.3746196 1.688124,1.6857596 0.427223,0.51924 0.419942,0.7162586 -0.04801,1.2991865 -0.498678,0.6211978 -1.189004,1.3265357 -6.702198,6.8479421 l -3.6880123,3.693506 -0.142841,0.0031 c -0.1132515,0.0024 -0.1267053,0.0067 -0.064946,0.02057 l 0.077895,0.0175 -0.087631,0.01838 c -0.1108288,0.02324 -0.4111525,0.01939 -0.4965786,-0.0064 z"
id="path20" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.0259649"
d="m 8.6203443,17.950619 c -0.028561,-0.0077 -0.06303,-0.02447 -0.076596,-0.03736 -0.013567,-0.0129 -0.055343,-0.02344 -0.092836,-0.02344 -0.055821,0 -0.4817541,-0.415089 -2.3512643,-2.291402 C 2.7959023,12.282643 2.0645421,11.51168 1.975809,11.251262 1.8266689,10.81356 2.0072269,10.516323 3.0619016,9.4633218 4.1394235,8.3875096 4.4491825,8.1992737 4.8765321,8.3605983 c 0.2617602,0.098814 0.6718546,0.4754662 2.5559184,2.3474857 0.7318854,0.727207 1.3307007,1.312622 1.3307007,1.300921 0,-0.0117 0.023253,-2.29e-4 0.051674,0.02549 0.050344,0.04556 0.055189,0.04354 0.1882454,-0.07836 0.2184642,-0.200152 1.7411654,-1.705055 3.9793754,-3.9328596 2.841989,-2.8287771 3.374758,-3.340184 3.868769,-3.7136438 0.604489,-0.4569784 0.826866,-0.3722869 1.965299,0.7484787 1.128626,1.1111102 1.344444,1.4212266 1.229632,1.7668947 -0.107962,0.3250446 -0.636879,0.9285357 -2.232329,2.5470739 -0.613024,0.6218955 -2.774586,2.7927971 -4.803471,4.8242251 -3.058843,3.062678 -3.7007126,3.693506 -3.7581549,3.693506 -0.038101,0 -0.073931,0.01397 -0.079623,0.03105 -0.010867,0.0326 -0.4527695,0.05642 -0.5522239,0.02976 z"
id="path21" />
<path
style="fill:#000000;fill-opacity:1;stroke-width:0.0259649"
d="M 8.5368942,17.931444 C 8.4767159,17.911863 8.371558,17.863828 8.3032101,17.824699 8.1599699,17.742696 2.2892441,11.896748 2.1014323,11.649097 1.8601282,11.33091 1.8143447,10.888204 1.9850073,10.523323 2.0599738,10.363043 2.1493729,10.26476 2.9681209,9.4425174 3.4634773,8.9450464 3.9186246,8.5032704 3.9795591,8.4607929 4.1520751,8.3405318 4.3570373,8.2806326 4.595786,8.2807035 c 0.2329819,6.93e-5 0.3971426,0.041511 0.5732409,0.1447114 0.071837,0.042099 0.8313587,0.7850171 1.9072948,1.8656001 l 1.7879209,1.795642 3.9675214,-3.9622388 c 3.573705,-3.5689468 3.981471,-3.9680325 4.108053,-4.0206026 0.218182,-0.090612 0.44231,-0.1192347 0.643448,-0.082173 0.353693,0.06517 0.402932,0.1040354 1.413023,1.115333 0.839265,0.8402661 0.932848,0.9425738 1.012631,1.1070356 0.08436,0.1738873 0.08791,0.1933159 0.08791,0.4808964 0,0.5408526 0.59116,-0.1039317 -5.371294,5.8585214 -4.9504632,4.950464 -5.204949,5.200534 -5.3766684,5.283385 -0.1627521,0.07852 -0.205604,0.08761 -0.4414031,0.0936 -0.1680069,0.0043 -0.3001776,-0.0061 -0.3705673,-0.02897 z"
id="path22" />
<path
style="fill:#000000;stroke-width:0.0371735"
d="M 8.5966475,17.941793 C 8.4174041,17.905879 8.3399339,17.861762 8.1133924,17.66659 7.9650071,17.538753 6.5457589,16.133596 4.9595078,14.54402 2.6775314,12.25726 2.0566704,11.615077 1.9856307,11.468014 1.912846,11.317338 1.896228,11.22584 1.8978611,10.984759 1.9009795,10.524395 1.943339,10.465022 3.0543899,9.3637096 4.1498817,8.2778203 4.1596188,8.2713198 4.6533689,8.2962532 c 0.1784839,0.00901 0.3481012,0.041533 0.4393956,0.084244 0.09899,0.046311 0.7482859,0.6671677 1.9424209,1.8573418 0.9865499,0.983277 1.813013,1.794221 1.8365847,1.802098 0.023571,0.0079 1.8008789,-1.751812 3.9495729,-3.9104198 2.783223,-2.7960651 3.954822,-3.9493185 4.073996,-4.0102024 0.243589,-0.1244455 0.763599,-0.1234726 1.003684,0.00188 0.104787,0.05471 0.517253,0.4365164 1.104072,1.0220037 1.067399,1.0649759 1.102344,1.1152732 1.101373,1.5852007 -4.41e-4,0.213268 -0.02096,0.3224897 -0.08728,0.4646683 -0.07102,0.152252 -1.018345,1.1190387 -5.237982,5.3455975 -2.833204,2.837852 -5.2265562,5.207766 -5.3185605,5.266477 -0.1502161,0.09586 -0.2517811,0.126283 -0.557602,0.167038 -0.051113,0.0068 -0.1889921,-0.0069 -0.306397,-0.03038 z"
id="path23" />
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+50 -20
View File
@@ -7,12 +7,9 @@
#include <stdlib.h>
#include <string.h>
vpi_config_t vpi_config;
#include "platform.h"
int vpi_config_filename(char *out, size_t out_size)
{
return snprintf(out, out_size, "%s/.config/vanilla.conf", getenv("HOME"));
}
vpi_config_t vpi_config;
void hex_to_string(char *output, uint8_t *data, size_t count)
{
@@ -37,7 +34,7 @@ void vpi_config_save()
vpi_config_filename(config_fn, sizeof(config_fn));
xmlTextWriterPtr writer = xmlNewTextWriterFilename(config_fn, 0);
if (!writer) {
fprintf(stderr, "Error: xmlNewTextWriterFilename\n");
vpilog("Error: xmlNewTextWriterFilename\n");
return;
}
@@ -49,19 +46,29 @@ void vpi_config_save()
xmlTextWriterSetIndentString(writer, BAD_CAST "\t");
xmlTextWriterStartElement(writer, BAD_CAST "vanilla");
xmlTextWriterStartElement(writer, BAD_CAST "consoles");
for (uint8_t i = 0; i < vpi_config.connected_console_count; i++) {
vpi_console_entry_t *entry = &vpi_config.connected_console_entries[i];
xmlTextWriterStartElement(writer, BAD_CAST "console");
xmlTextWriterWriteElement(writer, BAD_CAST "name", entry->name);
xmlTextWriterWriteElement(writer, BAD_CAST "name", BAD_CAST entry->name);
hex_to_string(buf, entry->bssid.bssid, sizeof(vanilla_bssid_t));
xmlTextWriterWriteElement(writer, BAD_CAST "bssid", buf);
xmlTextWriterWriteElement(writer, BAD_CAST "bssid", BAD_CAST buf);
hex_to_string(buf, entry->psk.psk, sizeof(vanilla_psk_t));
xmlTextWriterWriteElement(writer, BAD_CAST "psk", buf);
xmlTextWriterWriteElement(writer, BAD_CAST "psk", BAD_CAST buf);
xmlTextWriterEndElement(writer); // console
}
xmlTextWriterEndElement(writer); // consoles
sprintf(buf, "%X", vpi_config.server_address);
xmlTextWriterWriteElement(writer, BAD_CAST "server", BAD_CAST buf);
xmlTextWriterWriteElement(writer, BAD_CAST "wireless", BAD_CAST vpi_config.wireless_interface);
sprintf(buf, "%i", vpi_config.connection_setup);
xmlTextWriterWriteElement(writer, BAD_CAST "connectionsetup", BAD_CAST buf);
xmlTextWriterEndElement(writer); // vanilla
xmlTextWriterEndDocument(writer);
@@ -75,6 +82,7 @@ void vpi_config_init()
// Set defaults
memset(&vpi_config, 0, sizeof(vpi_config));
vpi_config.server_address = VANILLA_ADDRESS_LOCAL;
// Load from file
@@ -88,11 +96,11 @@ void vpi_config_init()
xmlNodePtr child = root->children;
while (child) {
if (child->type == XML_ELEMENT_NODE) {
if (!strcmp(child->name, "consoles")) {
if (!strcmp((const char *) child->name, "consoles")) {
// Firstly, count the number of consoles
xmlNodePtr console = child->children;
while (console) {
if (console->type == XML_ELEMENT_NODE && !strcmp(console->name, "console")) {
if (console->type == XML_ELEMENT_NODE && !strcmp((const char *) console->name, "console")) {
vpi_config.connected_console_count++;
}
console = console->next;
@@ -104,15 +112,15 @@ void vpi_config_init()
vpi_console_entry_t *entry = vpi_config.connected_console_entries;
console = child->children;
while (console) {
if (console->type == XML_ELEMENT_NODE && !strcmp(console->name, "console")) {
if (console->type == XML_ELEMENT_NODE && !strcmp((const char *) console->name, "console")) {
xmlNodePtr console_info = console->children;
while (console_info) {
if (!strcmp(console_info->name, "name")) {
strncpy(entry->name, console_info->children->content, sizeof(entry->name));
} else if (!strcmp(console_info->name, "bssid")) {
string_to_hex(entry->bssid.bssid, sizeof(entry->bssid), console_info->children->content);
} else if (!strcmp(console_info->name, "psk")) {
string_to_hex(entry->psk.psk, sizeof(entry->psk), console_info->children->content);
if (!strcmp((const char *) console_info->name, "name")) {
strncpy(entry->name, (const char *) console_info->children->content, sizeof(entry->name));
} else if (!strcmp((const char *) console_info->name, "bssid")) {
string_to_hex(entry->bssid.bssid, sizeof(entry->bssid), (const char *) console_info->children->content);
} else if (!strcmp((const char *) console_info->name, "psk")) {
string_to_hex(entry->psk.psk, sizeof(entry->psk), (const char *) console_info->children->content);
}
console_info = console_info->next;
}
@@ -120,6 +128,15 @@ void vpi_config_init()
}
console = console->next;
}
} else if (!strcmp((const char *) child->name, "server")) {
vpi_config.server_address = strtol((const char *) child->children->content, 0, 16);
} else if (!strcmp((const char *) child->name, "wireless")) {
if (child->children) {
strncpy(vpi_config.wireless_interface, (const char *) child->children->content, sizeof(vpi_config.wireless_interface)-1);
vpi_config.wireless_interface[sizeof(vpi_config.wireless_interface)-1] = 0;
}
} else if (!strcmp((const char *) child->name, "connectionsetup")) {
vpi_config.connection_setup = atoi((const char *) child->children->content);
}
}
child = child->next;
@@ -135,23 +152,36 @@ void vpi_config_free()
free(vpi_config.connected_console_entries);
}
void vpi_config_add_console(vpi_console_entry_t *entry)
int vpi_config_add_console(vpi_console_entry_t *entry)
{
if (vpi_config.connected_console_count == UINT8_MAX) {
// Nothing to do
return;
return -1;
}
vpi_console_entry_t *old_entries = vpi_config.connected_console_entries;
vpi_config.connected_console_entries = malloc((vpi_config.connected_console_count + 1) * sizeof(vpi_console_entry_t));
if (vpi_config.connected_console_count > 0)
memcpy(vpi_config.connected_console_entries, old_entries, vpi_config.connected_console_count * sizeof(vpi_console_entry_t));
int index = vpi_config.connected_console_count;
memcpy(vpi_config.connected_console_entries + vpi_config.connected_console_count, entry, sizeof(vpi_console_entry_t));
vpi_config.connected_console_count++;
if (old_entries)
free(old_entries);
vpi_config_save();
return index;
}
void vpi_config_rename_console(uint8_t index, const char *name)
{
strncpy(vpi_config.connected_console_entries[index].name, name, VPI_CONSOLE_MAX_NAME);
vpi_config.connected_console_entries[index].name[VPI_CONSOLE_MAX_NAME-1] = 0;
vpi_config_save();
}
void vpi_config_remove_console(uint8_t index)
+6 -2
View File
@@ -1,5 +1,5 @@
#ifndef VANILLA_PI_CONFIG_H
#define VANILLA_PI_CONFIG_
#define VANILLA_PI_CONFIG_H
#include <stdint.h>
#include <vanilla.h>
@@ -16,13 +16,17 @@ typedef struct {
uint8_t connected_console_count;
vpi_console_entry_t *connected_console_entries;
uint32_t server_address;
char wireless_interface[VPI_CONSOLE_MAX_NAME];
int connection_setup;
} vpi_config_t;
extern vpi_config_t vpi_config;
void vpi_config_init();
void vpi_config_free();
void vpi_config_add_console(vpi_console_entry_t *entry);
int vpi_config_add_console(vpi_console_entry_t *entry);
void vpi_config_rename_console(uint8_t index, const char *name);
void vpi_config_remove_console(uint8_t index);
void vpi_config_save();
#endif // VANILLA_PI_CONFIG_H
+1
View File
@@ -0,0 +1 @@
#include "def.h"
+389 -18
View File
@@ -1,18 +1,20 @@
#include "game_decode.h"
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <pthread.h>
#include <libswscale/swscale.h>
#include <sys/time.h>
#include <vanilla.h>
#include "def.h"
#include "game_main.h"
#include "lang.h"
#include "platform.h"
AVFrame *vpi_present_frame = 0;
static AVFrame *decoding_frame;
pthread_mutex_t vpi_decoding_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t decoding_wait_cond = PTHREAD_COND_INITIALIZER;
static AVCodecContext *video_codec_ctx;
static AVPacket *video_packet;
pthread_cond_t decoding_wait_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t vpi_decode_loop_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t vpi_decode_loop_cond = PTHREAD_COND_INITIALIZER;
int vpi_decode_loop_running = 0;
@@ -20,6 +22,23 @@ uint8_t vpi_decode_ready = 0;
uint8_t vpi_decode_data[65536];
size_t vpi_decode_size = 0;
AVFrame *vpi_present_frame = 0;
static AVFrame *decoding_frame;
static AVCodecContext *video_codec_ctx;
static AVPacket *video_packet;
static pthread_mutex_t screenshot_mutex = PTHREAD_MUTEX_INITIALIZER;
static char screenshot_buf[4096] = {0};
static pthread_mutex_t recording_mutex = PTHREAD_MUTEX_INITIALIZER;
static AVFormatContext *recording_fmt_ctx = 0;
static AVStream *recording_vstr;
static AVStream *recording_astr;
static struct timeval recording_start;
static const int VIDEO_STREAM_INDEX = 0;
static const int AUDIO_STREAM_INDEX = 1;
enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt)
{
while (*pix_fmt != AV_PIX_FMT_NONE) {
@@ -32,6 +51,322 @@ enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix
return AV_PIX_FMT_NONE;
}
int dump_frame_to_file(const AVFrame *frame, const char *filename)
{
AVFormatContext *ofmt;
int ret = avformat_alloc_output_context2(&ofmt, 0, 0, filename);
if (ret < 0) {
vpilog("Failed to allocate new AVFormatContext for screenshot\n");
goto exit;
}
enum AVCodecID codec_id = AV_CODEC_ID_PNG;
const AVCodec *codec = avcodec_find_encoder(codec_id);
if (!codec) {
vpilog("Failed to find encoder for screenshot\n");
ret = AVERROR_ENCODER_NOT_FOUND;
goto exit_and_free_avformatcontext;
}
AVStream *s = avformat_new_stream(ofmt, 0);
if (!s) {
vpilog("Failed to create new AVStream for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
s->id = 0;
AVCodecContext *ocodec_ctx = avcodec_alloc_context3(codec);
if (!ocodec_ctx) {
vpilog("Failed to create new AVCodecContext for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
enum AVPixelFormat opix_fmt = AV_PIX_FMT_RGB24;
ocodec_ctx->width = frame->width;
ocodec_ctx->height = frame->height;
ocodec_ctx->pix_fmt = opix_fmt;
ocodec_ctx->codec_id = codec_id;
ocodec_ctx->time_base = (AVRational){1, 1};
ocodec_ctx->frame_num = 1;
if (ofmt->flags & AVFMT_GLOBALHEADER) {
ocodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVPacket *opkt = av_packet_alloc();
if (!opkt) {
vpilog("Failed to create new AVPacket for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avcodeccontext;
}
ret = avcodec_open2(ocodec_ctx, codec, 0);
if (ret < 0) {
vpilog("Failed to open encoder for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avcodec_parameters_from_context(s->codecpar, ocodec_ctx);
if (ret < 0) {
vpilog("Failed to copy params from context for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avformat_write_header(ofmt, 0);
if (ret < 0) {
vpilog("Failed to write header for screenshot\n");
goto exit_and_free_avpacket;
}
struct SwsContext *osws_ctx = sws_getContext(
frame->width, frame->height, frame->format,
frame->width, frame->height, opix_fmt,
0, 0, 0, 0);
if (!osws_ctx) {
vpilog("Failed to get SwsContext for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_avpacket;
}
AVFrame *oframe = av_frame_alloc();
if (!oframe) {
vpilog("Failed to allocate AVFrame for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_swscontext;
}
oframe->width = frame->width;
oframe->height = frame->height;
oframe->format = opix_fmt;
ret = av_frame_get_buffer(oframe, 0);
if (ret < 0) {
vpilog("Failed to get AVFrame buffer for screenshot\n");
goto exit_and_free_frame;
}
ret = sws_scale(osws_ctx, (const uint8_t **) frame->data, (const int *) frame->linesize, 0, frame->height, oframe->data, oframe->linesize);
if (ret < 0) {
vpilog("Failed to do swscale for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_send_frame(ocodec_ctx, oframe);
if (ret < 0) {
vpilog("Failed to send frame to encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_receive_packet(ocodec_ctx, opkt);
if (ret < 0) {
vpilog("Failed to receive packet from encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_frame(ofmt, opkt);
if (ret < 0) {
vpilog("Failed to write frame for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_trailer(ofmt);
if (ret < 0) {
vpilog("Failed to write trailer for screenshot\n");
goto exit_and_free_frame;
}
exit_and_free_frame:
av_frame_free(&oframe);
exit_and_free_swscontext:
sws_freeContext(osws_ctx);
exit_and_free_avpacket:
av_packet_free(&opkt);
exit_and_free_avcodeccontext:
avcodec_free_context(&ocodec_ctx);
exit_and_free_avformatcontext:
avformat_free_context(ofmt);
exit:
if (ret >= 0) {
vpilog("Saved screenshot to \"%s\"\n", filename);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_SUCCESS), filename);
vpi_show_toast(buf);
} else {
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_ERROR), ret);
vpi_show_toast(buf);
}
return ret;
}
int64_t get_recording_timestamp(AVRational timebase)
{
struct timeval tv;
gettimeofday(&tv, 0);
int64_t us = (tv.tv_sec - recording_start.tv_sec) * 1000000 + (tv.tv_usec - recording_start.tv_usec);
return av_rescale_q(us, (AVRational) {1, 1000000}, timebase);
}
void vpi_decode_send_audio(const void *data, size_t size)
{
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
int ret;
AVPacket *pkt = av_packet_alloc();
pkt->data = (uint8_t *) data;
pkt->size = size;
int64_t ts = get_recording_timestamp(recording_astr->time_base);
pkt->stream_index = AUDIO_STREAM_INDEX;
pkt->dts = ts;
pkt->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, pkt);
av_packet_free(&pkt);
}
pthread_mutex_unlock(&recording_mutex);
}
int vpi_decode_is_recording()
{
int i;
pthread_mutex_lock(&recording_mutex);
i = (recording_fmt_ctx != 0);
pthread_mutex_unlock(&recording_mutex);
return i;
}
int vpi_decode_record(const char *filename)
{
pthread_mutex_lock(&recording_mutex);
int r = avformat_alloc_output_context2(&recording_fmt_ctx, 0, 0, filename);
if (r < 0) {
vpilog("Failed to allocate output context for recording\n");
goto exit;
}
recording_vstr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_vstr) {
vpilog("Failed to allocate video stream for recording\n");
goto exit_and_free_context;
}
recording_vstr->id = VIDEO_STREAM_INDEX;
recording_vstr->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
recording_vstr->codecpar->width = 854;
recording_vstr->codecpar->height = 480;
recording_vstr->codecpar->format = AV_PIX_FMT_YUV420P;
recording_vstr->time_base = (AVRational) {1, 60};
recording_vstr->codecpar->codec_id = AV_CODEC_ID_H264;
// char sps[100];
char pps[100];
// size_t sps_sz = vanilla_generate_sps_params(sps, sizeof(sps));
size_t pps_sz = vanilla_generate_pps_params(pps, sizeof(pps));
// For some reason vanilla_generate_sps_params() doesn't produce valid files?
static const uint8_t sps[] = {
0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x20, 0xac, 0x2b, 0x40, 0x6c, 0x1e, 0xf3, 0x68,
};
size_t sps_sz = sizeof(sps);
size_t sps_pps_size = sps_sz + pps_sz;
recording_vstr->codecpar->extradata_size = sps_pps_size;
recording_vstr->codecpar->extradata = (uint8_t *) av_malloc(sps_pps_size);
memcpy(recording_vstr->codecpar->extradata, sps, sps_sz);
memcpy(recording_vstr->codecpar->extradata + sps_sz, pps, pps_sz);
recording_astr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_astr) {
vpilog("Failed to allocate audio stream for recording\n");
goto exit_and_free_context;
}
recording_astr->id = AUDIO_STREAM_INDEX;
recording_astr->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
recording_astr->codecpar->sample_rate = 48000;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57,24,100)
recording_astr->codecpar->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
#else
recording_astr->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
#endif
recording_astr->codecpar->format = AV_SAMPLE_FMT_S16;
recording_astr->time_base = (AVRational) {1, 48000};
recording_astr->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
r = avio_open2(&recording_fmt_ctx->pb, filename, AVIO_FLAG_WRITE, 0, 0);
if (r < 0) {
vpilog("Failed to open AVIO for recording\n");
goto exit_and_free_context;
}
r = avformat_write_header(recording_fmt_ctx, 0);
if (r < 0) {
vpilog("Failed to write header for recording\n");
goto exit_and_free_context;
}
vanilla_request_idr();
gettimeofday(&recording_start, 0);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_RECORDING_START), filename);
vpi_show_toast(buf);
vpilog("Started recording to \"%s\"\n", filename);
goto exit;
exit_and_free_context:
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
exit:
pthread_mutex_unlock(&recording_mutex);
return r;
}
void vpi_decode_record_stop()
{
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
int r = av_write_trailer(recording_fmt_ctx);
if (r < 0) {
vpilog("Failed to write trailer for recording\n");
}
avio_closep(&recording_fmt_ctx->pb);
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
vpilog("Finished recording\n");
vpi_show_toast(lang(VPI_LANG_RECORDING_FINISH));
}
pthread_mutex_unlock(&recording_mutex);
}
int decode()
{
int err;
@@ -45,14 +380,14 @@ int decode()
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
fprintf(stderr, "Failed to receive frame from decoder: %i\n", err);
vpilog("Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else if (!decoding_frame->data[0]) {
fprintf(stderr, "WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n");
vpilog("WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n");
abort();
} else if ((decoding_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
fprintf(stderr, "GOT A CORRUPT FRAME??????\n");
vpilog("GOT A CORRUPT FRAME??????\n");
abort();
} else {
pthread_mutex_lock(&vpi_decoding_mutex);
@@ -61,6 +396,14 @@ int decode()
av_frame_unref(vpi_present_frame);
av_frame_move_ref(vpi_present_frame, decoding_frame);
pthread_mutex_lock(&screenshot_mutex);
if (screenshot_buf[0] != 0) {
// Dump this frame into file
dump_frame_to_file(vpi_present_frame, screenshot_buf);
screenshot_buf[0] = 0;
}
pthread_mutex_unlock(&screenshot_mutex);
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&vpi_decoding_mutex);
}
@@ -69,6 +412,15 @@ int decode()
return ret;
}
void vpi_decode_screenshot(const char *filename)
{
// Grab copy of filename
pthread_mutex_lock(&screenshot_mutex);
strncpy(screenshot_buf, filename, sizeof(screenshot_buf) - 1);
screenshot_buf[sizeof(screenshot_buf) - 1] = 0;
pthread_mutex_unlock(&screenshot_mutex);
}
void *vpi_decode_loop(void *)
{
int ret = VANILLA_PI_ERR_DECODER;
@@ -86,20 +438,20 @@ void *vpi_decode_loop(void *)
using_v4l2m2m = 0;
}
if (!codec) {
fprintf(stderr, "No decoder was available\n");
vpilog("No decoder was available\n");
goto exit;
}
video_codec_ctx = avcodec_alloc_context3(codec);
if (!video_codec_ctx) {
fprintf(stderr, "Failed to allocate codec context\n");
vpilog("Failed to allocate codec context\n");
goto free_context;
}
if (using_v4l2m2m) {
ffmpeg_err = av_hwdevice_ctx_create(&video_codec_ctx->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err);
vpilog("Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err);
goto free_context;
}
@@ -109,25 +461,25 @@ void *vpi_decode_loop(void *)
ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to open decoder: %i\n", ffmpeg_err);
vpilog("Failed to open decoder: %i\n", ffmpeg_err);
goto free_context;
}
decoding_frame = av_frame_alloc();
if (!decoding_frame) {
fprintf(stderr, "Failed to allocate AVFrame\n");
vpilog("Failed to allocate AVFrame\n");
goto free_context;
}
vpi_present_frame = av_frame_alloc();
if (!vpi_present_frame) {
fprintf(stderr, "Failed to allocate AVFrame\n");
vpilog("Failed to allocate AVFrame\n");
goto free_decode_frame;
}
video_packet = av_packet_alloc();
if (!video_packet) {
fprintf(stderr, "Failed to allocate AVPacket\n");
vpilog("Failed to allocate AVPacket\n");
goto free_present_frame;
}
@@ -148,14 +500,33 @@ void *vpi_decode_loop(void *)
video_packet->size = vpi_decode_size;
vpi_decode_ready = 0;
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
video_packet->stream_index = VIDEO_STREAM_INDEX;
int64_t ts = get_recording_timestamp(recording_vstr->time_base);
video_packet->dts = ts;
video_packet->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, video_packet);
// av_interleaved_write_frame() eventually calls av_packet_unref(),
// so we must put the references back in
video_packet->data = vpi_decode_data;
video_packet->size = vpi_decode_size;
}
pthread_mutex_unlock(&recording_mutex);
int err = avcodec_send_packet(video_codec_ctx, video_packet);
pthread_mutex_unlock(&vpi_decode_loop_mutex);
if (err < 0) {
fprintf(stderr, "Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
vpilog("Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
// return 0;
avcodec_flush_buffers(video_codec_ctx);
vanilla_request_idr();
} else {
decode();
+8
View File
@@ -2,6 +2,7 @@
#define VPI_GAME_DECODE_H
#include <libavutil/frame.h>
#include <pthread.h>
extern pthread_mutex_t vpi_decode_loop_mutex;
extern pthread_cond_t vpi_decode_loop_cond;
@@ -11,7 +12,14 @@ extern uint8_t vpi_decode_ready;
extern size_t vpi_decode_size;
extern AVFrame *vpi_present_frame;
extern pthread_mutex_t vpi_decoding_mutex;
extern pthread_cond_t decoding_wait_cond;
void *vpi_decode_loop(void *);
void vpi_decode_screenshot(const char *filename);
int vpi_decode_is_recording();
int vpi_decode_record(const char *filename);
void vpi_decode_record_stop();
void vpi_decode_send_audio(const void *data, size_t size);
#endif // VPI_GAME_DECODE_H
-10
View File
@@ -1,10 +0,0 @@
#ifndef GAME_DECODE_MEDIACODEC_H
#define GAME_DECODE_MEDIACODEC_H
#include <NdkMediaCodec.h>
/**
* Decoding information for Android
*/
#endif // GAME_DECODE_MEDIACODEC_H
+59 -13
View File
@@ -1,16 +1,22 @@
// TEMP: Remove this...
#include <libavutil/avutil.h>
#include "game_main.h"
#include <pthread.h>
#include <string.h>
#include <vanilla.h>
#include "game_decode.h"
#include "ui/ui.h"
#include "platform.h"
static pthread_t vpi_event_handler_thread;
static int vpi_game_error_value;
static pthread_mutex_t vpi_error_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t vpi_toast_mutex = PTHREAD_MUTEX_INITIALIZER;
static char vpi_toast_string[VPI_TOAST_MAX_LEN];
static struct timeval vpi_toast_expiry;
static int vpi_toast_number = 0;
int vpi_game_error()
{
int r;
@@ -41,30 +47,34 @@ void *vpi_event_handler(void *data)
while (vanilla_wait_event(&event)) {
int stop = 0;
if (event.type == VANILLA_EVENT_VIDEO) {
switch (event.type) {
case VANILLA_EVENT_VIDEO:
pthread_mutex_lock(&vpi_decode_loop_mutex);
vpi_decode_size = event.size;
memcpy(vpi_decode_data, event.data, event.size);
memcpy(vpi_decode_data, event.data, event.size);
vpi_decode_ready = 1;
pthread_cond_broadcast(&vpi_decode_loop_cond);
pthread_mutex_unlock(&vpi_decode_loop_mutex);
} else if (event.type == VANILLA_EVENT_AUDIO) {
break;
case VANILLA_EVENT_AUDIO:
vui_audio_push(vui, event.data, event.size);
} else if (event.type == VANILLA_EVENT_VIBRATE) {
// We send audio to vpi_decode, but not actually for decoding since
// the audio is already uncompressed. We send it in case vpi_decode
// is recording, so the audio can be written to the file.
vpi_decode_send_audio(event.data, event.size);
break;
case VANILLA_EVENT_VIBRATE:
vui_vibrate_set(vui, event.data[0]);
} else if (event.type == VANILLA_EVENT_ERROR) {
break;
case VANILLA_EVENT_ERROR:
vpi_game_set_error(*(int *)event.data);
stop = 1;
break;
}
vanilla_free_event(&event);
if (stop) {
vanilla_stop();
break;
}
}
pthread_mutex_lock(&vpi_decode_loop_mutex);
@@ -82,3 +92,39 @@ void vpi_game_start(vui_context_t *vui)
vpi_game_error_value = VANILLA_SUCCESS;
pthread_create(&vpi_event_handler_thread, 0, vpi_event_handler, vui);
}
void vpi_show_toast(const char *message)
{
pthread_mutex_lock(&vpi_toast_mutex);
strncpy(vpi_toast_string, message, sizeof(vpi_toast_string) - 1);
vpi_toast_string[sizeof(vpi_toast_string) - 1] = 0;
gettimeofday(&vpi_toast_expiry, 0);
// Toast lasts for 2 seconds
vpi_toast_expiry.tv_sec += 2;
vpi_toast_number++;
pthread_mutex_unlock(&vpi_toast_mutex);
}
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time)
{
pthread_mutex_lock(&vpi_toast_mutex);
if (number)
*number = vpi_toast_number;
if (output && output_size) {
strncpy(output, vpi_toast_string, output_size - 1);
output[output_size-1] = 0;
}
if (expiry_time) {
*expiry_time = vpi_toast_expiry;
}
pthread_mutex_unlock(&vpi_toast_mutex);
}
+7
View File
@@ -1,10 +1,17 @@
#ifndef VPI_GAME_MAIN_H
#define VPI_GAME_MAIN_H
#include <sys/time.h>
#include "ui/ui.h"
#define VPI_TOAST_MAX_LEN 1024
void vpi_game_start(vui_context_t *vui);
int vpi_game_error();
void vpi_game_set_error(int r);
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time);
void vpi_show_toast(const char *message);
#endif // VPI_GAME_MAIN_H
+2
View File
@@ -0,0 +1,2 @@
#include "game_record.h"
+8
View File
@@ -0,0 +1,8 @@
#ifndef VPI_GAME_RECORD_H
#define VPI_GAME_RECORD_H
void game_take_screenshot(const char *filename);
void game_start_recording(const char *filename);
void game_stop_recording();
#endif // VPI_GAME_RECORD_H
+12
View File
@@ -26,6 +26,18 @@ static const char *lang_str[__VPI_LANG_T_COUNT] = {
"Audio",
"Connection",
"Webcam",
"Local",
"Via Server",
"Set up how Vanilla connects to the Wii U",
"Choose 'Local' to connect directly with the hardware on your system. Otherwise, you may need a compatible 'server' to relay the necessary packets.",
"Select the wireless interface to use for the connection.",
"Enter the IP address of the server you wish to connect through.",
"Screenshot saved to \"%s\"",
"Recording started to \"%s\"",
"Recording finished",
"Connection lost, attempting to re-connect...",
"Your platform is not capable of \"Local\" connection, and must connect via a server.",
"Failed to save screenshot (%i)"
};
const char *lang(vpi_lang_t id)
+12
View File
@@ -27,6 +27,18 @@ typedef enum {
VPI_LANG_AUDIO,
VPI_LANG_CONNECTION,
VPI_LANG_WEBCAM,
VPI_LANG_LOCAL,
VPI_LANG_VIA_SERVER,
VPI_LANG_CONNECTION_HELP_1,
VPI_LANG_CONNECTION_HELP_2,
VPI_LANG_LOCAL_CONNECTION_HELP,
VPI_LANG_SERVER_CONNECTION_HELP,
VPI_LANG_SCREENSHOT_SUCCESS,
VPI_LANG_RECORDING_START,
VPI_LANG_RECORDING_FINISH,
VPI_LANG_DISCONNECTED,
VPI_LANG_NO_LOCAL_AVAILABLE,
VPI_LANG_SCREENSHOT_ERROR,
__VPI_LANG_T_COUNT
} vpi_lang_t;
+50 -548
View File
@@ -1,564 +1,66 @@
#include <stdio.h>
#include <string.h>
#include <vanilla.h>
#include "config.h"
#include "menu/menu.h"
#include "platform.h"
#include "ui/ui.h"
#include "ui/ui_sdl.h"
#define SCREEN_WIDTH 854
#define SCREEN_HEIGHT 480
#include <arpa/inet.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <SDL2/SDL.h>
#include <vanilla.h>
#include <unistd.h>
#include "drm.h"
#include "ui.h"
AVFrame *present_frame;
AVFrame *decoding_frame;
pthread_mutex_t decoding_mutex;
pthread_cond_t decoding_wait_cond;
AVCodecContext *video_codec_ctx;
AVPacket *video_packet;
int running = 0;
static int button_map[SDL_CONTROLLER_BUTTON_MAX] = {-1};
static int axis_map[SDL_CONTROLLER_AXIS_MAX] = {-1};
static int vibrate = 0;
// HACK: Easy macro to test between desktop and RPi (even though ARM doesn't necessarily mean RPi)
#ifdef __arm__
#define RASPBERRY_PI
#if !defined(ANDROID) && !defined(_WIN32)
#define SDL_main main
#endif
enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt)
#include "game/game_main.h"
#include "pipemgmt.h"
int SDL_main(int argc, const char **argv)
{
while (*pix_fmt != AV_PIX_FMT_NONE) {
if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) {
return AV_PIX_FMT_DRM_PRIME;
}
pix_fmt++;
int fs = 0;
#ifdef ANDROID
fs = 1;
#else
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-f")) {
fs = 1;
}
}
#endif // ANDROID
vanilla_install_logger(vpilog_va);
// Load config
vpi_config_init();
// Initialize UI system
vui_context_t *vui = vui_alloc(SCREEN_WIDTH, SCREEN_HEIGHT);
// Initialize SDL2
int ret = 1;
if (vui_init_sdl(vui, fs)) {
vpilog("Failed to initialize VUI\n");
goto exit;
}
return AV_PIX_FMT_NONE;
}
vpi_menu_init(vui);
int decode(void *data, size_t size)
{
int err;
while (vui_update_sdl(vui)) {
}
// Parse this data for packets
err = av_packet_from_data(video_packet, (uint8_t *) data, size);
if (err < 0) {
fprintf(stderr, "Failed to initialize AVPacket from data: %s (%i)\n", av_err2str(err), err);
av_free(data);
return 0;
}
ret = 0;
// Send packet to decoder
err = avcodec_send_packet(video_codec_ctx, video_packet);
av_packet_unref(video_packet);
if (err < 0) {
fprintf(stderr, "Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
return 0;
}
vpi_stop_pipe();
int ret = 1;
exit:
vui_close_sdl(vui);
// Retrieve frame from decoder
while (1) {
err = avcodec_receive_frame(video_codec_ctx, decoding_frame);
if (err == AVERROR(EAGAIN)) {
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
fprintf(stderr, "Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else if (!decoding_frame->data[0]) {
fprintf(stderr, "WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n");
abort();
} else if ((decoding_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
fprintf(stderr, "GOT A CORRUPT FRAME??????\n");
abort();
} else {
pthread_mutex_lock(&decoding_mutex);
vui_free(vui);
// Swap refs from decoding_frame to present_frame
av_frame_unref(present_frame);
av_frame_move_ref(present_frame, decoding_frame);
vpi_config_free();
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
}
}
return ret;
}
static SDL_mutex *decode_loop_mutex;
static SDL_cond *decode_loop_cond;
static int decode_loop_running = 0;
static uint8_t *decode_data = NULL;
static size_t decode_size = 0;
int decode_loop(void *)
{
SDL_LockMutex(decode_loop_mutex);
while (decode_loop_running) {
while (decode_loop_running && !decode_data) {
SDL_CondWait(decode_loop_cond, decode_loop_mutex);
}
if (!decode_loop_running) {
break;
}
// Copy into av_malloc buffer
uint8_t *our_data = decode_data;
size_t our_size = decode_size;
decode_data = NULL;
decode_size = 0;
SDL_UnlockMutex(decode_loop_mutex);
decode(our_data, our_size);
SDL_LockMutex(decode_loop_mutex);
}
SDL_UnlockMutex(decode_loop_mutex);
}
int run_backend(void *data)
{
SDL_AudioSpec desired = {0};
desired.freq = 48000;
desired.format = AUDIO_S16LSB;
desired.channels = 2;
SDL_AudioDeviceID audio = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
if (audio) {
SDL_PauseAudioDevice(audio, 0);
} else {
printf("Failed to open audio device\n");
}
vanilla_event_t event;
decode_loop_mutex = SDL_CreateMutex();
decode_loop_cond = SDL_CreateCond();
decode_loop_running = 1;
SDL_Thread *decode_thread = SDL_CreateThread(decode_loop, "vanilla-decode", NULL);
while (vanilla_wait_event(&event)) {
if (event.type == VANILLA_EVENT_VIDEO) {
SDL_LockMutex(decode_loop_mutex);
if (decode_data) {
av_free(decode_data);
decode_data = NULL;
}
decode_size = event.size;
decode_data = av_malloc(event.size);
memcpy(decode_data, event.data, event.size);
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
} else if (event.type == VANILLA_EVENT_AUDIO) {
if (audio) {
if (SDL_QueueAudio(audio, event.data, event.size) < 0) {
printf("Failed to queue audio\n", event.size);
}
}
} else if (event.type == VANILLA_EVENT_VIBRATE) {
vibrate = event.data[0];
}
vanilla_free_event(&event);
}
SDL_LockMutex(decode_loop_mutex);
decode_loop_running = 0;
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
SDL_WaitThread(decode_thread, NULL);
SDL_DestroyCond(decode_loop_cond);
SDL_DestroyMutex(decode_loop_mutex);
SDL_CloseAudioDevice(audio);
return 0;
}
void logger(const char *s, va_list args)
{
vfprintf(stderr, s, args);
}
int display_loop(void *data)
{
vanilla_drm_ctx_t *drm_ctx = (vanilla_drm_ctx_t *) data;
Uint32 start_ticks = SDL_GetTicks();
#define TICK_MAX 30
Uint32 tick_deltas[TICK_MAX];
size_t tick_delta_index = 0;
AVFrame *frame = av_frame_alloc();
pthread_mutex_lock(&decoding_mutex);
while (running) {
while (running && !present_frame->data[0]) {
pthread_cond_wait(&decoding_wait_cond, &decoding_mutex);
}
if (!running) {
break;
}
// If a frame is available, present it here
av_frame_unref(frame);
av_frame_move_ref(frame, present_frame);
pthread_mutex_unlock(&decoding_mutex);
display_drm(drm_ctx, frame);
// FPS counter stuff
Uint32 now = SDL_GetTicks();
tick_deltas[tick_delta_index] = (now - start_ticks);
start_ticks = now;
tick_delta_index++;
if (tick_delta_index == TICK_MAX) {
Uint32 total = 0;
for (int i = 0; i < TICK_MAX; i++) {
total += tick_deltas[i];
}
fprintf(stderr, "AVERAGE FPS: %.2f\n", 1000.0f / (total / (float) TICK_MAX));
tick_delta_index = 0;
}
pthread_mutex_lock(&decoding_mutex);
}
pthread_mutex_unlock(&decoding_mutex);
av_frame_free(&frame);
return 0;
}
SDL_GameController *find_valid_controller()
{
for (int i = 0; i < SDL_NumJoysticks(); i++) {
SDL_GameController *c = SDL_GameControllerOpen(i);
if (c) {
return c;
}
}
return NULL;
}
void sigint_handler(int signum)
{
printf("Received SIGINT...\n");
running = 0;
SDL_QuitEvent ev;
ev.type = SDL_QUIT;
ev.timestamp = SDL_GetTicks();
SDL_PushEvent((SDL_Event *) &ev);
}
void init_gamepad()
{
vibrate = 0;
button_map[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A;
button_map[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B;
button_map[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X;
button_map[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y;
button_map[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS;
button_map[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME;
button_map[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV;
button_map[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS;
button_map[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3;
button_map[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R;
button_map[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP;
button_map[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN;
button_map[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT;
button_map[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT;
axis_map[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X;
axis_map[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y;
axis_map[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X;
axis_map[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR;
}
int32_t pack_float(float f)
{
int32_t x;
memcpy(&x, &f, sizeof(int32_t));
return x;
}
int main(int argc, const char **argv)
{
vanilla_drm_ctx_t drm_ctx;
Uint32 sdl_flags = SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER;
#ifdef RASPBERRY_PI
if (!initialize_drm(&drm_ctx)) {
fprintf(stderr, "Failed to find DRM output\n");
//return 1;
}
#else
sdl_flags |= SDL_INIT_VIDEO;
#endif
// Initialize SDL2 for audio and input
if (SDL_Init(sdl_flags) < 0) {
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
return 1;
}
// Initialize FFmpeg
#ifdef RASPBERRY_PI
const AVCodec *codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
#else
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
#endif
if (!codec) {
fprintf(stderr, "No decoder was available\n");
return 1;
}
video_codec_ctx = avcodec_alloc_context3(codec);
if (!video_codec_ctx) {
fprintf(stderr, "Failed to allocate codec context\n");
return 1;
}
int ffmpeg_err;
#ifdef RASPBERRY_PI
ffmpeg_err = av_hwdevice_ctx_create(&video_codec_ctx->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err);
return 1;
}
// MAKE SURE WE GET DRM PRIME FRAMES BY OVERRIDING THE GET_FORMAT FUNCTION
video_codec_ctx->get_format = get_format;
#endif
ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to open decoder: %i\n", ffmpeg_err);
return 1;
}
decoding_frame = av_frame_alloc();
present_frame = av_frame_alloc();
if (!decoding_frame || !present_frame) {
fprintf(stderr, "Failed to allocate AVFrame\n");
return 1;
}
video_packet = av_packet_alloc();
if (!video_packet) {
fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError());
return 1;
}
// Install logging debugger
vanilla_install_logger(logger);
// Start Vanilla
#ifdef RASPBERRY_PI
vanilla_start(0);
#else
vanilla_start(ntohl(inet_addr("127.0.0.1")));
#endif
// Initialize gamepad lookup tables
init_gamepad();
// Create variable for background threads to run
running = 1;
signal(SIGINT, sigint_handler);
pthread_mutex_init(&decoding_mutex, NULL);
pthread_cond_init(&decoding_wait_cond, NULL);
// Start background threads
SDL_Thread *backend_thread = SDL_CreateThread(run_backend, "vanilla-pi-backend", NULL);
#ifdef RASPBERRY_PI
SDL_Thread *display_thread = SDL_CreateThread(display_loop, "vanilla-pi-display", &drm_ctx);
#else
SDL_Window *window = SDL_CreateWindow("vanilla-pi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_RESIZABLE);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
AVFrame *frame = av_frame_alloc();
#endif
SDL_GameController *controller = find_valid_controller();
vui_context_t *vui = vui_alloc();
Uint32 ticks = SDL_GetTicks();
while (running) {
vui_reset(vui);
int sync_btn = vui_draw_button(vui, 100, 100, 100, 100, "Sync");
int connect_btn = vui_draw_button(vui, 300, 100, 100, 100, "Connect");
SDL_Event event;
#ifdef RASPBERRY_PI
if (SDL_WaitEvent(&event)) {
#else
while (SDL_PollEvent(&event)) {
#endif
switch (event.type) {
case SDL_QUIT:
// Exit loop
running = 0;
break;
case SDL_CONTROLLERDEVICEADDED:
// Attempt to find controller if one doesn't exist already
if (!controller) {
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERDEVICEREMOVED:
// Handle current controller being removed
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
SDL_GameControllerClose(controller);
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_btn = button_map[event.cbutton.button];
if (vanilla_btn != -1) {
vanilla_set_button(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0);
}
}
break;
case SDL_CONTROLLERAXISMOTION:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_axis = axis_map[event.caxis.axis];
Sint16 axis_value = event.caxis.value;
if (vanilla_axis != -1) {
vanilla_set_button(vanilla_axis, axis_value);
}
}
break;
case SDL_CONTROLLERSENSORUPDATE:
if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(event.csensor.data[2]));
} else if (event.csensor.sensor == SDL_SENSOR_GYRO) {
vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(event.csensor.data[2]));
}
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
}
break;
case SDL_MOUSEBUTTONDOWN:
{
int btn = vui_process_click(vui, event.button.x, event.button.y);
if (btn != -1) {
if (btn == sync_btn) {
// Go to sync menu
} else if (btn == connect_btn) {
// Go to connect menu
}
}
break;
}
}
if (controller) {
uint16_t amount = vibrate ? 0xFFFF : 0;
SDL_GameControllerRumble(controller, amount, amount, 0);
}
}
#ifndef RASPBERRY_PI
pthread_mutex_lock(&decoding_mutex);
if (present_frame->data[0]) {
av_frame_move_ref(frame, present_frame);
}
pthread_mutex_unlock(&decoding_mutex);
if (frame->data[0]) {
SDL_UpdateYUVTexture(texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
av_frame_unref(frame);
SDL_RenderCopy(renderer, texture, NULL, NULL);
}
SDL_RenderPresent(renderer);
#endif
}
vui_free(vui);
// Terminate background threads and wait for them to end gracefully
vanilla_stop();
pthread_mutex_lock(&decoding_mutex);
running = 0;
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
SDL_WaitThread(backend_thread, NULL);
#ifdef RASPBERRY_PI
SDL_WaitThread(display_thread, NULL);
#else
av_frame_free(&frame);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
#endif
pthread_cond_destroy(&decoding_wait_cond);
pthread_mutex_destroy(&decoding_mutex);
av_frame_free(&present_frame);
av_frame_free(&decoding_frame);
av_packet_free(&video_packet);
avcodec_free_context(&video_codec_ctx);
SDL_Quit();
free_drm(&drm_ctx);
return 0;
return ret;
}
-49
View File
@@ -1,49 +0,0 @@
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "menu/menu.h"
#include "ui/ui.h"
#include "ui/ui_sdl.h"
#define SCREEN_WIDTH 854
#define SCREEN_HEIGHT 480
int main(int argc, const char **argv)
{
int fs = 0;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-f")) {
fs = 1;
}
}
// Load config
vpi_config_init();
// Initialize UI system
vui_context_t *vui = vui_alloc(SCREEN_WIDTH, SCREEN_HEIGHT);
// Initialize SDL2
int ret = 1;
if (vui_init_sdl(vui, fs)) {
fprintf(stderr, "Failed to initialize VUI\n");
goto exit;
}
vpi_menu_init(vui);
while (vui_update_sdl(vui)) {
}
ret = 0;
exit:
vui_close_sdl(vui);
vui_free(vui);
vpi_config_free();
return ret;
}
+564
View File
@@ -0,0 +1,564 @@
#define SCREEN_WIDTH 854
#define SCREEN_HEIGHT 480
#include <arpa/inet.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <SDL2/SDL.h>
#include <vanilla.h>
#include <unistd.h>
#include "drm.h"
#include "ui.h"
AVFrame *present_frame;
AVFrame *decoding_frame;
pthread_mutex_t decoding_mutex;
pthread_cond_t decoding_wait_cond;
AVCodecContext *video_codec_ctx;
AVPacket *video_packet;
int running = 0;
static int button_map[SDL_CONTROLLER_BUTTON_MAX] = {-1};
static int axis_map[SDL_CONTROLLER_AXIS_MAX] = {-1};
static int vibrate = 0;
// HACK: Easy macro to test between desktop and RPi (even though ARM doesn't necessarily mean RPi)
#ifdef __arm__
#define RASPBERRY_PI
#endif
enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt)
{
while (*pix_fmt != AV_PIX_FMT_NONE) {
if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) {
return AV_PIX_FMT_DRM_PRIME;
}
pix_fmt++;
}
return AV_PIX_FMT_NONE;
}
int decode(void *data, size_t size)
{
int err;
// Parse this data for packets
err = av_packet_from_data(video_packet, (uint8_t *) data, size);
if (err < 0) {
fprintf(stderr, "Failed to initialize AVPacket from data: %s (%i)\n", av_err2str(err), err);
av_free(data);
return 0;
}
// Send packet to decoder
err = avcodec_send_packet(video_codec_ctx, video_packet);
av_packet_unref(video_packet);
if (err < 0) {
fprintf(stderr, "Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
return 0;
}
int ret = 1;
// Retrieve frame from decoder
while (1) {
err = avcodec_receive_frame(video_codec_ctx, decoding_frame);
if (err == AVERROR(EAGAIN)) {
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
fprintf(stderr, "Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else if (!decoding_frame->data[0]) {
fprintf(stderr, "WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n");
abort();
} else if ((decoding_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
fprintf(stderr, "GOT A CORRUPT FRAME??????\n");
abort();
} else {
pthread_mutex_lock(&decoding_mutex);
// Swap refs from decoding_frame to present_frame
av_frame_unref(present_frame);
av_frame_move_ref(present_frame, decoding_frame);
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
}
}
return ret;
}
static SDL_mutex *decode_loop_mutex;
static SDL_cond *decode_loop_cond;
static int decode_loop_running = 0;
static uint8_t *decode_data = NULL;
static size_t decode_size = 0;
int decode_loop(void *)
{
SDL_LockMutex(decode_loop_mutex);
while (decode_loop_running) {
while (decode_loop_running && !decode_data) {
SDL_CondWait(decode_loop_cond, decode_loop_mutex);
}
if (!decode_loop_running) {
break;
}
// Copy into av_malloc buffer
uint8_t *our_data = decode_data;
size_t our_size = decode_size;
decode_data = NULL;
decode_size = 0;
SDL_UnlockMutex(decode_loop_mutex);
decode(our_data, our_size);
SDL_LockMutex(decode_loop_mutex);
}
SDL_UnlockMutex(decode_loop_mutex);
}
int run_backend(void *data)
{
SDL_AudioSpec desired = {0};
desired.freq = 48000;
desired.format = AUDIO_S16LSB;
desired.channels = 2;
SDL_AudioDeviceID audio = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
if (audio) {
SDL_PauseAudioDevice(audio, 0);
} else {
printf("Failed to open audio device\n");
}
vanilla_event_t event;
decode_loop_mutex = SDL_CreateMutex();
decode_loop_cond = SDL_CreateCond();
decode_loop_running = 1;
SDL_Thread *decode_thread = SDL_CreateThread(decode_loop, "vanilla-decode", NULL);
while (vanilla_wait_event(&event)) {
if (event.type == VANILLA_EVENT_VIDEO) {
SDL_LockMutex(decode_loop_mutex);
if (decode_data) {
av_free(decode_data);
decode_data = NULL;
}
decode_size = event.size;
decode_data = av_malloc(event.size);
memcpy(decode_data, event.data, event.size);
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
} else if (event.type == VANILLA_EVENT_AUDIO) {
if (audio) {
if (SDL_QueueAudio(audio, event.data, event.size) < 0) {
printf("Failed to queue audio\n", event.size);
}
}
} else if (event.type == VANILLA_EVENT_VIBRATE) {
vibrate = event.data[0];
}
vanilla_free_event(&event);
}
SDL_LockMutex(decode_loop_mutex);
decode_loop_running = 0;
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
SDL_WaitThread(decode_thread, NULL);
SDL_DestroyCond(decode_loop_cond);
SDL_DestroyMutex(decode_loop_mutex);
SDL_CloseAudioDevice(audio);
return 0;
}
void logger(const char *s, va_list args)
{
vfprintf(stderr, s, args);
}
int display_loop(void *data)
{
vanilla_drm_ctx_t *drm_ctx = (vanilla_drm_ctx_t *) data;
Uint32 start_ticks = SDL_GetTicks();
#define TICK_MAX 30
Uint32 tick_deltas[TICK_MAX];
size_t tick_delta_index = 0;
AVFrame *frame = av_frame_alloc();
pthread_mutex_lock(&decoding_mutex);
while (running) {
while (running && !present_frame->data[0]) {
pthread_cond_wait(&decoding_wait_cond, &decoding_mutex);
}
if (!running) {
break;
}
// If a frame is available, present it here
av_frame_unref(frame);
av_frame_move_ref(frame, present_frame);
pthread_mutex_unlock(&decoding_mutex);
display_drm(drm_ctx, frame);
// FPS counter stuff
Uint32 now = SDL_GetTicks();
tick_deltas[tick_delta_index] = (now - start_ticks);
start_ticks = now;
tick_delta_index++;
if (tick_delta_index == TICK_MAX) {
Uint32 total = 0;
for (int i = 0; i < TICK_MAX; i++) {
total += tick_deltas[i];
}
fprintf(stderr, "AVERAGE FPS: %.2f\n", 1000.0f / (total / (float) TICK_MAX));
tick_delta_index = 0;
}
pthread_mutex_lock(&decoding_mutex);
}
pthread_mutex_unlock(&decoding_mutex);
av_frame_free(&frame);
return 0;
}
SDL_GameController *find_valid_controller()
{
for (int i = 0; i < SDL_NumJoysticks(); i++) {
SDL_GameController *c = SDL_GameControllerOpen(i);
if (c) {
return c;
}
}
return NULL;
}
void sigint_handler(int signum)
{
printf("Received SIGINT...\n");
running = 0;
SDL_QuitEvent ev;
ev.type = SDL_QUIT;
ev.timestamp = SDL_GetTicks();
SDL_PushEvent((SDL_Event *) &ev);
}
void init_gamepad()
{
vibrate = 0;
button_map[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A;
button_map[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B;
button_map[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X;
button_map[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y;
button_map[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS;
button_map[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME;
button_map[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV;
button_map[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS;
button_map[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3;
button_map[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R;
button_map[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP;
button_map[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN;
button_map[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT;
button_map[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT;
axis_map[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X;
axis_map[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y;
axis_map[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X;
axis_map[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR;
}
int32_t pack_float(float f)
{
int32_t x;
memcpy(&x, &f, sizeof(int32_t));
return x;
}
int main(int argc, const char **argv)
{
vanilla_drm_ctx_t drm_ctx;
Uint32 sdl_flags = SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER;
#ifdef RASPBERRY_PI
if (!initialize_drm(&drm_ctx)) {
fprintf(stderr, "Failed to find DRM output\n");
//return 1;
}
#else
sdl_flags |= SDL_INIT_VIDEO;
#endif
// Initialize SDL2 for audio and input
if (SDL_Init(sdl_flags) < 0) {
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
return 1;
}
// Initialize FFmpeg
#ifdef RASPBERRY_PI
const AVCodec *codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
#else
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
#endif
if (!codec) {
fprintf(stderr, "No decoder was available\n");
return 1;
}
video_codec_ctx = avcodec_alloc_context3(codec);
if (!video_codec_ctx) {
fprintf(stderr, "Failed to allocate codec context\n");
return 1;
}
int ffmpeg_err;
#ifdef RASPBERRY_PI
ffmpeg_err = av_hwdevice_ctx_create(&video_codec_ctx->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err);
return 1;
}
// MAKE SURE WE GET DRM PRIME FRAMES BY OVERRIDING THE GET_FORMAT FUNCTION
video_codec_ctx->get_format = get_format;
#endif
ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to open decoder: %i\n", ffmpeg_err);
return 1;
}
decoding_frame = av_frame_alloc();
present_frame = av_frame_alloc();
if (!decoding_frame || !present_frame) {
fprintf(stderr, "Failed to allocate AVFrame\n");
return 1;
}
video_packet = av_packet_alloc();
if (!video_packet) {
fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError());
return 1;
}
// Install logging debugger
vanilla_install_logger(logger);
// Start Vanilla
#ifdef RASPBERRY_PI
vanilla_start(0);
#else
vanilla_start(ntohl(inet_addr("127.0.0.1")));
#endif
// Initialize gamepad lookup tables
init_gamepad();
// Create variable for background threads to run
running = 1;
signal(SIGINT, sigint_handler);
pthread_mutex_init(&decoding_mutex, NULL);
pthread_cond_init(&decoding_wait_cond, NULL);
// Start background threads
SDL_Thread *backend_thread = SDL_CreateThread(run_backend, "vanilla-pi-backend", NULL);
#ifdef RASPBERRY_PI
SDL_Thread *display_thread = SDL_CreateThread(display_loop, "vanilla-pi-display", &drm_ctx);
#else
SDL_Window *window = SDL_CreateWindow("vanilla-pi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_RESIZABLE);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
AVFrame *frame = av_frame_alloc();
#endif
SDL_GameController *controller = find_valid_controller();
vui_context_t *vui = vui_alloc();
Uint32 ticks = SDL_GetTicks();
while (running) {
vui_reset(vui);
int sync_btn = vui_draw_button(vui, 100, 100, 100, 100, "Sync");
int connect_btn = vui_draw_button(vui, 300, 100, 100, 100, "Connect");
SDL_Event event;
#ifdef RASPBERRY_PI
if (SDL_WaitEvent(&event)) {
#else
while (SDL_PollEvent(&event)) {
#endif
switch (event.type) {
case SDL_QUIT:
// Exit loop
running = 0;
break;
case SDL_CONTROLLERDEVICEADDED:
// Attempt to find controller if one doesn't exist already
if (!controller) {
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERDEVICEREMOVED:
// Handle current controller being removed
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
SDL_GameControllerClose(controller);
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_btn = button_map[event.cbutton.button];
if (vanilla_btn != -1) {
vanilla_set_button(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0);
}
}
break;
case SDL_CONTROLLERAXISMOTION:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_axis = axis_map[event.caxis.axis];
Sint16 axis_value = event.caxis.value;
if (vanilla_axis != -1) {
vanilla_set_button(vanilla_axis, axis_value);
}
}
break;
case SDL_CONTROLLERSENSORUPDATE:
if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(event.csensor.data[2]));
} else if (event.csensor.sensor == SDL_SENSOR_GYRO) {
vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(event.csensor.data[2]));
}
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
}
break;
case SDL_MOUSEBUTTONDOWN:
{
int btn = vui_process_click(vui, event.button.x, event.button.y);
if (btn != -1) {
if (btn == sync_btn) {
// Go to sync menu
} else if (btn == connect_btn) {
// Go to connect menu
}
}
break;
}
}
if (controller) {
uint16_t amount = vibrate ? 0xFFFF : 0;
SDL_GameControllerRumble(controller, amount, amount, 0);
}
}
#ifndef RASPBERRY_PI
pthread_mutex_lock(&decoding_mutex);
if (present_frame->data[0]) {
av_frame_move_ref(frame, present_frame);
}
pthread_mutex_unlock(&decoding_mutex);
if (frame->data[0]) {
SDL_UpdateYUVTexture(texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
av_frame_unref(frame);
SDL_RenderCopy(renderer, texture, NULL, NULL);
}
SDL_RenderPresent(renderer);
#endif
}
vui_free(vui);
// Terminate background threads and wait for them to end gracefully
vanilla_stop();
pthread_mutex_lock(&decoding_mutex);
running = 0;
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
SDL_WaitThread(backend_thread, NULL);
#ifdef RASPBERRY_PI
SDL_WaitThread(display_thread, NULL);
#else
av_frame_free(&frame);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
#endif
pthread_cond_destroy(&decoding_wait_cond);
pthread_mutex_destroy(&decoding_mutex);
av_frame_free(&present_frame);
av_frame_free(&decoding_frame);
av_packet_free(&video_packet);
avcodec_free_context(&video_codec_ctx);
SDL_Quit();
free_drm(&drm_ctx);
return 0;
}
+48
View File
@@ -3,11 +3,59 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <vanilla.h>
#include "game/game_decode.h"
#include "game/game_main.h"
#include "menu_main.h"
#include "platform.h"
void vpi_menu_init(vui_context_t *vui)
{
// Start with main menu
vpi_menu_main(vui, 0);
}
void get_valid_filename(const char *fmt, char *abs_buf, size_t size_abs_buf)
{
char buf[100];
int index = 0;
do {
index++;
snprintf(buf, sizeof(buf), fmt, index);
vpi_get_data_filename(abs_buf, size_abs_buf, buf);
} while (access(abs_buf, F_OK) == 0);
}
void vpi_menu_action(vui_context_t *vui, vpi_extra_action_t action)
{
switch (action) {
case VPI_ACTION_SCREENSHOT:
{
char ss_fn[4096];
get_valid_filename("Screenshot-%04i.png", ss_fn, sizeof(ss_fn));
vpi_decode_screenshot(ss_fn);
break;
}
case VPI_ACTION_TOGGLE_RECORDING:
{
int recording = vpi_decode_is_recording();
if (!recording) {
char mov_fn[4096];
get_valid_filename("Recording-%04i.mp4", mov_fn, sizeof(mov_fn));
vpi_decode_record(mov_fn);
} else {
vpi_decode_record_stop();
}
break;
}
case VPI_ACTION_DISCONNECT:
{
// Simulate shutdown
vpi_game_set_error(VANILLA_ERR_SHUTDOWN);
break;
}
}
}
+9
View File
@@ -3,6 +3,15 @@
#include "ui/ui.h"
typedef enum {
VPI_ACTION_START_INDEX = 10000,
VPI_ACTION_SCREENSHOT,
VPI_ACTION_TOGGLE_RECORDING,
VPI_ACTION_DISCONNECT,
} vpi_extra_action_t;
void vpi_menu_init(vui_context_t *vui);
void vpi_menu_action(vui_context_t *vui, vpi_extra_action_t action);
#endif // VANILLA_PI_MENU_H
+107
View File
@@ -1,5 +1,17 @@
#include "menu_common.h"
#include <stdio.h>
#include "config.h"
#include "lang.h"
#include "menu_connection.h"
#include "menu_main.h"
#include "menu_sync.h"
#include "pipemgmt.h"
#include "ui/ui_anim.h"
int err_fglayer;
void vpi_menu_create_background(vui_context_t *vui, int layer, vui_rect_t *bkg_rect, int *margin)
{
int scrw, scrh;
@@ -14,3 +26,98 @@ void vpi_menu_create_background(vui_context_t *vui, int layer, vui_rect_t *bkg_r
vui_rect_create(vui, bkg_rect->x, bkg_rect->y, bkg_rect->w, bkg_rect->h, scrh*1/10, vui_color_create(0, 0, 0, 0.66f), layer);
}
void vpi_menu_create_back_button(vui_context_t *vui, int layer, vui_button_callback_t action, void *action_data)
{
int cbtn = vui_button_create(vui, 0, 0, BTN_SZ, BTN_SZ, lang(VPI_LANG_BACK), 0, VUI_BUTTON_STYLE_CORNER, layer, action, action_data);
vui_button_set_cancel(vui, cbtn);
}
struct vpi_menu_start_pipe_data {
vui_callback_t success_action;
void *success_data;
vui_callback_t cancel_action;
void *cancel_data;
};
void vpi_menu_start_pipe_thunk(vui_context_t *vui, void *data)
{
struct vpi_menu_start_pipe_data *p = (struct vpi_menu_start_pipe_data *) data;
vpi_menu_start_pipe(vui, 1, p->success_action, p->success_data, p->cancel_action, p->cancel_data);
free(p);
}
void back_to_main_menu_action(vui_context_t *vui, int btn, void *v)
{
vui_transition_fade_layer_out(vui, err_fglayer, vpi_menu_main, (void *) (intptr_t) 1);
}
void vpi_menu_exit_pipe_thunk(vui_context_t *vui, void *data)
{
struct vpi_menu_start_pipe_data *p = (struct vpi_menu_start_pipe_data *) data;
free(p);
vpi_menu_main(vui, (void *) (intptr_t) 1);
}
void jump_to_connection_setup(vui_context_t *vui, int fade_fglayer, vui_callback_t success_action, void *success_data, vui_callback_t cancel_action, void *cancel_data)
{
struct vpi_menu_start_pipe_data *p = malloc(sizeof(struct vpi_menu_start_pipe_data));
p->success_action = success_action;
p->success_data = success_data;
p->cancel_action = cancel_action;
p->cancel_data = cancel_data;
vpi_menu_connection_and_return_to(vui, fade_fglayer, vpi_menu_start_pipe_thunk, p, vpi_menu_exit_pipe_thunk, p);
}
void vpi_menu_start_pipe(vui_context_t *vui, int fade_fglayer, vui_callback_t success_action, void *success_data, vui_callback_t cancel_action, void *cancel_data)
{
if (!vpi_config.connection_setup) {
jump_to_connection_setup(vui, fade_fglayer, success_action, success_data, cancel_action, cancel_data);
} else if (vpi_config.server_address == VANILLA_ADDRESS_LOCAL) {
#ifdef VANILLA_PIPE_AVAILABLE
int r = vpi_start_pipe();
if (r == VANILLA_SUCCESS) {
success_action(vui, success_data);
} else {
if (cancel_action) {
cancel_action(vui, cancel_data);
}
err_fglayer = vpi_menu_show_error(vui, r, 0, back_to_main_menu_action, 0);
}
#else
// Local is not available on this platform
jump_to_connection_setup(vui, fade_fglayer, success_action, success_data, cancel_action, cancel_data);
#endif
} else {
// Using a middleman server, proceed as usual
success_action(vui, success_data);
}
}
int vpi_menu_show_error(vui_context_t *vui, int status, int fade_fglayer, vui_button_callback_t ok_action, void *ok_data)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
int fglayer = vui_layer_create(vui);
vui_label_create(vui, bkg_rect.x, bkg_rect.y + bkg_rect.h/4, bkg_rect.w, bkg_rect.h, lang(VPI_LANG_SYNC_FAILED), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
char buf[20];
snprintf(buf, sizeof(buf), "%i", status);
vui_label_create(vui, bkg_rect.x, bkg_rect.y + bkg_rect.h/2, bkg_rect.w, bkg_rect.h, buf, vui_color_create(1,0,0,1), VUI_FONT_SIZE_SMALL, fglayer);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - BTN_SZ, bkg_rect.y + bkg_rect.h * 3 / 4, BTN_SZ*2, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, ok_action, ok_data);
if (fade_fglayer)
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
else
vui_transition_fade_black_out(vui, 0, 0);
return bglayer;
}
+5
View File
@@ -1,10 +1,15 @@
#ifndef VANILLA_MENU_COMMON_H
#define VANILLA_MENU_COMMON_H
#include "platform.h"
#include "ui/ui.h"
#define BTN_SZ 80
void vpi_menu_create_background(vui_context_t *vui, int layer, vui_rect_t *bkg_rect, int *margin);
void vpi_menu_create_back_button(vui_context_t *vui, int layer, vui_button_callback_t action, void *action_data);
void vpi_menu_start_pipe(vui_context_t *vui, int fade_fglayer, vui_callback_t success_action, void *success_data, vui_callback_t cancel_action, void *cancel_data);
int vpi_menu_show_error(vui_context_t *vui, int status, int fade_fglayer, vui_button_callback_t ok_action, void *ok_data);
#endif // VANILLA_MENU_COMMON_H
+273
View File
@@ -0,0 +1,273 @@
#include "menu_connection.h"
#ifdef _WIN32
#include <winsock2.h>
typedef uint32_t in_addr_t;
#else
#include <arpa/inet.h>
#endif
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "lang.h"
#include "menu_common.h"
#include "menu_settings.h"
#include "pipemgmt.h"
#include "ui/ui_anim.h"
static int fglayer;
static int error_lbl;
static int ip_textedit;
#define MAX_WIRELESS_ENTRIES 4
static char detected_wireless_interfaces[256][256];
static int intf_start = 0;
static int intf_buttons[MAX_WIRELESS_ENTRIES];
static const char *checkmark = 0;//"checkmark.svg";
static vui_callback_t g_continue_callback = 0;
static void *g_continue_callback_data = 0;
static vui_callback_t g_fail_callback = 0;
static void *g_fail_callback_data = 0;
static void return_to_settings(vui_context_t *vui, int btn, void *v)
{
int layer = (intptr_t) v;
vui_callback_t cb = g_fail_callback ? g_fail_callback : vpi_menu_settings;
void *cb_data = g_fail_callback ? g_fail_callback_data : 0;
vui_transition_fade_layer_out(vui, layer, cb, cb_data);
}
static void return_to_connection(vui_context_t *vui, int btn, void *v)
{
int layer = (intptr_t) v;
vui_callback_t cb = g_continue_callback ? g_continue_callback : vpi_menu_connection;
void *cb_data = g_continue_callback ? g_continue_callback_data : (void *) (intptr_t) 1;
vui_transition_fade_layer_out(vui, layer, cb, cb_data);
}
static void populate_wireless_intf()
{
// LINUX ONLY:
struct dirent *dir;
DIR *d = opendir("/sys/class/net");
char buf[1024];
size_t cur = 0;
if (d) {
while ((dir = readdir(d)) != 0) {
const char *intf = dir->d_name;
if (intf[0] != '.') {
snprintf(buf, sizeof(buf), "/sys/class/net/%s/wireless", intf);
DIR *wifi_check = opendir(buf);
if (wifi_check) {
strncpy(detected_wireless_interfaces[cur], intf, sizeof(detected_wireless_interfaces[cur]));
cur++;
closedir(wifi_check);
}
}
}
closedir(d);
}
}
static void intf_pressed(vui_context_t *vui, int button, void *v)
{
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
if (button == intf_buttons[i]) {
vpi_config.server_address = VANILLA_ADDRESS_LOCAL;
strncpy(vpi_config.wireless_interface, detected_wireless_interfaces[intf_start + i], sizeof(vpi_config.wireless_interface));
vpi_config.connection_setup = 1;
vpi_stop_pipe();
vpi_config_save();
break;
}
}
return_to_connection(vui, button, v);
}
static void update_wireless_buttons(vui_context_t *vui)
{
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
const char *intf = detected_wireless_interfaces[intf_start + i];
int b = intf_buttons[i];
if (intf[0] == 0) {
vui_button_update_visible(vui, b, 0);
} else {
vui_button_update_visible(vui, b, 1);
vui_button_update_text(vui, b, intf);
int sel = !strcmp(intf, vpi_config.wireless_interface);
vui_button_update_checked(vui, b, sel);
vui_button_update_icon(vui, b, sel ? checkmark : 0);
}
}
}
static void local_connection_menu(vui_context_t *vui, void *v)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_LOCAL_CONNECTION_HELP), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
// Detect wireless interfaces
memset(detected_wireless_interfaces, 0, sizeof(detected_wireless_interfaces));
populate_wireless_intf();
const int button_w = bkg_rect.w/2;
const int button_h = BTN_SZ*2/3;
const int button_x = bkg_rect.x+bkg_rect.w/2-button_w/2;
const int button_y = bkg_rect.y+bkg_rect.h*2/5;
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
intf_buttons[i] = vui_button_create(vui, button_x, button_y+button_h*i, button_w, button_h, "intf", 0, VUI_BUTTON_STYLE_BUTTON, fglayer, intf_pressed, (void *) (intptr_t) fglayer);
}
update_wireless_buttons(vui);
vpi_menu_create_back_button(vui, fglayer, return_to_connection, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
static void check_ip_address(vui_context_t *vui, int btn, void *v)
{
char buf[256];
vui_textedit_get_text(vui, ip_textedit, buf, sizeof(buf));
in_addr_t ip = inet_addr(buf);
int valid = (ip != (in_addr_t) -1);
vui_label_update_visible(vui, error_lbl, !valid);
if (valid) {
vpi_config.server_address = ip;
vpi_config.connection_setup = 1;
vpi_stop_pipe();
vpi_config_save();
return_to_connection(vui, btn, v);
}
}
static void via_server_connection_menu(vui_context_t *vui, void *v)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_SERVER_CONNECTION_HELP), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
const int textedit_w = bkg_rect.w/2;
const int textedit_h = vui_get_font_height(vui, VUI_FONT_SIZE_NORMAL);
const int textedit_x = bkg_rect.x + bkg_rect.w/2 - textedit_w/2;
const char *ip_text;
if (vpi_config.server_address == VANILLA_ADDRESS_LOCAL) {
ip_text = 0;
} else {
struct in_addr in;
in.s_addr = vpi_config.server_address;
ip_text = inet_ntoa(in);
}
ip_textedit = vui_textedit_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 3 / 7, textedit_w, textedit_h, ip_text, VUI_FONT_SIZE_NORMAL, fglayer);
error_lbl = vui_label_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 4 / 7, textedit_w, BTN_SZ, "Invalid IP address", vui_color_create(1,0,0,1), VUI_FONT_SIZE_SMALL, fglayer);
vui_label_update_visible(vui, error_lbl, 0);
vui_button_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 5 / 7, textedit_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, check_ip_address, (void *) (intptr_t) fglayer);
vpi_menu_create_back_button(vui, fglayer, return_to_connection, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
void connection_btn_pressed(vui_context_t *vui, int btn, void *v)
{
vui_callback_t cb = (vui_callback_t) v;
vui_transition_fade_layer_out(vui, fglayer, cb, 0);
}
void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui_callback_t success_callback, void *success_data, vui_callback_t fail_callback, void *fail_data)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_CONNECTION_HELP_1), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
const int btn_w = bkg_rect.w/2;
const int btn_y = bkg_rect.y + bkg_rect.h * 2 / 4;
const int btn_x = bkg_rect.x + bkg_rect.w/2 - btn_w/2;
int is_local = vpi_config.server_address == VANILLA_ADDRESS_LOCAL;
int is_via_server = vpi_config.server_address != VANILLA_ADDRESS_LOCAL;
const int lbl2_width = bkg_rect.w*2/3;
const char *lbl2_txt;
#ifdef VANILLA_PIPE_AVAILABLE
lbl2_txt = lang(VPI_LANG_CONNECTION_HELP_2);
#else
lbl2_txt = lang(VPI_LANG_NO_LOCAL_AVAILABLE);
is_local = 0;
#endif
vui_label_create(vui, bkg_rect.x + bkg_rect.w/2 - lbl2_width/2, bkg_rect.y + lbl_margin + bkg_rect.h/8, lbl2_width, bkg_rect.h - lbl_margin - lbl_margin, lbl2_txt, vui_color_create(0.66f,0.66f,0.66f,1), VUI_FONT_SIZE_SMALL, fglayer);
int local_btn = vui_button_create(vui, btn_x, btn_y, btn_w, BTN_SZ, lang(VPI_LANG_LOCAL), is_local ? checkmark : 0, VUI_BUTTON_STYLE_BUTTON, fglayer, connection_btn_pressed, local_connection_menu);
int via_server_btn = vui_button_create(vui, btn_x, btn_y + BTN_SZ, btn_w, BTN_SZ, lang(VPI_LANG_VIA_SERVER), is_via_server ? checkmark : 0, VUI_BUTTON_STYLE_BUTTON, fglayer, connection_btn_pressed, via_server_connection_menu);
vui_button_update_checkable(vui, local_btn, 1);
vui_button_update_checkable(vui, via_server_btn, 1);
vui_button_update_checked(vui, local_btn, is_local);
vui_button_update_checked(vui, via_server_btn, is_via_server);
#ifndef VANILLA_PIPE_AVAILABLE
vui_button_update_enabled(vui, local_btn, 0);
#endif
// Back button
vpi_menu_create_back_button(vui, fglayer, return_to_settings, (void *) (intptr_t) bglayer);
vui_transition_fade_layer_in(vui, fade_fglayer ? fglayer : bglayer, 0, 0);
g_continue_callback = success_callback;
g_continue_callback_data = success_data;
g_fail_callback = fail_callback;
g_fail_callback_data = fail_data;
}
void vpi_menu_connection(vui_context_t *vui, void *v)
{
vpi_menu_connection_and_return_to(vui, (intptr_t) v, 0, 0, 0, 0);
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef VANILLA_PI_MENU_CONNECTION_H
#define VANILLA_PI_MENU_CONNECTION_H
#include "ui/ui.h"
void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui_callback_t success_callback, void *success_data, vui_callback_t fail_callback, void *fail_data);
void vpi_menu_connection(vui_context_t *vui, void *v);
#endif // VANILLA_PI_MENU_CONNECTION_H
+1 -2
View File
@@ -45,7 +45,6 @@ void vpi_menu_delete_status(vui_context_t *vui, void *v)
snprintf(buf, sizeof(buf), lang(VPI_LANG_DELETE_SUCCESS), cname);
vui_label_create(vui, bkg_rect.x, bkg_rect.y + bkg_rect.h*1/5, bkg_rect.w, bkg_rect.h, buf, vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
printf("%s\n", buf);
static const int ok_btn_w = BTN_SZ * 3;
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w)/2, bkg_rect.y + bkg_rect.h * 3 / 4, ok_btn_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, vpi_menu_delete_return_to_main, v);
@@ -79,7 +78,7 @@ void vpi_menu_delete(vui_context_t *vui, void *v)
static const int cancel_btn_w = BTN_SZ * 3;
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2, bkg_rect.y + bkg_rect.h * 3 / 4, ok_btn_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, vpi_menu_delete_ok, v);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2 + ok_btn_w, bkg_rect.y + bkg_rect.h * 3 / 4, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, vpi_menu_delete_return_to_edit, (void *) (intptr_t) bglayer);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2 + ok_btn_w, bkg_rect.y + bkg_rect.h * 3 / 4, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, vpi_menu_delete_return_to_edit, v);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
+8 -2
View File
@@ -4,6 +4,7 @@
#include "lang.h"
#include "menu_common.h"
#include "menu_delete.h"
#include "menu_rename.h"
#include "menu_main.h"
#include "ui/ui_anim.h"
@@ -15,6 +16,11 @@ static void delete_btn_pressed(vui_context_t *vui, int btn, void *v)
vui_transition_fade_layer_out(vui, fglayer, vpi_menu_delete, v);
}
static void rename_btn_pressed(vui_context_t *vui, int btn, void *v)
{
vui_transition_fade_layer_out(vui, fglayer, vpi_menu_rename, v);
}
static void return_to_main(vui_context_t *vui, int btn, void *v)
{
int layer = (intptr_t) v;
@@ -38,11 +44,11 @@ void vpi_menu_edit_nofade(vui_context_t *vui, void *v)
const int btn_w = bkg_rect.w / 2;
const int btn_y = bkg_rect.y + bkg_rect.h * 1 / 2;
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - btn_w/2, btn_y + BTN_SZ*0, btn_w, BTN_SZ, lang(VPI_LANG_RENAME), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, 0, 0);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - btn_w/2, btn_y + BTN_SZ*0, btn_w, BTN_SZ, lang(VPI_LANG_RENAME), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, rename_btn_pressed, v);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - btn_w/2, btn_y + BTN_SZ*1, btn_w, BTN_SZ, lang(VPI_LANG_DELETE), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, delete_btn_pressed, v);
// Back button
vui_button_create(vui, 0, 0, BTN_SZ, BTN_SZ, lang(VPI_LANG_BACK), 0, VUI_BUTTON_STYLE_CORNER, fglayer, return_to_main, (void *) (intptr_t) bglayer);
vpi_menu_create_back_button(vui, fglayer, return_to_main, (void *) (intptr_t) bglayer);
}
void vpi_menu_edit(vui_context_t *vui, void *v)
+42 -6
View File
@@ -5,11 +5,14 @@
#include "config.h"
#include "lang.h"
#include "game/game_decode.h"
#include "game/game_main.h"
#include "menu_common.h"
#include "menu_main.h"
#include "ui/ui_anim.h"
static int status_lbl;
void back_to_main_menu(vui_context_t *vui, int button, void *v)
{
int layer = (intptr_t) v;
@@ -39,17 +42,43 @@ void show_error(vui_context_t *vui, void *v)
vui_transition_fade_black_out(vui, 0, 0);
}
void cancel_connect(vui_context_t *vui, int button, void *v)
{
int layer = (intptr_t) v;
vanilla_stop();
vui_transition_fade_layer_out(vui, layer, vpi_menu_main, 0);
}
void vpi_display_update(vui_context_t *vui, int64_t time, void *v)
{
int backend_err = vpi_game_error();
if (backend_err != VANILLA_SUCCESS) {
vui_game_mode_set(vui, 0);
show_error(vui, (void*)(intptr_t) backend_err);
return;
switch (backend_err) {
case VANILLA_ERR_CONNECTED:
// Wait for frame before enabling "game mode"
pthread_mutex_lock(&vpi_decode_loop_mutex);
if (vpi_decode_ready) {
vui_game_mode_set(vui, 1);
vpi_game_set_error(VANILLA_SUCCESS);
}
pthread_mutex_unlock(&vpi_decode_loop_mutex);
break;
case VANILLA_ERR_DISCONNECTED:
vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED));
vui_game_mode_set(vui, 0);
break;
case VANILLA_ERR_SHUTDOWN:
vanilla_stop();
vpi_menu_main(vui, 0);
break;
default:
vanilla_stop();
show_error(vui, (void*)(intptr_t) backend_err);
};
}
}
void vpi_menu_game(vui_context_t *vui, void *v)
void vpi_menu_game_start(vui_context_t *vui, void *v)
{
int console = (intptr_t) v;
@@ -69,13 +98,20 @@ void vpi_menu_game(vui_context_t *vui, void *v)
int scrw, scrh;
vui_get_screen_size(vui, &scrw, &scrh);
vui_label_create(vui, 0, scrh * 2 / 5, scrw, scrh, buf, vui_color_create(0.5f,0.5f,0.5f,1), VUI_FONT_SIZE_NORMAL, fglayer);
status_lbl = vui_label_create(vui, 0, scrh * 2 / 5, scrw, scrh, buf, vui_color_create(0.5f,0.5f,0.5f,1), VUI_FONT_SIZE_NORMAL, fglayer);
const int cancel_btn_w = BTN_SZ*3;
int cancel_btn = vui_button_create(vui, scrw/2 - cancel_btn_w/2, scrh * 3 / 5, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, cancel_connect, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
vpi_game_start(vui);
vui_game_mode_set(vui, 1);
vui_start_passive_animation(vui, vpi_display_update, 0);
}
}
void vpi_menu_game(vui_context_t *vui, void *v)
{
vpi_menu_start_pipe(vui, 0, vpi_menu_game_start, v, 0, 0);
}
+3 -3
View File
@@ -20,12 +20,12 @@ void vpi_menu_gamepad(vui_context_t *vui, void *v)
int scrw, scrh;
vui_get_screen_size(vui, &scrw, &scrh);
int img_w = scrw * 8 / 10;
int img_h = scrh * 8 / 10;
int img_w = scrw / 2;
int img_h = scrh / 2;
vui_image_create(vui, scrw/2-img_w/2, scrh/2-img_h/2, img_w, img_h, "gamepad.svg", layer);
// Back button
vui_button_create(vui, 0, 0, BTN_SZ, BTN_SZ, lang(VPI_LANG_BACK), 0, VUI_BUTTON_STYLE_CORNER, layer, return_to_settings, (void *) (intptr_t) layer);
vpi_menu_create_back_button(vui, layer, return_to_settings, (void *) (intptr_t) layer);
// More button
vui_button_create(vui, scrw-BTN_SZ, 0, BTN_SZ, BTN_SZ, lang(VPI_LANG_MORE), 0, VUI_BUTTON_STYLE_CORNER, layer, return_to_settings, (void *) (intptr_t) layer);
+4 -3
View File
@@ -15,7 +15,6 @@
static int main_menu_btns[MAIN_MENU_ENTRIES] = {0};
static int main_menu_first_run = 1;
static int main_menu_current = 0;
static int console_menu_layer = 0;
@@ -28,6 +27,8 @@ static int main_menu_no_console_lbl;
static int main_menu_connect_btn;
static int main_menu_edit_btn;
static const char *checkmark = "checkmark.svg";
void vpi_menu_main_sync_action(vui_context_t *vui, int btn, void *v)
{
vui_transition_fade_layer_out(vui, (int) (intptr_t) v, vpi_menu_sync, 0);
@@ -179,12 +180,12 @@ void vpi_menu_main(vui_context_t *vui, void *v)
int list_item_height = (SCREEN_HEIGHT - BTN_SZ - BTN_SZ) / MAIN_MENU_ENTRIES;
for (int i = 0; i < MAIN_MENU_ENTRIES; i++) {
main_menu_btns[i] = vui_button_create(vui, BTN_SZ, BTN_SZ + list_item_height * i, list_item_width, list_item_height, NULL, NULL, VUI_BUTTON_STYLE_LIST, console_menu_layer, vpi_menu_main_connect_console, (void *) (intptr_t) i);
vui_button_update_checkable(vui, main_menu_btns[i], 1);
}
vpi_menu_main_populate(vui);
if (main_menu_first_run) {
if (!v) {
vui_transition_fade_black_out(vui, NULL, NULL);
main_menu_first_run = 0;
} else {
vui_transition_fade_layer_in(vui, layer, NULL, NULL);
}
+53
View File
@@ -0,0 +1,53 @@
#include "menu_rename.h"
static int fglayer;
#include "config.h"
#include "lang.h"
#include "menu_common.h"
#include "menu_edit.h"
#include "ui/ui_anim.h"
static int textedit;
static void vpi_menu_rename_return_to_edit(vui_context_t *vui, int btn, void *v)
{
vui_transition_fade_layer_out(vui, fglayer, vpi_menu_edit_return, v);
}
static void commit_rename(vui_context_t *vui, int btn, void *v)
{
uint8_t console = (uintptr_t) v;
char buf[VPI_CONSOLE_MAX_NAME];
vui_textedit_get_text(vui, textedit, buf, sizeof(buf));
vpi_config_rename_console(console, buf);
vpi_menu_rename_return_to_edit(vui, btn, v);
}
void vpi_menu_rename(vui_context_t *vui, void *v)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
uint8_t console = (uintptr_t) v;
const int textedit_w = bkg_rect.w / 2;
const int textedit_h = vui_get_font_height(vui, VUI_FONT_SIZE_NORMAL);
textedit = vui_textedit_create(vui, bkg_rect.x + bkg_rect.w/2 - textedit_w/2, bkg_rect.y + bkg_rect.h * 1 / 3, textedit_w, textedit_h, vpi_config.connected_console_entries[console].name, VUI_FONT_SIZE_NORMAL, fglayer);
static const int ok_btn_w = BTN_SZ * 3;
static const int cancel_btn_w = BTN_SZ * 3;
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2, bkg_rect.y + bkg_rect.h * 3 / 4, ok_btn_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, commit_rename, v);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2 + ok_btn_w, bkg_rect.y + bkg_rect.h * 3 / 4, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, vpi_menu_rename_return_to_edit, v);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef VANILLA_PI_MENU_RENAME_H
#define VANILLA_PI_MENU_RENAME_H
#include "ui/ui.h"
void vpi_menu_rename(vui_context_t *vui, void *v);
#endif // VANILLA_PI_MENU_RENAME_H
+9 -2
View File
@@ -2,6 +2,7 @@
#include "lang.h"
#include "menu_common.h"
#include "menu_connection.h"
#include "menu_gamepad.h"
#include "menu_main.h"
#include "ui/ui_anim.h"
@@ -18,6 +19,12 @@ static void transition_to_gamepad(vui_context_t *vui, int button, void *v)
vui_transition_fade_layer_out(vui, layer, vpi_menu_gamepad, 0);
}
static void transition_to_connection(vui_context_t *vui, int button, void *v)
{
int layer = (intptr_t) v;
vui_transition_fade_layer_out(vui, layer, vpi_menu_connection, 0);
}
void vpi_menu_settings(vui_context_t *vui, void *v)
{
vui_reset(vui);
@@ -47,7 +54,7 @@ void vpi_menu_settings(vui_context_t *vui, void *v)
// Highlight currently implemented functionality
static const vui_button_callback_t SETTINGS_ACTION[] = {
transition_to_gamepad,
0,
transition_to_connection,
0,
0,
};
@@ -67,7 +74,7 @@ void vpi_menu_settings(vui_context_t *vui, void *v)
}
// Back button
vui_button_create(vui, 0, 0, BTN_SZ, BTN_SZ, lang(VPI_LANG_BACK), 0, VUI_BUTTON_STYLE_CORNER, fglayer, return_to_main, (void *) (intptr_t) fglayer);
vpi_menu_create_back_button(vui, fglayer, return_to_main, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
-55
View File
@@ -1,55 +0,0 @@
#include "menu_sudo.h"
#include "menu_common.h"
#include "lang.h"
#include "ui/ui_anim.h"
static int sudo_fglayer;
static vui_callback_t sudo_success;
static void *sudo_success_data;
static vui_callback_t sudo_cancel;
static void *sudo_cancel_data;
void cancel_sudo(vui_context_t *vui, int button, void *data)
{
vui_transition_fade_layer_out(vui, sudo_fglayer, sudo_cancel, sudo_cancel_data);
}
void ok_sudo(vui_context_t *vui, int button, void *data)
{
vui_transition_fade_layer_out(vui, sudo_fglayer, sudo_success, sudo_success_data);
}
void vpi_menu_sudo(vui_context_t *vui, vui_callback_t success, void *success_data, vui_callback_t cancel, void *cancel_data)
{
vui_reset(vui);
sudo_success = success;
sudo_success_data = success_data;
sudo_cancel = cancel;
sudo_cancel_data = cancel_data;
int bglayer = vui_layer_create(vui);
sudo_fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
int scrw, scrh;
vui_get_screen_size(vui, &scrw, &scrh);
const int lbl_margin = margin * 3;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_SUDO_HELP_1), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, sudo_fglayer);
const int lbl2_width = scrw/2;
vui_label_create(vui, scrw/2 - lbl2_width/2, bkg_rect.y + lbl_margin + scrh/5, lbl2_width, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_SUDO_HELP_2), vui_color_create(0.66f,0.66f,0.66f,1), VUI_FONT_SIZE_SMALL, sudo_fglayer);
static const int ok_btn_w = BTN_SZ * 3;
static const int cancel_btn_w = BTN_SZ * 3;
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2, bkg_rect.y + bkg_rect.h * 3 / 4, ok_btn_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, sudo_fglayer, ok_sudo, 0);
vui_button_create(vui, bkg_rect.x + bkg_rect.w/2 - (ok_btn_w + cancel_btn_w)/2 + ok_btn_w, bkg_rect.y + bkg_rect.h * 3 / 4, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, sudo_fglayer, cancel_sudo, 0);
vui_transition_fade_layer_in(vui, sudo_fglayer, 0, 0);
}
-8
View File
@@ -1,8 +0,0 @@
#ifndef VANILLA_PI_MENU_SUDO_H
#define VANILLA_PI_MENU_SUDO_H
#include "ui/ui.h"
void vpi_menu_sudo(vui_context_t *vui, vui_callback_t success, void *success_data, vui_callback_t cancel, void *cancel_data);
#endif // VANILLA_PI_MENU_SUDO_H
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff. Show more