mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +00:00
462 lines
13 KiB
Java
462 lines
13 KiB
Java
package com.limelight;
|
|
|
|
import com.limelight.nvstream.NvConnection;
|
|
import com.limelight.nvstream.input.NvControllerPacket;
|
|
|
|
import android.app.Activity;
|
|
import android.content.ComponentCallbacks2;
|
|
import android.os.Bundle;
|
|
import android.view.InputDevice;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.SurfaceView;
|
|
import android.view.View;
|
|
import android.view.View.OnGenericMotionListener;
|
|
import android.view.View.OnTouchListener;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
|
|
|
|
public class Game extends Activity implements OnGenericMotionListener, OnTouchListener {
|
|
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 lastTouchX = 0;
|
|
private int lastTouchY = 0;
|
|
private boolean hasMoved = false;
|
|
|
|
private NvConnection conn;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
// Full-screen and don't let the display go off
|
|
getWindow().setFlags(
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
|
|
// We don't want a title bar
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
|
|
// Inflate the content
|
|
setContentView(R.layout.activity_game);
|
|
|
|
// Listen for events on the game surface
|
|
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
|
|
sv.setOnGenericMotionListener(this);
|
|
sv.setOnTouchListener(this);
|
|
|
|
// Start the connection
|
|
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface());
|
|
conn.start();
|
|
}
|
|
|
|
public void hideSystemUi() {
|
|
runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Use immersive mode on 4.4+ or standard low profile on previous builds
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
|
Game.this.getWindow().getDecorView().setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
|
}
|
|
else {
|
|
Game.this.getWindow().getDecorView().setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
|
View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
finish();
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
conn.stop();
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
public void onTrimMemory(int trimLevel) {
|
|
if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW)
|
|
{
|
|
conn.trim();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
|
|
// Skip keyboard and virtual button events
|
|
if (event.getSource() == InputDevice.SOURCE_KEYBOARD)
|
|
return super.onKeyDown(keyCode, event);
|
|
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_BUTTON_START:
|
|
case KeyEvent.KEYCODE_MENU:
|
|
inputMap |= NvControllerPacket.PLAY_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BACK:
|
|
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
|
inputMap |= NvControllerPacket.BACK_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
inputMap |= NvControllerPacket.LEFT_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
inputMap |= NvControllerPacket.RIGHT_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_UP:
|
|
inputMap |= NvControllerPacket.UP_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
|
inputMap |= NvControllerPacket.DOWN_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_B:
|
|
inputMap |= NvControllerPacket.B_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_A:
|
|
inputMap |= NvControllerPacket.A_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_X:
|
|
inputMap |= NvControllerPacket.X_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_Y:
|
|
inputMap |= NvControllerPacket.Y_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_L1:
|
|
inputMap |= NvControllerPacket.LB_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_R1:
|
|
inputMap |= NvControllerPacket.RB_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
|
inputMap |= NvControllerPacket.LS_CLK_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
|
inputMap |= NvControllerPacket.RS_CLK_FLAG;
|
|
break;
|
|
default:
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
// We detect back+start as the special button combo
|
|
if ((inputMap & NvControllerPacket.BACK_FLAG) != 0 &&
|
|
(inputMap & NvControllerPacket.PLAY_FLAG) != 0)
|
|
{
|
|
inputMap &= ~(NvControllerPacket.BACK_FLAG | NvControllerPacket.PLAY_FLAG);
|
|
inputMap |= NvControllerPacket.SPECIAL_BUTTON_FLAG;
|
|
}
|
|
|
|
sendControllerInputPacket();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
|
|
// Skip keyboard and virtual button events
|
|
if (event.getSource() == InputDevice.SOURCE_KEYBOARD)
|
|
return super.onKeyUp(keyCode, event);
|
|
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_BUTTON_START:
|
|
case KeyEvent.KEYCODE_MENU:
|
|
inputMap &= ~NvControllerPacket.PLAY_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BACK:
|
|
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
|
inputMap &= ~NvControllerPacket.BACK_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
inputMap &= ~NvControllerPacket.LEFT_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
inputMap &= ~NvControllerPacket.RIGHT_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_UP:
|
|
inputMap &= ~NvControllerPacket.UP_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
|
inputMap &= ~NvControllerPacket.DOWN_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_B:
|
|
inputMap &= ~NvControllerPacket.B_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_A:
|
|
inputMap &= ~NvControllerPacket.A_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_X:
|
|
inputMap &= ~NvControllerPacket.X_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_Y:
|
|
inputMap &= ~NvControllerPacket.Y_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_L1:
|
|
inputMap &= ~NvControllerPacket.LB_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_R1:
|
|
inputMap &= ~NvControllerPacket.RB_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
|
inputMap &= ~NvControllerPacket.LS_CLK_FLAG;
|
|
break;
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
|
inputMap &= ~NvControllerPacket.RS_CLK_FLAG;
|
|
break;
|
|
default:
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
|
|
// If one of the two is up, the special button comes up too
|
|
if ((inputMap & NvControllerPacket.BACK_FLAG) == 0 ||
|
|
(inputMap & NvControllerPacket.PLAY_FLAG) == 0)
|
|
{
|
|
inputMap &= ~NvControllerPacket.SPECIAL_BUTTON_FLAG;
|
|
}
|
|
|
|
sendControllerInputPacket();
|
|
return true;
|
|
}
|
|
|
|
public void touchDownEvent(int eventX, int eventY)
|
|
{
|
|
lastTouchX = eventX;
|
|
lastTouchY = eventY;
|
|
hasMoved = false;
|
|
}
|
|
|
|
public void touchUpEvent(int eventX, int eventY)
|
|
{
|
|
if (!hasMoved)
|
|
{
|
|
// We haven't moved so send a click
|
|
|
|
// Lower the mouse button
|
|
conn.sendMouseButtonDown();
|
|
|
|
// We need to sleep a bit here because some games
|
|
// do input detection by polling
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (InterruptedException e) {}
|
|
|
|
// Raise the mouse button
|
|
conn.sendMouseButtonUp();
|
|
}
|
|
}
|
|
|
|
public void touchMoveEvent(int eventX, int eventY)
|
|
{
|
|
if (eventX != lastTouchX || eventY != lastTouchY)
|
|
{
|
|
hasMoved = true;
|
|
conn.sendMouseMove((short)(eventX - lastTouchX),
|
|
(short)(eventY - lastTouchY));
|
|
|
|
lastTouchX = eventX;
|
|
lastTouchY = eventY;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
|
{
|
|
// This case is for touch-based input devices
|
|
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN ||
|
|
event.getSource() == InputDevice.SOURCE_STYLUS)
|
|
{
|
|
int eventX = (int)event.getX();
|
|
int eventY = (int)event.getY();
|
|
|
|
switch (event.getActionMasked())
|
|
{
|
|
case MotionEvent.ACTION_DOWN:
|
|
touchDownEvent(eventX, eventY);
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
touchUpEvent(eventX, eventY);
|
|
break;
|
|
case MotionEvent.ACTION_MOVE:
|
|
touchMoveEvent(eventX, eventY);
|
|
break;
|
|
default:
|
|
return super.onTouchEvent(event);
|
|
}
|
|
}
|
|
// This case is for mice
|
|
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
|
|
{
|
|
switch (event.getActionMasked())
|
|
{
|
|
case MotionEvent.ACTION_DOWN:
|
|
conn.sendMouseButtonDown();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
conn.sendMouseButtonUp();
|
|
break;
|
|
default:
|
|
return super.onTouchEvent(event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return super.onTouchEvent(event);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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 &= ~(NvControllerPacket.LEFT_FLAG | NvControllerPacket.RIGHT_FLAG);
|
|
inputMap &= ~(NvControllerPacket.UP_FLAG | NvControllerPacket.DOWN_FLAG);
|
|
if (hatX < -0.5) {
|
|
inputMap |= NvControllerPacket.LEFT_FLAG;
|
|
}
|
|
if (hatX > 0.5) {
|
|
inputMap |= NvControllerPacket.RIGHT_FLAG;
|
|
}
|
|
if (hatY < -0.5) {
|
|
inputMap |= NvControllerPacket.UP_FLAG;
|
|
}
|
|
if (hatY > 0.5) {
|
|
inputMap |= NvControllerPacket.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;
|
|
}
|
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
|
{
|
|
// Send a mouse move update (if neccessary)
|
|
updateMousePosition((int)event.getX(), (int)event.getY());
|
|
return true;
|
|
}
|
|
|
|
return super.onGenericMotionEvent(event);
|
|
}
|
|
|
|
private void updateMousePosition(int eventX, int eventY) {
|
|
// Send a mouse move if we already have a mouse location
|
|
// and the mouse coordinates change
|
|
if (lastMouseX != Integer.MIN_VALUE &&
|
|
lastMouseY != Integer.MIN_VALUE &&
|
|
!(lastMouseX == eventX && lastMouseY == eventY))
|
|
{
|
|
conn.sendMouseMove((short)(eventX - lastMouseX),
|
|
(short)(eventY - lastMouseY));
|
|
}
|
|
|
|
// 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) {
|
|
// Send it to the activity's motion event handler
|
|
return onGenericMotionEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
// Send it to the activity's touch event handler
|
|
return onTouchEvent(event);
|
|
}
|
|
}
|