Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48a9d3ac12 | |||
| efdd1e2046 | |||
| 8a40892865 | |||
| 20635a3012 | |||
| b908c5cec3 | |||
| 1cb0b723f6 | |||
| 3f3c573c79 | |||
| e1253bbb59 | |||
| b2eb953f45 | |||
| 9638c15c93 | |||
| 8ce972ea7a | |||
| 968557d3a8 | |||
| cf80a7380c | |||
| ceb957e7f3 | |||
| 669691d8e8 | |||
| ea57e48ed5 | |||
| f744c7d9c4 | |||
| 0d42beca93 | |||
| bafb9e6230 | |||
| 5d30a3f4ab | |||
| 70a1dd56b4 | |||
| 9857017b22 | |||
| 53474c7d28 | |||
| 283c1060a3 | |||
| eb7c54b95c | |||
| 735ad02fb4 |
+3
-3
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.limelight"
|
||||
android:versionCode="3"
|
||||
android:versionName="2.0.1" >
|
||||
android:versionCode="9"
|
||||
android:versionName="2.1.3" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
@@ -29,7 +29,7 @@
|
||||
<activity
|
||||
android:name="com.limelight.Game"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="@string/title_activity_game"
|
||||
android:parentActivityName="com.limelight.Connection"
|
||||
android:theme="@style/FullscreenTheme" >
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/hostTextView"
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<requestFocus />
|
||||
|
||||
</Button>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
android:id="@+id/pairButton"
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.limelight.nvstream.http.NvHTTP;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
@@ -52,6 +53,9 @@ public class Connection extends Activity {
|
||||
|
||||
setContentView(R.layout.activity_connection);
|
||||
|
||||
// Hide the keyboard by default
|
||||
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
|
||||
this.statusButton = (Button) findViewById(R.id.statusButton);
|
||||
this.pairButton = (Button) findViewById(R.id.pairButton);
|
||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||
@@ -159,6 +163,11 @@ public class Connection extends Activity {
|
||||
this.statusButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
if (Connection.this.hostText.getText().length() == 0) {
|
||||
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Connection.this, Game.class);
|
||||
intent.putExtra("host", Connection.this.hostText.getText().toString());
|
||||
Connection.this.startActivity(intent);
|
||||
|
||||
+114
-231
@@ -1,12 +1,14 @@
|
||||
package com.limelight;
|
||||
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.binding.input.ControllerHandler;
|
||||
import com.limelight.binding.input.KeyboardTranslator;
|
||||
import com.limelight.binding.video.ConfigurableDecoderRenderer;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.NvConnectionListener;
|
||||
import com.limelight.nvstream.StreamConfiguration;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
import com.limelight.nvstream.input.KeyboardPacket;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
@@ -14,9 +16,11 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Point;
|
||||
import android.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
@@ -31,19 +35,20 @@ import android.widget.Toast;
|
||||
|
||||
|
||||
public class Game extends Activity implements OnGenericMotionListener, OnTouchListener, NvConnectionListener {
|
||||
private short inputMap = 0x0000;
|
||||
private byte leftTrigger = 0x00;
|
||||
private byte rightTrigger = 0x00;
|
||||
private short rightStickX = 0x0000;
|
||||
private short rightStickY = 0x0000;
|
||||
private short leftStickX = 0x0000;
|
||||
private short leftStickY = 0x0000;
|
||||
private int lastMouseX = Integer.MIN_VALUE;
|
||||
private int lastMouseY = Integer.MIN_VALUE;
|
||||
private int lastButtonState = 0;
|
||||
private int lastTouchX = 0;
|
||||
private int lastTouchY = 0;
|
||||
private boolean hasMoved = false;
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private KeyboardTranslator keybTranslator;
|
||||
|
||||
private int height;
|
||||
private int width;
|
||||
private Point screenSize = new Point(0, 0);
|
||||
|
||||
private NvConnection conn;
|
||||
private SpinnerDialog spinner;
|
||||
private boolean displayedFailureDialog = false;
|
||||
@@ -108,18 +113,23 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
break;
|
||||
}
|
||||
|
||||
int width, height, refreshRate;
|
||||
int refreshRate;
|
||||
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
|
||||
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
|
||||
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
|
||||
sh.setFixedSize(width, height);
|
||||
|
||||
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
display.getSize(screenSize);
|
||||
|
||||
// Warn the user if they're on a metered connection
|
||||
checkDataConnection();
|
||||
|
||||
// Start the connection
|
||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
|
||||
new StreamConfiguration(width, height, refreshRate));
|
||||
keybTranslator = new KeyboardTranslator(conn);
|
||||
controllerHandler = new ControllerHandler(conn);
|
||||
conn.start(PlatformBinding.getDeviceName(), sv.getHolder(), drFlags,
|
||||
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
||||
}
|
||||
@@ -157,142 +167,71 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
displayedFailureDialog = true;
|
||||
conn.stop();
|
||||
finish();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
SpinnerDialog.closeDialogs();
|
||||
Dialog.closeDialogs();
|
||||
super.onDestroy();
|
||||
|
||||
displayedFailureDialog = true;
|
||||
conn.stop();
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private static byte getModifierState(KeyEvent event) {
|
||||
byte modifier = 0;
|
||||
if (event.isShiftPressed()) {
|
||||
modifier |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
}
|
||||
if (event.isCtrlPressed()) {
|
||||
modifier |= KeyboardPacket.MODIFIER_CTRL;
|
||||
}
|
||||
if (event.isAltPressed()) {
|
||||
modifier |= KeyboardPacket.MODIFIER_ALT;
|
||||
}
|
||||
return modifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
inputMap |= ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
inputMap |= ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
inputMap |= ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap |= ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap |= ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
inputMap |= ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
inputMap |= ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
inputMap |= ControllerPacket.LB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
inputMap |= ControllerPacket.RB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
inputMap |= ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
default:
|
||||
return super.onKeyDown(keyCode, event);
|
||||
if (event.getDevice() != null &&
|
||||
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
|
||||
short translated = keybTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyDown(translated,
|
||||
getModifierState(event));
|
||||
}
|
||||
|
||||
// We detect back+start as the special button combo
|
||||
if ((inputMap & ControllerPacket.BACK_FLAG) != 0 &&
|
||||
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
||||
{
|
||||
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG);
|
||||
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
else {
|
||||
if (!controllerHandler.handleButtonDown(keyCode, event)) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
sendControllerInputPacket();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
inputMap &= ~ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap &= ~ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap &= ~ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
inputMap &= ~ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
inputMap &= ~ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
inputMap &= ~ControllerPacket.LB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
inputMap &= ~ControllerPacket.RB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
default:
|
||||
return super.onKeyUp(keyCode, event);
|
||||
if (event.getDevice() != null &&
|
||||
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
|
||||
short translated = keybTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyUp(translated,
|
||||
getModifierState(event));
|
||||
}
|
||||
else {
|
||||
if (!controllerHandler.handleButtonUp(keyCode, event)) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the two is up, the special button comes up too
|
||||
if ((inputMap & ControllerPacket.BACK_FLAG) == 0 ||
|
||||
(inputMap & ControllerPacket.PLAY_FLAG) == 0)
|
||||
{
|
||||
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
}
|
||||
|
||||
sendControllerInputPacket();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -365,17 +304,38 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
// This case is for mice
|
||||
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
|
||||
{
|
||||
switch (event.getActionMasked())
|
||||
{
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
conn.sendMouseButtonDown((byte) 0x01);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
conn.sendMouseButtonUp((byte) 0x01);
|
||||
break;
|
||||
default:
|
||||
return super.onTouchEvent(event);
|
||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
conn.sendMouseButtonDown((byte) 0x01);
|
||||
}
|
||||
else {
|
||||
conn.sendMouseButtonUp((byte) 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||
conn.sendMouseButtonDown((byte) 0x03);
|
||||
}
|
||||
else {
|
||||
conn.sendMouseButtonUp((byte) 0x03);
|
||||
}
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||
conn.sendMouseButtonDown((byte) 0x02);
|
||||
}
|
||||
else {
|
||||
conn.sendMouseButtonUp((byte) 0x02);
|
||||
}
|
||||
}
|
||||
|
||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||
|
||||
lastButtonState = event.getButtonState();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -387,92 +347,13 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
InputDevice dev = event.getDevice();
|
||||
|
||||
if (dev == null) {
|
||||
System.err.println("Unknown device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
float LS_X = event.getAxisValue(MotionEvent.AXIS_X);
|
||||
float LS_Y = event.getAxisValue(MotionEvent.AXIS_Y);
|
||||
|
||||
float RS_X, RS_Y, L2, R2;
|
||||
|
||||
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
|
||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||
{
|
||||
// Ouya controller
|
||||
L2 = event.getAxisValue(MotionEvent.AXIS_LTRIGGER);
|
||||
R2 = event.getAxisValue(MotionEvent.AXIS_RTRIGGER);
|
||||
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
|
||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
|
||||
if (brakeRange != null && gasRange != null)
|
||||
{
|
||||
// Moga controller
|
||||
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
|
||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
|
||||
L2 = event.getAxisValue(MotionEvent.AXIS_BRAKE);
|
||||
R2 = event.getAxisValue(MotionEvent.AXIS_GAS);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Xbox controller
|
||||
RS_X = event.getAxisValue(MotionEvent.AXIS_RX);
|
||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RY);
|
||||
L2 = (event.getAxisValue(MotionEvent.AXIS_Z) + 1) / 2;
|
||||
R2 = (event.getAxisValue(MotionEvent.AXIS_RZ) + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
|
||||
if (hatXRange != null && hatYRange != null)
|
||||
{
|
||||
// Xbox controller D-pad
|
||||
float hatX, hatY;
|
||||
|
||||
hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
||||
hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
||||
|
||||
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
|
||||
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
|
||||
if (hatX < -0.5) {
|
||||
inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
}
|
||||
if (hatX > 0.5) {
|
||||
inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
}
|
||||
if (hatY < -0.5) {
|
||||
inputMap |= ControllerPacket.UP_FLAG;
|
||||
}
|
||||
if (hatY > 0.5) {
|
||||
inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
leftStickX = (short)Math.round(LS_X * 0x7FFF);
|
||||
leftStickY = (short)Math.round(-LS_Y * 0x7FFF);
|
||||
|
||||
rightStickX = (short)Math.round(RS_X * 0x7FFF);
|
||||
rightStickY = (short)Math.round(-RS_Y * 0x7FFF);
|
||||
|
||||
leftTrigger = (byte)Math.round(L2 * 0xFF);
|
||||
rightTrigger = (byte)Math.round(R2 * 0xFF);
|
||||
|
||||
sendControllerInputPacket();
|
||||
return true;
|
||||
if (controllerHandler.handleMotionEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||
{
|
||||
@@ -491,19 +372,21 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
lastMouseY != Integer.MIN_VALUE &&
|
||||
!(lastMouseX == eventX && lastMouseY == eventY))
|
||||
{
|
||||
conn.sendMouseMove((short)(eventX - lastMouseX),
|
||||
(short)(eventY - lastMouseY));
|
||||
int deltaX = eventX - lastMouseX;
|
||||
int deltaY = eventY - lastMouseY;
|
||||
|
||||
// Scale the deltas if the device resolution is different
|
||||
// than the stream resolution
|
||||
deltaX = (int)Math.round((double)deltaX * ((double)width / (double)screenSize.x));
|
||||
deltaY = (int)Math.round((double)deltaY * ((double)height / (double)screenSize.y));
|
||||
|
||||
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
||||
}
|
||||
|
||||
// Update pointer location for delta calculation next time
|
||||
lastMouseX = eventX;
|
||||
lastMouseY = eventY;
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket() {
|
||||
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
|
||||
leftStickX, leftStickY, rightStickX, rightStickY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
|
||||
public class ControllerHandler {
|
||||
private short inputMap = 0x0000;
|
||||
private byte leftTrigger = 0x00;
|
||||
private byte rightTrigger = 0x00;
|
||||
private short rightStickX = 0x0000;
|
||||
private short rightStickY = 0x0000;
|
||||
private short leftStickX = 0x0000;
|
||||
private short leftStickY = 0x0000;
|
||||
|
||||
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
|
||||
|
||||
private NvConnection conn;
|
||||
|
||||
public ControllerHandler(NvConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
private ControllerMapping createMappingForDevice(InputDevice dev) {
|
||||
ControllerMapping mapping = new ControllerMapping();
|
||||
|
||||
mapping.leftStickXAxis = MotionEvent.AXIS_X;
|
||||
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||
|
||||
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
|
||||
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
|
||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||
{
|
||||
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
|
||||
mapping.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
|
||||
mapping.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
|
||||
}
|
||||
else if (brakeRange != null && gasRange != null)
|
||||
{
|
||||
// Others use GAS and BRAKE (like Moga)
|
||||
mapping.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||
mapping.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||
if (rxRange != null && ryRange != null) {
|
||||
String devName = dev.getName();
|
||||
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
|
||||
// Xbox controllers use RX and RY for right stick
|
||||
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
|
||||
// Xbox controllers use Z and RZ for triggers
|
||||
mapping.leftTriggerAxis = MotionEvent.AXIS_Z;
|
||||
mapping.rightTriggerAxis = MotionEvent.AXIS_RZ;
|
||||
mapping.triggersIdleNegative = true;
|
||||
}
|
||||
else {
|
||||
// DS4 controller uses RX and RY for triggers
|
||||
mapping.leftTriggerAxis = MotionEvent.AXIS_RX;
|
||||
mapping.rightTriggerAxis = MotionEvent.AXIS_RY;
|
||||
mapping.triggersIdleNegative = true;
|
||||
|
||||
mapping.isDualShock4 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.rightStickXAxis == -1 && mapping.rightStickYAxis == -1) {
|
||||
InputDevice.MotionRange zRange = dev.getMotionRange(MotionEvent.AXIS_Z);
|
||||
InputDevice.MotionRange rzRange = dev.getMotionRange(MotionEvent.AXIS_RZ);
|
||||
|
||||
// Most other controllers use Z and RZ for the right stick
|
||||
if (zRange != null && rzRange != null) {
|
||||
mapping.rightStickXAxis = MotionEvent.AXIS_Z;
|
||||
mapping.rightStickYAxis = MotionEvent.AXIS_RZ;
|
||||
}
|
||||
else {
|
||||
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||
|
||||
// Try RX and RY now
|
||||
if (rxRange != null && ryRange != null) {
|
||||
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some devices have "hats" for d-pads
|
||||
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
|
||||
if (hatXRange != null && hatYRange != null) {
|
||||
mapping.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||
mapping.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||
|
||||
mapping.hatXDeadzone = hatXRange.getFlat();
|
||||
mapping.hatYDeadzone = hatYRange.getFlat();
|
||||
}
|
||||
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
InputDevice.MotionRange lsXRange = dev.getMotionRange(mapping.leftStickXAxis);
|
||||
InputDevice.MotionRange lsYRange = dev.getMotionRange(mapping.leftStickYAxis);
|
||||
if (lsXRange != null) {
|
||||
mapping.leftStickXAxisDeadzone = lsXRange.getFlat();
|
||||
}
|
||||
if (lsYRange != null) {
|
||||
mapping.leftStickYAxisDeadzone = lsYRange.getFlat();
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||
InputDevice.MotionRange rsXRange = dev.getMotionRange(mapping.rightStickXAxis);
|
||||
InputDevice.MotionRange rsYRange = dev.getMotionRange(mapping.rightStickYAxis);
|
||||
if (rsXRange != null) {
|
||||
mapping.rightStickXAxisDeadzone = rsXRange.getFlat();
|
||||
}
|
||||
if (rsYRange != null) {
|
||||
mapping.rightStickYAxisDeadzone = rsYRange.getFlat();
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private ControllerMapping getMappingForDevice(InputDevice dev) {
|
||||
// Unknown devices can't be handled
|
||||
if (dev == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String descriptor = dev.getDescriptor();
|
||||
|
||||
// Return the existing mapping if it exists
|
||||
ControllerMapping mapping = mappings.get(descriptor);
|
||||
if (mapping != null) {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping
|
||||
mapping = createMappingForDevice(dev);
|
||||
mappings.put(descriptor, mapping);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket() {
|
||||
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
|
||||
leftStickX, leftStickY, rightStickX, rightStickY);
|
||||
}
|
||||
|
||||
private int handleRemapping(ControllerMapping mapping, int keyCode) {
|
||||
if (mapping.isDualShock4) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
|
||||
// These are duplicate trigger events
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||
switch (keyCode) {
|
||||
// These are duplicate dpad events for hat input
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||
if (mapping == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle left stick events outside of the deadzone
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
float LS_X = event.getAxisValue(mapping.leftStickXAxis);
|
||||
float LS_Y = event.getAxisValue(mapping.leftStickYAxis);
|
||||
if (LS_X >= -mapping.leftStickXAxisDeadzone && LS_X <= mapping.leftStickXAxisDeadzone) {
|
||||
LS_X = 0;
|
||||
}
|
||||
if (LS_Y >= -mapping.leftStickYAxisDeadzone && LS_Y <= mapping.leftStickYAxisDeadzone) {
|
||||
LS_Y = 0;
|
||||
}
|
||||
leftStickX = (short)Math.round(LS_X * 0x7FFF);
|
||||
leftStickY = (short)Math.round(-LS_Y * 0x7FFF);
|
||||
}
|
||||
|
||||
// Handle right stick events outside of the deadzone
|
||||
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||
float RS_X = event.getAxisValue(mapping.rightStickXAxis);
|
||||
float RS_Y = event.getAxisValue(mapping.rightStickYAxis);
|
||||
if (RS_X >= -mapping.rightStickXAxisDeadzone && RS_X <= mapping.rightStickXAxisDeadzone) {
|
||||
RS_X = 0;
|
||||
}
|
||||
if (RS_Y >= -mapping.rightStickYAxisDeadzone && RS_Y <= mapping.rightStickYAxisDeadzone) {
|
||||
RS_Y = 0;
|
||||
}
|
||||
rightStickX = (short)Math.round(RS_X * 0x7FFF);
|
||||
rightStickY = (short)Math.round(-RS_Y * 0x7FFF);
|
||||
}
|
||||
|
||||
// Handle controllers with analog triggers
|
||||
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
|
||||
float L2 = event.getAxisValue(mapping.leftTriggerAxis);
|
||||
float R2 = event.getAxisValue(mapping.rightTriggerAxis);
|
||||
|
||||
if (mapping.triggersIdleNegative) {
|
||||
L2 = (L2 + 1) / 2;
|
||||
R2 = (R2 + 1) / 2;
|
||||
}
|
||||
|
||||
leftTrigger = (byte)Math.round(L2 * 0xFF);
|
||||
rightTrigger = (byte)Math.round(R2 * 0xFF);
|
||||
}
|
||||
|
||||
// Hats emulate d-pad events
|
||||
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||
float hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
||||
float hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
||||
|
||||
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
|
||||
if (hatX < -(0.5 + mapping.hatXDeadzone)) {
|
||||
inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
}
|
||||
else if (hatX > (0.5 + mapping.hatXDeadzone)) {
|
||||
inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
}
|
||||
|
||||
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
|
||||
if (hatY < -(0.5 + mapping.hatYDeadzone)) {
|
||||
inputMap |= ControllerPacket.UP_FLAG;
|
||||
}
|
||||
else if (hatY > (0.5 + mapping.hatYDeadzone)) {
|
||||
inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
sendControllerInputPacket();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(int keyCode, KeyEvent event) {
|
||||
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||
if (mapping == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyCode = handleRemapping(mapping, keyCode);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
inputMap &= ~ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap &= ~ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap &= ~ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
inputMap &= ~ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
inputMap &= ~ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
inputMap &= ~ControllerPacket.LB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
inputMap &= ~ControllerPacket.RB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
leftTrigger = 0;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
rightTrigger = 0;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// If one of the two is up, the special button comes up too
|
||||
if ((inputMap & ControllerPacket.BACK_FLAG) == 0 ||
|
||||
(inputMap & ControllerPacket.PLAY_FLAG) == 0)
|
||||
{
|
||||
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
}
|
||||
|
||||
sendControllerInputPacket();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonDown(int keyCode, KeyEvent event) {
|
||||
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||
if (mapping == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyCode = handleRemapping(mapping, keyCode);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
inputMap |= ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
inputMap |= ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
inputMap |= ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap |= ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap |= ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
inputMap |= ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
inputMap |= ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
inputMap |= ControllerPacket.LB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
inputMap |= ControllerPacket.RB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
inputMap |= ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
leftTrigger = (byte)0xFF;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
rightTrigger = (byte)0xFF;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// We detect back+start as the special button combo
|
||||
if ((inputMap & ControllerPacket.BACK_FLAG) != 0 &&
|
||||
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
||||
{
|
||||
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG);
|
||||
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
}
|
||||
|
||||
sendControllerInputPacket();
|
||||
return true;
|
||||
}
|
||||
|
||||
class ControllerMapping {
|
||||
public int leftStickXAxis = -1;
|
||||
public float leftStickXAxisDeadzone;
|
||||
|
||||
public int leftStickYAxis = -1;
|
||||
public float leftStickYAxisDeadzone;
|
||||
|
||||
public int rightStickXAxis = -1;
|
||||
public float rightStickXAxisDeadzone;
|
||||
|
||||
public int rightStickYAxis = -1;
|
||||
public float rightStickYAxisDeadzone;
|
||||
|
||||
public int leftTriggerAxis = -1;
|
||||
public int rightTriggerAxis = -1;
|
||||
public boolean triggersIdleNegative;
|
||||
|
||||
public int hatXAxis = -1;
|
||||
public int hatYAxis = -1;
|
||||
public float hatXDeadzone;
|
||||
public float hatYDeadzone;
|
||||
|
||||
public boolean isDualShock4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.KeycodeTranslator;
|
||||
|
||||
/**
|
||||
* Class to translate a Android key code into the codes GFE is expecting
|
||||
* @author Diego Waxemberg
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class KeyboardTranslator extends KeycodeTranslator {
|
||||
|
||||
/**
|
||||
* GFE's prefix for every key code
|
||||
*/
|
||||
public static final short KEY_PREFIX = (short) 0x80;
|
||||
|
||||
public static final int VK_0 = 48;
|
||||
public static final int VK_9 = 57;
|
||||
public static final int VK_A = 65;
|
||||
public static final int VK_Z = 90;
|
||||
public static final int VK_ALT = 18;
|
||||
public static final int VK_NUMPAD0 = 96;
|
||||
public static final int VK_BACK_SLASH = 92;
|
||||
public static final int VK_CAPS_LOCK = 20;
|
||||
public static final int VK_CLEAR = 12;
|
||||
public static final int VK_COMMA = 44;
|
||||
public static final int VK_CONTROL = 17;
|
||||
public static final int VK_BACK_SPACE = 8;
|
||||
public static final int VK_EQUALS = 61;
|
||||
public static final int VK_ESCAPE = 27;
|
||||
public static final int VK_F1 = 112;
|
||||
public static final int VK_PERIOD = 46;
|
||||
public static final int VK_INSERT = 155;
|
||||
public static final int VK_OPEN_BRACKET = 91;
|
||||
public static final int VK_WINDOWS = 524;
|
||||
public static final int VK_MINUS = 45;
|
||||
public static final int VK_END = 35;
|
||||
public static final int VK_HOME = 36;
|
||||
public static final int VK_NUM_LOCK = 144;
|
||||
public static final int VK_PAGE_UP = 33;
|
||||
public static final int VK_PAGE_DOWN = 34;
|
||||
public static final int VK_PLUS = 521;
|
||||
public static final int VK_CLOSE_BRACKET = 93;
|
||||
public static final int VK_SCROLL_LOCK = 145;
|
||||
public static final int VK_SEMICOLON = 59;
|
||||
public static final int VK_SHIFT = 16;
|
||||
public static final int VK_SLASH = 47;
|
||||
public static final int VK_SPACE = 32;
|
||||
public static final int VK_PRINTSCREEN = 154;
|
||||
public static final int VK_TAB = 9;
|
||||
public static final int VK_LEFT = 37;
|
||||
public static final int VK_RIGHT = 39;
|
||||
public static final int VK_UP = 38;
|
||||
public static final int VK_DOWN = 40;
|
||||
public static final int VK_BACK_QUOTE = 192;
|
||||
public static final int VK_QUOTE = 222;
|
||||
public static final int VK_PAUSE = 19;
|
||||
|
||||
/**
|
||||
* Constructs a new translator for the specified connection
|
||||
* @param conn the connection to which the translated codes are sent
|
||||
*/
|
||||
public KeyboardTranslator(NvConnection conn) {
|
||||
super(conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
* @return a GFE keycode for the given keycode
|
||||
*/
|
||||
@Override
|
||||
public short translate(int keycode) {
|
||||
int translated;
|
||||
|
||||
/* There seems to be no clean mapping between Android key codes
|
||||
* and what Nvidia sends over the wire. If someone finds one,
|
||||
* I'll happily delete this code :)
|
||||
*/
|
||||
if (keycode >= KeyEvent.KEYCODE_0 &&
|
||||
keycode <= KeyEvent.KEYCODE_9) {
|
||||
translated = (keycode - KeyEvent.KEYCODE_0) + VK_0;
|
||||
}
|
||||
else if (keycode >= KeyEvent.KEYCODE_A &&
|
||||
keycode <= KeyEvent.KEYCODE_Z) {
|
||||
translated = (keycode - KeyEvent.KEYCODE_A) + VK_A;
|
||||
}
|
||||
else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 &&
|
||||
keycode <= KeyEvent.KEYCODE_NUMPAD_9) {
|
||||
translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0;
|
||||
}
|
||||
else if (keycode >= KeyEvent.KEYCODE_F1 &&
|
||||
keycode <= KeyEvent.KEYCODE_F12) {
|
||||
translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1;
|
||||
}
|
||||
else {
|
||||
switch (keycode) {
|
||||
case KeyEvent.KEYCODE_ALT_LEFT:
|
||||
case KeyEvent.KEYCODE_ALT_RIGHT:
|
||||
translated = VK_ALT;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_BACKSLASH:
|
||||
translated = 0xdc;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_CAPS_LOCK:
|
||||
translated = VK_CAPS_LOCK;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_CLEAR:
|
||||
translated = VK_CLEAR;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_COMMA:
|
||||
translated = 0xbc;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_CTRL_LEFT:
|
||||
case KeyEvent.KEYCODE_CTRL_RIGHT:
|
||||
translated = VK_CONTROL;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DEL:
|
||||
translated = VK_BACK_SPACE;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
translated = 0x0d;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_EQUALS:
|
||||
translated = 0xbb;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_ESCAPE:
|
||||
translated = VK_ESCAPE;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_FORWARD_DEL:
|
||||
// Nvidia maps period to delete
|
||||
translated = VK_PERIOD;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_INSERT:
|
||||
translated = -1;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_LEFT_BRACKET:
|
||||
translated = 0xdb;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_META_LEFT:
|
||||
case KeyEvent.KEYCODE_META_RIGHT:
|
||||
translated = VK_WINDOWS;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_MINUS:
|
||||
translated = 0xbd;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_MOVE_END:
|
||||
translated = VK_END;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_MOVE_HOME:
|
||||
translated = VK_HOME;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_NUM_LOCK:
|
||||
translated = VK_NUM_LOCK;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_PAGE_DOWN:
|
||||
translated = VK_PAGE_DOWN;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_PAGE_UP:
|
||||
translated = VK_PAGE_UP;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_PERIOD:
|
||||
translated = 0xbe;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_RIGHT_BRACKET:
|
||||
translated = 0xdd;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SCROLL_LOCK:
|
||||
translated = VK_SCROLL_LOCK;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SEMICOLON:
|
||||
translated = 0xba;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SHIFT_LEFT:
|
||||
case KeyEvent.KEYCODE_SHIFT_RIGHT:
|
||||
translated = VK_SHIFT;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SLASH:
|
||||
translated = 0xbf;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
translated = VK_SPACE;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_SYSRQ:
|
||||
// Android defines this as SysRq/PrntScrn
|
||||
translated = VK_PRINTSCREEN;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_TAB:
|
||||
translated = VK_TAB;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
translated = VK_LEFT;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
translated = VK_RIGHT;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
translated = VK_UP;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
translated = VK_DOWN;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_GRAVE:
|
||||
translated = VK_BACK_QUOTE;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_APOSTROPHE:
|
||||
translated = 0xde;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_BREAK:
|
||||
translated = VK_PAUSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("No key for "+keycode);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (short) ((KEY_PREFIX << 8) | translated);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import android.graphics.PixelFormat;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
@@ -124,12 +125,15 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
LimeLog.info("Using high quality decoding");
|
||||
}
|
||||
|
||||
SurfaceHolder sh = (SurfaceHolder)renderTarget;
|
||||
sh.setFormat(PixelFormat.RGBX_8888);
|
||||
|
||||
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
|
||||
if (err != 0) {
|
||||
throw new IllegalStateException("AVC decoder initialization failure: "+err);
|
||||
}
|
||||
|
||||
AvcDecoder.setRenderTarget(((SurfaceHolder)renderTarget).getSurface());
|
||||
AvcDecoder.setRenderTarget(sh.getSurface());
|
||||
|
||||
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
@@ -31,9 +33,9 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
|
||||
// TI's decoder technically supports high profile but doesn't work for some reason
|
||||
blacklistedDecoderPrefixes.add("omx.TI");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -58,38 +60,58 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findSafeDecoder() {
|
||||
|
||||
|
||||
public static void dumpDecoders() {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
boolean badCodec = false;
|
||||
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String badPrefix : blacklistedDecoderPrefixes) {
|
||||
String name = codecInfo.getName();
|
||||
if (name.length() >= badPrefix.length()) {
|
||||
String prefix = name.substring(0, badPrefix.length());
|
||||
if (prefix.equalsIgnoreCase(badPrefix)) {
|
||||
badCodec = true;
|
||||
break;
|
||||
}
|
||||
LimeLog.info("Decoder: "+codecInfo.getName());
|
||||
for (String type : codecInfo.getSupportedTypes()) {
|
||||
LimeLog.info("\t"+type);
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
|
||||
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
LimeLog.info("\t\t"+profile.profile+" "+profile.level);
|
||||
}
|
||||
}
|
||||
|
||||
if (badCodec) {
|
||||
LimeLog.info("Blacklisted decoder: "+codecInfo.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findSafeDecoder() {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for explicitly blacklisted decoders
|
||||
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
|
||||
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a decoder that supports H.264 high profile
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
||||
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
if (profile.profile == CodecProfileLevel.AVCProfileHigh) {
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" supports high profile");
|
||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" does NOT support high profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,6 +123,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
this.redrawRate = redrawRate;
|
||||
|
||||
//dumpDecoders();
|
||||
|
||||
MediaCodecInfo safeDecoder = findSafeDecoder();
|
||||
if (safeDecoder != null) {
|
||||
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
|
||||
|
||||
Reference in New Issue
Block a user