Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 042a6b943e | |||
| e114b73654 | |||
| da0a505978 | |||
| cb6d4a385c | |||
| 2806aee0fc | |||
| 52736f5162 | |||
| 6d45ad7fe8 | |||
| 2fc53644bc | |||
| b33eaec493 | |||
| 63d6f3ac78 | |||
| fd4caac013 | |||
| ada875cdb0 | |||
| 49ddfa573d | |||
| b58ac367ee | |||
| cf62b4ed95 | |||
| b05c62e141 | |||
| 095556106c | |||
| 5cdd72a45c | |||
| 5d84f8af43 | |||
| d9483d9214 | |||
| 250475830f | |||
| b8a0a823e0 | |||
| 6a54d669a3 | |||
| 62559c4e66 | |||
| e04ecaaf7a | |||
| fa4706c95f | |||
| 7067c0e02e | |||
| cc71ce6180 | |||
| f409a3583c | |||
| ac7504e017 | |||
| 345bd3f7c1 | |||
| 2e2960ec69 | |||
| e93b103d1e | |||
| 22977a4c5b | |||
| 7da5d5322b | |||
| 49e2c40ba4 | |||
| 8041a004c2 | |||
| db62d78e04 | |||
| bd79318b1e | |||
| 2736bd9165 | |||
| b6bd48584f | |||
| 7b4f3c975a | |||
| b165fadc55 | |||
| 274e0d0557 | |||
| 7594e51a18 | |||
| bf22819b53 | |||
| 3dea4b15e0 | |||
| 5836b3292b | |||
| a8fd49a234 | |||
| 006ad72eb2 | |||
| dc254e1ee5 | |||
| b0d31a4d35 | |||
| 24155feea4 | |||
| db0a4e35c6 | |||
| 68ef98d346 | |||
| f23bb9fac1 | |||
| d20dde0b6d | |||
| f76b30d109 | |||
| ee1a047cde | |||
| 4c533fedfd | |||
| f8ab7b8e13 | |||
| 46c5eaf0e1 | |||
| 1d6b5a35bd | |||
| 1ff6ee14ac | |||
| d2e51e97c0 | |||
| 9f94465979 | |||
| d83526ff5c | |||
| 1d6b7e1b2e | |||
| 1c9458d056 | |||
| 4e29f2ae8b | |||
| 69321636b5 | |||
| d190b254bd | |||
| 005a96f3d3 | |||
| e39e0910a1 | |||
| 56a6cee8f2 |
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
|
|
||||||
versionName "4.0.2"
|
versionName "4.5"
|
||||||
versionCode = 79
|
versionCode = 84
|
||||||
}
|
}
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
|||||||
Binary file not shown.
@@ -76,6 +76,7 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.AppView" />
|
android:value="com.limelight.AppView" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".discovery.DiscoveryService"
|
android:name=".discovery.DiscoveryService"
|
||||||
android:label="mDNS PC Auto-Discovery Service" />
|
android:label="mDNS PC Auto-Discovery Service" />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.limelight.binding.input.TouchContext;
|
|||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.evdev.EvdevHandler;
|
import com.limelight.binding.input.evdev.EvdevHandler;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
|
import com.limelight.binding.input.virtual_controller.VirtualController;
|
||||||
import com.limelight.binding.video.EnhancedDecoderRenderer;
|
import com.limelight.binding.video.EnhancedDecoderRenderer;
|
||||||
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
||||||
import com.limelight.binding.video.MediaCodecHelper;
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
@@ -53,6 +54,7 @@ import android.view.View.OnTouchListener;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -77,6 +79,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||||
|
|
||||||
private ControllerHandler controllerHandler;
|
private ControllerHandler controllerHandler;
|
||||||
|
private VirtualController virtualController;
|
||||||
private KeyboardTranslator keybTranslator;
|
private KeyboardTranslator keybTranslator;
|
||||||
|
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
@@ -284,6 +287,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
evdevHandler.start();
|
evdevHandler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefConfig.onscreenController) {
|
||||||
|
// create virtual onscreen controller
|
||||||
|
virtualController = new VirtualController(conn,
|
||||||
|
(FrameLayout)findViewById(R.id.surfaceView).getParent(),
|
||||||
|
this);
|
||||||
|
virtualController.refreshLayout();
|
||||||
|
}
|
||||||
|
|
||||||
if (prefConfig.usbDriver) {
|
if (prefConfig.usbDriver) {
|
||||||
// Start the USB driver
|
// Start the USB driver
|
||||||
bindService(new Intent(this, UsbDriverService.class),
|
bindService(new Intent(this, UsbDriverService.class),
|
||||||
@@ -307,6 +318,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Apply the size change
|
// Apply the size change
|
||||||
sv.setLayoutParams(lp);
|
sv.setLayoutParams(lp);
|
||||||
|
|
||||||
|
// refresh virtual controller layout
|
||||||
|
if (virtualController != null) {
|
||||||
|
virtualController.refreshLayout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDataConnection()
|
private void checkDataConnection()
|
||||||
@@ -354,8 +370,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
SpinnerDialog.closeDialogs(this);
|
SpinnerDialog.closeDialogs(this);
|
||||||
Dialog.closeDialogs();
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
if (controllerHandler != null) {
|
||||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
|
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||||
|
}
|
||||||
|
|
||||||
wifiLock.release();
|
wifiLock.release();
|
||||||
|
|
||||||
@@ -364,36 +382,38 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
unbindService(usbDriverServiceConnection);
|
unbindService(usbDriverServiceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoDecoderRenderer.VideoFormat videoFormat = conn.getActiveVideoFormat();
|
if (conn != null) {
|
||||||
|
VideoDecoderRenderer.VideoFormat videoFormat = conn.getActiveVideoFormat();
|
||||||
|
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
stopConnection();
|
||||||
|
|
||||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
||||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
||||||
String message = null;
|
String message = null;
|
||||||
if (averageEndToEndLat > 0) {
|
if (averageEndToEndLat > 0) {
|
||||||
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
|
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
|
||||||
if (averageDecoderLat > 0) {
|
if (averageDecoderLat > 0) {
|
||||||
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
|
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (averageDecoderLat > 0) {
|
||||||
else if (averageDecoderLat > 0) {
|
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
||||||
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the video codec to the post-stream toast
|
|
||||||
if (message != null && videoFormat != VideoDecoderRenderer.VideoFormat.Unknown) {
|
|
||||||
if (videoFormat == VideoDecoderRenderer.VideoFormat.H265) {
|
|
||||||
message += " [H.265]";
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
message += " [H.264]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message != null) {
|
// Add the video codec to the post-stream toast
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
if (message != null && videoFormat != VideoDecoderRenderer.VideoFormat.Unknown) {
|
||||||
|
if (videoFormat == VideoDecoderRenderer.VideoFormat.H265) {
|
||||||
|
message += " [H.265]";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message += " [H.264]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
@@ -646,6 +666,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// This case is for touch-based input devices
|
// This case is for touch-based input devices
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (virtualController != null &&
|
||||||
|
virtualController.getControllerMode() == VirtualController.ControllerMode.Configuration) {
|
||||||
|
// Ignore presses when the virtual controller is in configuration mode
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int actionIndex = event.getActionIndex();
|
int actionIndex = event.getActionIndex();
|
||||||
|
|
||||||
int eventX = (int)event.getX(actionIndex);
|
int eventX = (int)event.getX(actionIndex);
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
// Samsung's face buttons appear as a non-virtual button so we'll explicitly ignore
|
// Samsung's face buttons appear as a non-virtual button so we'll explicitly ignore
|
||||||
// back presses on this device
|
// back presses on this device
|
||||||
else if (devName.equals("sec_touchscreen")) {
|
else if (devName.equals("sec_touchscreen") || devName.equals("sec_touchkey")) {
|
||||||
context.ignoreBack = true;
|
context.ignoreBack = true;
|
||||||
}
|
}
|
||||||
// The Serval has a couple of unknown buttons that are start and select. It also has
|
// The Serval has a couple of unknown buttons that are start and select. It also has
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import android.hardware.usb.UsbManager;
|
|||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class UsbDriverService extends Service implements UsbDriverListener {
|
public class UsbDriverService extends Service implements UsbDriverListener {
|
||||||
@@ -110,6 +112,10 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
// Open the device
|
// Open the device
|
||||||
UsbDeviceConnection connection = usbManager.openDevice(device);
|
UsbDeviceConnection connection = usbManager.openDevice(device);
|
||||||
|
if (connection == null) {
|
||||||
|
LimeLog.warning("Unable to open USB device: "+device.getDeviceName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to initialize it
|
// Try to initialize it
|
||||||
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ public class XboxOneController {
|
|||||||
// Read the next input state packet
|
// Read the next input state packet
|
||||||
long lastMillis = MediaCodecHelper.getMonotonicMillis();
|
long lastMillis = MediaCodecHelper.getMonotonicMillis();
|
||||||
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
|
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
|
||||||
|
|
||||||
|
// If we get a zero length response, treat it as an error
|
||||||
|
if (res == 0) {
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
|
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
|
||||||
LimeLog.warning("Detected device I/O error");
|
LimeLog.warning("Detected device I/O error");
|
||||||
XboxOneController.this.stop();
|
XboxOneController.this.stop();
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ package com.limelight.binding.input.evdev;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
public class EvdevHandler {
|
public class EvdevHandler {
|
||||||
|
|
||||||
@@ -15,7 +20,9 @@ public class EvdevHandler {
|
|||||||
private boolean shutdown = false;
|
private boolean shutdown = false;
|
||||||
private InputStream evdevIn;
|
private InputStream evdevIn;
|
||||||
private OutputStream evdevOut;
|
private OutputStream evdevOut;
|
||||||
private Process reader;
|
private Process su;
|
||||||
|
private ServerSocket servSock;
|
||||||
|
private Socket evdevSock;
|
||||||
|
|
||||||
private static final byte UNGRAB_REQUEST = 1;
|
private static final byte UNGRAB_REQUEST = 1;
|
||||||
private static final byte REGRAB_REQUEST = 2;
|
private static final byte REGRAB_REQUEST = 2;
|
||||||
@@ -27,19 +34,45 @@ public class EvdevHandler {
|
|||||||
int deltaY = 0;
|
int deltaY = 0;
|
||||||
byte deltaScroll = 0;
|
byte deltaScroll = 0;
|
||||||
|
|
||||||
// Launch the evdev reader shell
|
// Bind a local listening socket for evdevreader to connect to
|
||||||
ProcessBuilder builder = new ProcessBuilder("su", "-c", libraryPath+File.separatorChar+"libevdev_reader.so");
|
|
||||||
builder.redirectErrorStream(false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
reader = builder.start();
|
servSock = new ServerSocket(0, 1);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
evdevIn = reader.getInputStream();
|
// Launch a su shell
|
||||||
evdevOut = reader.getOutputStream();
|
ProcessBuilder builder = new ProcessBuilder("su");
|
||||||
|
builder.redirectErrorStream(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
su = builder.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start evdevreader
|
||||||
|
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
||||||
|
try {
|
||||||
|
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for evdevreader's connection
|
||||||
|
LimeLog.info("Waiting for EvdevReader connection to port "+servSock.getLocalPort());
|
||||||
|
try {
|
||||||
|
evdevSock = servSock.accept();
|
||||||
|
evdevIn = evdevSock.getInputStream();
|
||||||
|
evdevOut = evdevSock.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LimeLog.info("EvdevReader connected from port "+evdevSock.getPort());
|
||||||
|
|
||||||
while (!isInterrupted() && !shutdown) {
|
while (!isInterrupted() && !shutdown) {
|
||||||
EvdevEvent event;
|
EvdevEvent event;
|
||||||
@@ -159,6 +192,22 @@ public class EvdevHandler {
|
|||||||
shutdown = true;
|
shutdown = true;
|
||||||
handlerThread.interrupt();
|
handlerThread.interrupt();
|
||||||
|
|
||||||
|
if (servSock != null) {
|
||||||
|
try {
|
||||||
|
servSock.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evdevSock != null) {
|
||||||
|
try {
|
||||||
|
evdevSock.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (evdevIn != null) {
|
if (evdevIn != null) {
|
||||||
try {
|
try {
|
||||||
evdevIn.close();
|
evdevIn.close();
|
||||||
@@ -175,8 +224,8 @@ public class EvdevHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader != null) {
|
if (su != null) {
|
||||||
reader.destroy();
|
su.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a analog stick on screen element. It is used to get 2-Axis user input.
|
||||||
|
*/
|
||||||
|
public class AnalogStick extends VirtualControllerElement {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* outer radius size in percent of the ui element
|
||||||
|
*/
|
||||||
|
public static final int SIZE_RADIUS_COMPLETE = 90;
|
||||||
|
/**
|
||||||
|
* analog stick size in percent of the ui element
|
||||||
|
*/
|
||||||
|
public static final int SIZE_RADIUS_ANALOG_STICK = 90;
|
||||||
|
/**
|
||||||
|
* dead zone size in percent of the ui element
|
||||||
|
*/
|
||||||
|
public static final int SIZE_RADIUS_DEADZONE = 90;
|
||||||
|
/**
|
||||||
|
* time frame for a double click
|
||||||
|
*/
|
||||||
|
public final static long timeoutDoubleClick = 350;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* touch down time until the deadzone is lifted to allow precise movements with the analog sticks
|
||||||
|
*/
|
||||||
|
public final static long timeoutDeadzone = 150;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface to update registered observers.
|
||||||
|
*/
|
||||||
|
public interface AnalogStickListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onMovement event will be fired on real analog stick movement (outside of the deadzone).
|
||||||
|
*
|
||||||
|
* @param x horizontal position, value from -1.0 ... 0 .. 1.0
|
||||||
|
* @param y vertical position, value from -1.0 ... 0 .. 1.0
|
||||||
|
*/
|
||||||
|
void onMovement(float x, float y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onClick event will be fired on click on the analog stick
|
||||||
|
*/
|
||||||
|
void onClick();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onDoubleClick event will be fired on a double click in a short time frame on the analog
|
||||||
|
* stick.
|
||||||
|
*/
|
||||||
|
void onDoubleClick();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onRevoke event will be fired on unpress of the analog stick.
|
||||||
|
*/
|
||||||
|
void onRevoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Movement states of the analog sick.
|
||||||
|
*/
|
||||||
|
private enum STICK_STATE {
|
||||||
|
NO_MOVEMENT,
|
||||||
|
MOVED_IN_DEAD_ZONE,
|
||||||
|
MOVED_ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click type states.
|
||||||
|
*/
|
||||||
|
private enum CLICK_STATE {
|
||||||
|
SINGLE,
|
||||||
|
DOUBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* configuration if the analog stick should be displayed as circle or square
|
||||||
|
*/
|
||||||
|
private boolean circle_stick = true; // TODO: implement square sick for simulations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* outer radius, this size will be automatically updated on resize
|
||||||
|
*/
|
||||||
|
private float radius_complete = 0;
|
||||||
|
/**
|
||||||
|
* analog stick radius, this size will be automatically updated on resize
|
||||||
|
*/
|
||||||
|
private float radius_analog_stick = 0;
|
||||||
|
/**
|
||||||
|
* dead zone radius, this size will be automatically updated on resize
|
||||||
|
*/
|
||||||
|
private float radius_dead_zone = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* horizontal position in relation to the center of the element
|
||||||
|
*/
|
||||||
|
private float relative_x = 0;
|
||||||
|
/**
|
||||||
|
* vertical position in relation to the center of the element
|
||||||
|
*/
|
||||||
|
private float relative_y = 0;
|
||||||
|
|
||||||
|
|
||||||
|
private double movement_radius = 0;
|
||||||
|
private double movement_angle = 0;
|
||||||
|
|
||||||
|
private float position_stick_x = 0;
|
||||||
|
private float position_stick_y = 0;
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
private STICK_STATE stick_state = STICK_STATE.NO_MOVEMENT;
|
||||||
|
private CLICK_STATE click_state = CLICK_STATE.SINGLE;
|
||||||
|
|
||||||
|
private List<AnalogStickListener> listeners = new ArrayList<>();
|
||||||
|
private long timeLastClick = 0;
|
||||||
|
|
||||||
|
private static double getMovementRadius(float x, float y) {
|
||||||
|
return Math.sqrt(x * x + y * y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double getAngle(float way_x, float way_y) {
|
||||||
|
// prevent divisions by zero for corner cases
|
||||||
|
if (way_x == 0) {
|
||||||
|
return way_y < 0 ? Math.PI : 0;
|
||||||
|
} else if (way_y == 0) {
|
||||||
|
if (way_x > 0) {
|
||||||
|
return Math.PI * 3 / 2;
|
||||||
|
} else if (way_x < 0) {
|
||||||
|
return Math.PI * 1 / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return correct calculated angle for each quadrant
|
||||||
|
if (way_x > 0) {
|
||||||
|
if (way_y < 0) {
|
||||||
|
// first quadrant
|
||||||
|
return 3 * Math.PI / 2 + Math.atan((double) (-way_y / way_x));
|
||||||
|
} else {
|
||||||
|
// second quadrant
|
||||||
|
return Math.PI + Math.atan((double) (way_x / way_y));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (way_y > 0) {
|
||||||
|
// third quadrant
|
||||||
|
return Math.PI / 2 + Math.atan((double) (way_y / -way_x));
|
||||||
|
} else {
|
||||||
|
// fourth quadrant
|
||||||
|
return 0 + Math.atan((double) (-way_x / -way_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalogStick(VirtualController controller, Context context) {
|
||||||
|
super(controller, context);
|
||||||
|
// reset stick position
|
||||||
|
position_stick_x = getWidth() / 2;
|
||||||
|
position_stick_y = getHeight() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAnalogStickListener(AnalogStickListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyOnMovement(float x, float y) {
|
||||||
|
_DBG("movement x: " + x + " movement y: " + y);
|
||||||
|
// notify listeners
|
||||||
|
for (AnalogStickListener listener : listeners) {
|
||||||
|
listener.onMovement(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyOnClick() {
|
||||||
|
_DBG("click");
|
||||||
|
// notify listeners
|
||||||
|
for (AnalogStickListener listener : listeners) {
|
||||||
|
listener.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyOnDoubleClick() {
|
||||||
|
_DBG("double click");
|
||||||
|
// notify listeners
|
||||||
|
for (AnalogStickListener listener : listeners) {
|
||||||
|
listener.onDoubleClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyOnRevoke() {
|
||||||
|
_DBG("revoke");
|
||||||
|
// notify listeners
|
||||||
|
for (AnalogStickListener listener : listeners) {
|
||||||
|
listener.onRevoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
// calculate new radius sizes depending
|
||||||
|
radius_complete = getPercent(getCorrectWidth() / 2, 90);
|
||||||
|
radius_dead_zone = getPercent(getCorrectWidth() / 2, 30);
|
||||||
|
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
|
||||||
|
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onElementDraw(Canvas canvas) {
|
||||||
|
// set transparent background
|
||||||
|
canvas.drawColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(getDefaultStrokeWidth());
|
||||||
|
|
||||||
|
// draw outer circle
|
||||||
|
if (!isPressed() || click_state == CLICK_STATE.SINGLE) {
|
||||||
|
paint.setColor(getDefaultColor());
|
||||||
|
} else {
|
||||||
|
paint.setColor(pressedColor);
|
||||||
|
}
|
||||||
|
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_complete, paint);
|
||||||
|
|
||||||
|
paint.setColor(getDefaultColor());
|
||||||
|
// draw dead zone
|
||||||
|
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_dead_zone, paint);
|
||||||
|
|
||||||
|
// draw stick depending on state
|
||||||
|
switch (stick_state) {
|
||||||
|
case NO_MOVEMENT: {
|
||||||
|
paint.setColor(getDefaultColor());
|
||||||
|
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_analog_stick, paint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MOVED_IN_DEAD_ZONE:
|
||||||
|
case MOVED_ACTIVE: {
|
||||||
|
paint.setColor(pressedColor);
|
||||||
|
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePosition() {
|
||||||
|
// get 100% way
|
||||||
|
float complete = radius_complete - radius_analog_stick;
|
||||||
|
|
||||||
|
// calculate relative way
|
||||||
|
float correlated_y = (float) (Math.sin(Math.PI / 2 - movement_angle) * (movement_radius));
|
||||||
|
float correlated_x = (float) (Math.cos(Math.PI / 2 - movement_angle) * (movement_radius));
|
||||||
|
|
||||||
|
// update positions
|
||||||
|
position_stick_x = getWidth() / 2 - correlated_x;
|
||||||
|
position_stick_y = getHeight() / 2 - correlated_y;
|
||||||
|
|
||||||
|
// Stay active even if we're back in the deadzone because we know the user is actively
|
||||||
|
// giving analog stick input and we don't want to snap back into the deadzone.
|
||||||
|
// We also release the deadzone if the user keeps the stick pressed for a bit to allow
|
||||||
|
// them to make precise movements.
|
||||||
|
stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE ||
|
||||||
|
System.currentTimeMillis() - timeLastClick > timeoutDeadzone ||
|
||||||
|
movement_radius > radius_dead_zone) ?
|
||||||
|
STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE;
|
||||||
|
|
||||||
|
// trigger move event if state active
|
||||||
|
if (stick_state == STICK_STATE.MOVED_ACTIVE) {
|
||||||
|
notifyOnMovement(-correlated_x / complete, correlated_y / complete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onElementTouchEvent(MotionEvent event) {
|
||||||
|
// save last click state
|
||||||
|
CLICK_STATE lastClickState = click_state;
|
||||||
|
|
||||||
|
// get absolute way for each axis
|
||||||
|
relative_x = -(getWidth() / 2 - event.getX());
|
||||||
|
relative_y = -(getHeight() / 2 - event.getY());
|
||||||
|
|
||||||
|
// get radius and angel of movement from center
|
||||||
|
movement_radius = getMovementRadius(relative_x, relative_y);
|
||||||
|
movement_angle = getAngle(relative_x, relative_y);
|
||||||
|
|
||||||
|
// chop radius if out of outer circle and already pressed
|
||||||
|
if (movement_radius > (radius_complete - radius_analog_stick)) {
|
||||||
|
// not pressed already, so ignore event from outer circle
|
||||||
|
if (!isPressed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
movement_radius = radius_complete - radius_analog_stick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle event depending on action
|
||||||
|
switch (event.getActionMasked()) {
|
||||||
|
// down event (touch event)
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||||
|
// set to dead zoned, will be corrected in update position if necessary
|
||||||
|
stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE;
|
||||||
|
// check for double click
|
||||||
|
if (lastClickState == CLICK_STATE.SINGLE &&
|
||||||
|
timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) {
|
||||||
|
click_state = CLICK_STATE.DOUBLE;
|
||||||
|
notifyOnDoubleClick();
|
||||||
|
} else {
|
||||||
|
click_state = CLICK_STATE.SINGLE;
|
||||||
|
notifyOnClick();
|
||||||
|
}
|
||||||
|
// reset last click timestamp
|
||||||
|
timeLastClick = System.currentTimeMillis();
|
||||||
|
// set item pressed and update
|
||||||
|
setPressed(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// up event (revoke touch)
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_UP: {
|
||||||
|
setPressed(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
// when is pressed calculate new positions (will trigger movement if necessary)
|
||||||
|
updatePosition();
|
||||||
|
} else {
|
||||||
|
stick_state = STICK_STATE.NO_MOVEMENT;
|
||||||
|
notifyOnRevoke();
|
||||||
|
// not longer pressed reset analog stick
|
||||||
|
notifyOnMovement(0, 0);
|
||||||
|
}
|
||||||
|
// refresh view
|
||||||
|
invalidate();
|
||||||
|
// accept the touch event
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a digital button on screen element. It is used to get click and double click user input.
|
||||||
|
*/
|
||||||
|
public class DigitalButton extends VirtualControllerElement {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface to update registered observers.
|
||||||
|
*/
|
||||||
|
public interface DigitalButtonListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onClick event will be fired on button click.
|
||||||
|
*/
|
||||||
|
void onClick();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onLongClick event will be fired on button long click.
|
||||||
|
*/
|
||||||
|
void onLongClick();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onRelease event will be fired on button unpress.
|
||||||
|
*/
|
||||||
|
void onRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private class TimerLongClickTimerTask extends TimerTask {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onLongClickCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>();
|
||||||
|
private String text = "";
|
||||||
|
private int icon = -1;
|
||||||
|
private long timerLongClickTimeout = 3000;
|
||||||
|
private Timer timerLongClick = null;
|
||||||
|
private TimerLongClickTimerTask longClickTimerTask = null;
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
private int layer;
|
||||||
|
private DigitalButton movingButton = null;
|
||||||
|
|
||||||
|
boolean inRange(float x, float y) {
|
||||||
|
return (this.getX() < x && this.getX() + this.getWidth() > x) &&
|
||||||
|
(this.getY() < y && this.getY() + this.getHeight() > y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkMovement(float x, float y, DigitalButton movingButton) {
|
||||||
|
// check if the movement happened in the same layer
|
||||||
|
if (movingButton.layer != this.layer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save current pressed state
|
||||||
|
boolean wasPressed = isPressed();
|
||||||
|
|
||||||
|
// check if the movement directly happened on the button
|
||||||
|
if ((this.movingButton == null || movingButton == this.movingButton)
|
||||||
|
&& this.inRange(x, y)) {
|
||||||
|
// set button pressed state depending on moving button pressed state
|
||||||
|
if (this.isPressed() != movingButton.isPressed()) {
|
||||||
|
this.setPressed(movingButton.isPressed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if the movement is outside of the range and the movement button
|
||||||
|
// is the saved moving button
|
||||||
|
else if (movingButton == this.movingButton) {
|
||||||
|
this.setPressed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a change occurred
|
||||||
|
if (wasPressed != isPressed()) {
|
||||||
|
if (isPressed()) {
|
||||||
|
// is pressed set moving button and emit click event
|
||||||
|
this.movingButton = movingButton;
|
||||||
|
onClickCallback();
|
||||||
|
} else {
|
||||||
|
// no longer pressed reset moving button and emit release event
|
||||||
|
this.movingButton = null;
|
||||||
|
onReleaseCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMovementForAllButtons(float x, float y) {
|
||||||
|
for (VirtualControllerElement element : virtualController.getElements()) {
|
||||||
|
if (element != this && element instanceof DigitalButton) {
|
||||||
|
((DigitalButton) element).checkMovement(x, y, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DigitalButton(VirtualController controller, int layer, Context context) {
|
||||||
|
super(controller, context);
|
||||||
|
this.layer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDigitalButtonListener(DigitalButtonListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(int id) {
|
||||||
|
this.icon = id;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onElementDraw(Canvas canvas) {
|
||||||
|
// set transparent background
|
||||||
|
canvas.drawColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
|
paint.setTextSize(getPercent(getWidth(), 30));
|
||||||
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
paint.setStrokeWidth(getDefaultStrokeWidth());
|
||||||
|
|
||||||
|
paint.setColor(isPressed() ? pressedColor : getDefaultColor());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawRect(paint.getStrokeWidth(), paint.getStrokeWidth(),
|
||||||
|
getWidth() - paint.getStrokeWidth(), getHeight() - paint.getStrokeWidth(), paint);
|
||||||
|
|
||||||
|
if (icon != -1) {
|
||||||
|
Drawable d = getResources().getDrawable(icon);
|
||||||
|
d.setBounds(5, 5, getWidth() - 5, getHeight() - 5);
|
||||||
|
d.draw(canvas);
|
||||||
|
} else {
|
||||||
|
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||||
|
paint.setStrokeWidth(getDefaultStrokeWidth()/2);
|
||||||
|
canvas.drawText(text, getPercent(getWidth(), 50), getPercent(getHeight(), 63), paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickCallback() {
|
||||||
|
_DBG("clicked");
|
||||||
|
// notify listeners
|
||||||
|
for (DigitalButtonListener listener : listeners) {
|
||||||
|
listener.onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
timerLongClick = new Timer();
|
||||||
|
longClickTimerTask = new TimerLongClickTimerTask();
|
||||||
|
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLongClickCallback() {
|
||||||
|
_DBG("long click");
|
||||||
|
// notify listeners
|
||||||
|
for (DigitalButtonListener listener : listeners) {
|
||||||
|
listener.onLongClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onReleaseCallback() {
|
||||||
|
_DBG("released");
|
||||||
|
// notify listeners
|
||||||
|
for (DigitalButtonListener listener : listeners) {
|
||||||
|
listener.onRelease();
|
||||||
|
}
|
||||||
|
timerLongClick.cancel();
|
||||||
|
longClickTimerTask.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onElementTouchEvent(MotionEvent event) {
|
||||||
|
// get masked (not specific to a pointer) action
|
||||||
|
float x = getX() + event.getX();
|
||||||
|
float y = getY() + event.getY();
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||||
|
movingButton = null;
|
||||||
|
setPressed(true);
|
||||||
|
onClickCallback();
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
checkMovementForAllButtons(x, y);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_UP: {
|
||||||
|
setPressed(false);
|
||||||
|
onReleaseCallback();
|
||||||
|
|
||||||
|
checkMovementForAllButtons(x, y);
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DigitalPad extends VirtualControllerElement {
|
||||||
|
public final static int DIGITAL_PAD_DIRECTION_NO_DIRECTION = 0;
|
||||||
|
int direction = DIGITAL_PAD_DIRECTION_NO_DIRECTION;
|
||||||
|
public final static int DIGITAL_PAD_DIRECTION_LEFT = 1;
|
||||||
|
public final static int DIGITAL_PAD_DIRECTION_UP = 2;
|
||||||
|
public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4;
|
||||||
|
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8;
|
||||||
|
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>();
|
||||||
|
|
||||||
|
private static final int DPAD_MARGIN = 5;
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
public DigitalPad(VirtualController controller, Context context) {
|
||||||
|
super(controller, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDigitalPadListener(DigitalPadListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onElementDraw(Canvas canvas) {
|
||||||
|
// set transparent background
|
||||||
|
canvas.drawColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
|
paint.setTextSize(getPercent(getCorrectWidth(), 20));
|
||||||
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
paint.setStrokeWidth(getDefaultStrokeWidth());
|
||||||
|
|
||||||
|
if (direction == DIGITAL_PAD_DIRECTION_NO_DIRECTION) {
|
||||||
|
// draw no direction rect
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setColor(getDefaultColor());
|
||||||
|
canvas.drawRect(
|
||||||
|
getPercent(getWidth(), 36), getPercent(getHeight(), 36),
|
||||||
|
getPercent(getWidth(), 63), getPercent(getHeight(), 63),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw left rect
|
||||||
|
paint.setColor(
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 ? pressedColor : getDefaultColor());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawRect(
|
||||||
|
paint.getStrokeWidth()+DPAD_MARGIN, getPercent(getHeight(), 33),
|
||||||
|
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// draw up rect
|
||||||
|
paint.setColor(
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_UP) > 0 ? pressedColor : getDefaultColor());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawRect(
|
||||||
|
getPercent(getWidth(), 33), paint.getStrokeWidth()+DPAD_MARGIN,
|
||||||
|
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw right rect
|
||||||
|
paint.setColor(
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 ? pressedColor : getDefaultColor());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawRect(
|
||||||
|
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
|
||||||
|
getWidth() - (paint.getStrokeWidth()+DPAD_MARGIN), getPercent(getHeight(), 66),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw down rect
|
||||||
|
paint.setColor(
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 ? pressedColor : getDefaultColor());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawRect(
|
||||||
|
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
|
||||||
|
getPercent(getWidth(), 66), getHeight() - (paint.getStrokeWidth()+DPAD_MARGIN),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw left up line
|
||||||
|
paint.setColor((
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 &&
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_UP) > 0
|
||||||
|
) ? pressedColor : getDefaultColor()
|
||||||
|
);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawLine(
|
||||||
|
paint.getStrokeWidth()+DPAD_MARGIN, getPercent(getHeight(), 33),
|
||||||
|
getPercent(getWidth(), 33), paint.getStrokeWidth()+DPAD_MARGIN,
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw up right line
|
||||||
|
paint.setColor((
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_UP) > 0 &&
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0
|
||||||
|
) ? pressedColor : getDefaultColor()
|
||||||
|
);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawLine(
|
||||||
|
getPercent(getWidth(), 66), paint.getStrokeWidth()+DPAD_MARGIN,
|
||||||
|
getWidth() - (paint.getStrokeWidth()+DPAD_MARGIN), getPercent(getHeight(), 33),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw right down line
|
||||||
|
paint.setColor((
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 &&
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0
|
||||||
|
) ? pressedColor : getDefaultColor()
|
||||||
|
);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawLine(
|
||||||
|
getWidth()-paint.getStrokeWidth(), getPercent(getHeight(), 66),
|
||||||
|
getPercent(getWidth(), 66), getHeight()-(paint.getStrokeWidth()+DPAD_MARGIN),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw down left line
|
||||||
|
paint.setColor((
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 &&
|
||||||
|
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0
|
||||||
|
) ? pressedColor : getDefaultColor()
|
||||||
|
);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
canvas.drawLine(
|
||||||
|
getPercent(getWidth(), 33), getHeight()-(paint.getStrokeWidth()+DPAD_MARGIN),
|
||||||
|
paint.getStrokeWidth()+DPAD_MARGIN, getPercent(getHeight(), 66),
|
||||||
|
paint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void newDirectionCallback(int direction) {
|
||||||
|
_DBG("direction: " + direction);
|
||||||
|
|
||||||
|
// notify listeners
|
||||||
|
for (DigitalPadListener listener : listeners) {
|
||||||
|
listener.onDirectionChange(direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onElementTouchEvent(MotionEvent event) {
|
||||||
|
// get masked (not specific to a pointer) action
|
||||||
|
switch (event.getActionMasked()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
direction = 0;
|
||||||
|
|
||||||
|
if (event.getX() < getPercent(getWidth(), 33)) {
|
||||||
|
direction |= DIGITAL_PAD_DIRECTION_LEFT;
|
||||||
|
}
|
||||||
|
if (event.getX() > getPercent(getWidth(), 66)) {
|
||||||
|
direction |= DIGITAL_PAD_DIRECTION_RIGHT;
|
||||||
|
}
|
||||||
|
if (event.getY() > getPercent(getHeight(), 66)) {
|
||||||
|
direction |= DIGITAL_PAD_DIRECTION_DOWN;
|
||||||
|
}
|
||||||
|
if (event.getY() < getPercent(getHeight(), 33)) {
|
||||||
|
direction |= DIGITAL_PAD_DIRECTION_UP;
|
||||||
|
}
|
||||||
|
newDirectionCallback(direction);
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_UP: {
|
||||||
|
direction = 0;
|
||||||
|
newDirectionCallback(direction);
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DigitalPadListener {
|
||||||
|
void onDirectionChange(int direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
public class LeftAnalogStick extends AnalogStick {
|
||||||
|
public LeftAnalogStick(final VirtualController controller, final Context context) {
|
||||||
|
super(controller, context);
|
||||||
|
|
||||||
|
addAnalogStickListener(new AnalogStick.AnalogStickListener() {
|
||||||
|
@Override
|
||||||
|
public void onMovement(float x, float y) {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.leftStickX = (short) (x * 0x7FFE);
|
||||||
|
inputContext.leftStickY = (short) (y * 0x7FFE);
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDoubleClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap |= ControllerPacket.LS_CLK_FLAG;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRevoke() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class LeftTrigger extends DigitalButton {
|
||||||
|
public LeftTrigger(final VirtualController controller, final int layer, final Context context) {
|
||||||
|
super(controller, layer, context);
|
||||||
|
addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.leftTrigger = (byte) 0xFF;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongClick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRelease() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.leftTrigger = (byte) 0x00;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
public class RightAnalogStick extends AnalogStick {
|
||||||
|
public RightAnalogStick(final VirtualController controller, final Context context) {
|
||||||
|
super(controller, context);
|
||||||
|
|
||||||
|
addAnalogStickListener(new AnalogStick.AnalogStickListener() {
|
||||||
|
@Override
|
||||||
|
public void onMovement(float x, float y) {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.rightStickX = (short) (x * 0x7FFE);
|
||||||
|
inputContext.rightStickY = (short) (y * 0x7FFE);
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDoubleClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRevoke() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class RightTrigger extends DigitalButton {
|
||||||
|
public RightTrigger(final VirtualController controller, final int layer, final Context context) {
|
||||||
|
super(controller, layer, context);
|
||||||
|
addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.rightTrigger = (byte) 0xFF;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongClick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRelease() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.rightTrigger = (byte) 0x00;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.limelight.R;
|
||||||
|
import com.limelight.nvstream.NvConnection;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VirtualController {
|
||||||
|
public class ControllerInputContext {
|
||||||
|
public short inputMap = 0x0000;
|
||||||
|
public byte leftTrigger = 0x00;
|
||||||
|
public byte rightTrigger = 0x00;
|
||||||
|
public short rightStickX = 0x0000;
|
||||||
|
public short rightStickY = 0x0000;
|
||||||
|
public short leftStickX = 0x0000;
|
||||||
|
public short leftStickY = 0x0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ControllerMode {
|
||||||
|
Active,
|
||||||
|
Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
||||||
|
|
||||||
|
private NvConnection connection = null;
|
||||||
|
private Context context = null;
|
||||||
|
|
||||||
|
private FrameLayout frame_layout = null;
|
||||||
|
private RelativeLayout relative_layout = null;
|
||||||
|
|
||||||
|
ControllerMode currentMode = ControllerMode.Active;
|
||||||
|
ControllerInputContext inputContext = new ControllerInputContext();
|
||||||
|
|
||||||
|
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
|
||||||
|
private Button buttonConfigure = null;
|
||||||
|
|
||||||
|
private List<VirtualControllerElement> elements = new ArrayList<VirtualControllerElement>();
|
||||||
|
|
||||||
|
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) {
|
||||||
|
this.connection = conn;
|
||||||
|
this.frame_layout = layout;
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
relative_layout = new RelativeLayout(context);
|
||||||
|
|
||||||
|
frame_layout.addView(relative_layout);
|
||||||
|
|
||||||
|
buttonConfigure = new Button(context);
|
||||||
|
buttonConfigure.setBackgroundResource(R.drawable.settings);
|
||||||
|
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
String message;
|
||||||
|
|
||||||
|
if (currentMode == ControllerMode.Configuration) {
|
||||||
|
currentMode = ControllerMode.Active;
|
||||||
|
message = "Exiting configuration mode";
|
||||||
|
} else {
|
||||||
|
currentMode = ControllerMode.Configuration;
|
||||||
|
message = "Entering configuration mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
relative_layout.invalidate();
|
||||||
|
|
||||||
|
for (VirtualControllerElement element : elements) {
|
||||||
|
element.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeElements() {
|
||||||
|
for (VirtualControllerElement element : elements) {
|
||||||
|
relative_layout.removeView(element);
|
||||||
|
}
|
||||||
|
elements.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
|
||||||
|
elements.add(element);
|
||||||
|
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
|
||||||
|
layoutParams.setMargins(x, y, 0, 0);
|
||||||
|
|
||||||
|
relative_layout.addView(element, layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<VirtualControllerElement> getElements() {
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final void _DBG(String text) {
|
||||||
|
if (_PRINT_DEBUG_INFORMATION) {
|
||||||
|
System.out.println("VirtualController: " + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshLayout() {
|
||||||
|
relative_layout.removeAllViews();
|
||||||
|
removeElements();
|
||||||
|
|
||||||
|
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
||||||
|
|
||||||
|
int buttonSize = (int)(screen.heightPixels*0.05f);
|
||||||
|
layoutParamsButtonConfigure = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
|
||||||
|
relative_layout.addView(buttonConfigure, layoutParamsButtonConfigure);
|
||||||
|
|
||||||
|
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerMode getControllerMode() {
|
||||||
|
return currentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerInputContext getControllerInputContext() {
|
||||||
|
return inputContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendControllerInputContext() {
|
||||||
|
sendControllerInputPacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendControllerInputPacket() {
|
||||||
|
try {
|
||||||
|
_DBG("INPUT_MAP + " + inputContext.inputMap);
|
||||||
|
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
|
||||||
|
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
|
||||||
|
_DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY);
|
||||||
|
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
|
||||||
|
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
|
||||||
|
|
||||||
|
if (connection != null) {
|
||||||
|
connection.sendControllerInput(
|
||||||
|
inputContext.inputMap,
|
||||||
|
inputContext.leftTrigger,
|
||||||
|
inputContext.rightTrigger,
|
||||||
|
inputContext.leftStickX,
|
||||||
|
inputContext.leftStickY,
|
||||||
|
inputContext.rightStickX,
|
||||||
|
inputContext.rightStickY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+285
@@ -0,0 +1,285 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
public class VirtualControllerConfigurationLoader {
|
||||||
|
private static final String PROFILE_PATH = "profiles";
|
||||||
|
|
||||||
|
private static int getPercent(
|
||||||
|
int percent,
|
||||||
|
int total) {
|
||||||
|
return (int) (((float) total / (float) 100) * (float) percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DigitalPad createDigitalPad(
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
|
||||||
|
DigitalPad digitalPad = new DigitalPad(controller, context);
|
||||||
|
digitalPad.addDigitalPadListener(new DigitalPad.DigitalPadListener() {
|
||||||
|
@Override
|
||||||
|
public void onDirectionChange(int direction) {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
|
||||||
|
if (direction == DigitalPad.DIGITAL_PAD_DIRECTION_NO_DIRECTION) {
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.UP_FLAG;
|
||||||
|
inputContext.inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_LEFT) > 0) {
|
||||||
|
inputContext.inputMap |= ControllerPacket.LEFT_FLAG;
|
||||||
|
}
|
||||||
|
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_RIGHT) > 0) {
|
||||||
|
inputContext.inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||||
|
}
|
||||||
|
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_UP) > 0) {
|
||||||
|
inputContext.inputMap |= ControllerPacket.UP_FLAG;
|
||||||
|
}
|
||||||
|
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_DOWN) > 0) {
|
||||||
|
inputContext.inputMap |= ControllerPacket.DOWN_FLAG;
|
||||||
|
}
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return digitalPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DigitalButton createDigitalButton(
|
||||||
|
final int keyShort,
|
||||||
|
final int keyLong,
|
||||||
|
final int layer,
|
||||||
|
final String text,
|
||||||
|
final int icon,
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
DigitalButton button = new DigitalButton(controller, layer, context);
|
||||||
|
button.setText(text);
|
||||||
|
button.setIcon(icon);
|
||||||
|
|
||||||
|
button.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap |= keyShort;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongClick() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap |= keyLong;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRelease() {
|
||||||
|
VirtualController.ControllerInputContext inputContext =
|
||||||
|
controller.getControllerInputContext();
|
||||||
|
inputContext.inputMap &= ~keyShort;
|
||||||
|
inputContext.inputMap &= ~keyLong;
|
||||||
|
|
||||||
|
controller.sendControllerInputContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DigitalButton createLeftTrigger(
|
||||||
|
final int layer,
|
||||||
|
final String text,
|
||||||
|
final int icon,
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
LeftTrigger button = new LeftTrigger(controller, layer, context);
|
||||||
|
button.setText(text);
|
||||||
|
button.setIcon(icon);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DigitalButton createRightTrigger(
|
||||||
|
final int layer,
|
||||||
|
final String text,
|
||||||
|
final int icon,
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
RightTrigger button = new RightTrigger(controller, layer, context);
|
||||||
|
button.setText(text);
|
||||||
|
button.setIcon(icon);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnalogStick createLeftStick(
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
return new LeftAnalogStick(controller, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnalogStick createRightStick(
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
return new RightAnalogStick(controller, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int BUTTON_BASE_X = 65;
|
||||||
|
private static final int BUTTON_BASE_Y = 5;
|
||||||
|
private static final int BUTTON_WIDTH = getPercent(30, 33);
|
||||||
|
private static final int BUTTON_HEIGHT = getPercent(40, 33);
|
||||||
|
|
||||||
|
public static void createDefaultLayout(final VirtualController controller, final Context context) {
|
||||||
|
|
||||||
|
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
||||||
|
|
||||||
|
// NOTE: Some of these getPercent() expressions seem like they can be combined
|
||||||
|
// into a single call. Due to floating point rounding, this isn't actually possible.
|
||||||
|
|
||||||
|
controller.addElement(createDigitalPad(controller, context),
|
||||||
|
getPercent(5, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels),
|
||||||
|
getPercent(30, screen.widthPixels),
|
||||||
|
getPercent(40, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.A_FLAG, 0, 1, "A", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels)+getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels)+2*getPercent(BUTTON_HEIGHT, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.B_FLAG, 0, 1, "B", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels)+2*getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels)+getPercent(BUTTON_HEIGHT, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.X_FLAG, 0, 1, "X", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels)+getPercent(BUTTON_HEIGHT, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.Y_FLAG, 0, 1, "Y", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels)+getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createLeftTrigger(
|
||||||
|
0, "LT", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createRightTrigger(
|
||||||
|
0, "RT", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels)+2*getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.LB_FLAG, 0, 1, "LB", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels)+2*getPercent(BUTTON_HEIGHT, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.RB_FLAG, 0, 1, "RB", -1, controller, context),
|
||||||
|
getPercent(BUTTON_BASE_X, screen.widthPixels)+2*getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_BASE_Y, screen.heightPixels)+2*getPercent(BUTTON_HEIGHT, screen.heightPixels),
|
||||||
|
getPercent(BUTTON_WIDTH, screen.widthPixels),
|
||||||
|
getPercent(BUTTON_HEIGHT, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createLeftStick(controller, context),
|
||||||
|
getPercent(5, screen.widthPixels),
|
||||||
|
getPercent(50, screen.heightPixels),
|
||||||
|
getPercent(40, screen.widthPixels),
|
||||||
|
getPercent(50, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createRightStick(controller, context),
|
||||||
|
getPercent(55, screen.widthPixels),
|
||||||
|
getPercent(50, screen.heightPixels),
|
||||||
|
getPercent(40, screen.widthPixels),
|
||||||
|
getPercent(50, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.BACK_FLAG, 0, 2, "BACK", -1, controller, context),
|
||||||
|
getPercent(40, screen.widthPixels),
|
||||||
|
getPercent(90, screen.heightPixels),
|
||||||
|
getPercent(10, screen.widthPixels),
|
||||||
|
getPercent(10, screen.heightPixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addElement(createDigitalButton(
|
||||||
|
ControllerPacket.PLAY_FLAG, 0, 3, "START", -1, controller, context),
|
||||||
|
getPercent(40, screen.widthPixels)+getPercent(10, screen.widthPixels),
|
||||||
|
getPercent(90, screen.heightPixels),
|
||||||
|
getPercent(10, screen.widthPixels),
|
||||||
|
getPercent(10, screen.heightPixels)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NOT IMPLEMENTED YET,
|
||||||
|
this should later be used to store and load a profile for the virtual controller
|
||||||
|
public static void saveProfile(final String name,
|
||||||
|
final VirtualController controller,
|
||||||
|
final Context context) {
|
||||||
|
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(PROFILE_PATH + "/" +
|
||||||
|
name, Activity.MODE_PRIVATE);
|
||||||
|
|
||||||
|
JSONArray elementConfigurations = new JSONArray();
|
||||||
|
for (VirtualControllerElement element : controller.getElements()) {
|
||||||
|
JSONObject elementConfiguration = new JSONObject();
|
||||||
|
try {
|
||||||
|
elementConfiguration.put("TYPE", element.getClass().getName());
|
||||||
|
elementConfiguration.put("CONFIGURATION", element.getConfiguration());
|
||||||
|
elementConfigurations.put(elementConfiguration);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor= preferences.edit();
|
||||||
|
editor.putString("ELEMENTS", elementConfigurations.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadFromPreferences(final VirtualController controller) {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
+290
@@ -0,0 +1,290 @@
|
|||||||
|
/**
|
||||||
|
* Created by Karim Mreisi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.limelight.binding.input.virtual_controller;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
public abstract class VirtualControllerElement extends View {
|
||||||
|
protected static boolean _PRINT_DEBUG_INFORMATION = false;
|
||||||
|
|
||||||
|
protected VirtualController virtualController;
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
private int normalColor = 0xF0888888;
|
||||||
|
protected int pressedColor = 0xF00000FF;
|
||||||
|
private int configNormalColor = 0xF0FF0000;
|
||||||
|
private int configSelectedColor = 0xF000FF00;
|
||||||
|
|
||||||
|
protected int startSize_x;
|
||||||
|
protected int startSize_y;
|
||||||
|
|
||||||
|
float position_pressed_x = 0;
|
||||||
|
float position_pressed_y = 0;
|
||||||
|
|
||||||
|
private enum Mode {
|
||||||
|
Normal,
|
||||||
|
Resize,
|
||||||
|
Move
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mode currentMode = Mode.Normal;
|
||||||
|
|
||||||
|
protected VirtualControllerElement(VirtualController controller, Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
this.virtualController = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void moveElement(int pressed_x, int pressed_y, int x, int y) {
|
||||||
|
int newPos_x = (int) getX() + x - pressed_x;
|
||||||
|
int newPos_y = (int) getY() + y - pressed_y;
|
||||||
|
|
||||||
|
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
|
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
|
||||||
|
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
|
||||||
|
layoutParams.rightMargin = 0;
|
||||||
|
layoutParams.bottomMargin = 0;
|
||||||
|
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
|
||||||
|
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
|
int newHeight = height + (startSize_y - pressed_y);
|
||||||
|
int newWidth = width + (startSize_x - pressed_x);
|
||||||
|
|
||||||
|
layoutParams.height = newHeight > 20 ? newHeight : 20;
|
||||||
|
layoutParams.width = newWidth > 20 ? newWidth : 20;
|
||||||
|
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
onElementDraw(canvas);
|
||||||
|
|
||||||
|
if (currentMode != Mode.Normal) {
|
||||||
|
paint.setColor(configSelectedColor);
|
||||||
|
paint.setStrokeWidth(getDefaultStrokeWidth());
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
|
||||||
|
canvas.drawRect(paint.getStrokeWidth(), paint.getStrokeWidth(),
|
||||||
|
getWidth()-paint.getStrokeWidth(), getHeight()-paint.getStrokeWidth(),
|
||||||
|
paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDraw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
protected void actionShowNormalColorChooser() {
|
||||||
|
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(AmbilWarnaDialog dialog)
|
||||||
|
{}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOk(AmbilWarnaDialog dialog, int color) {
|
||||||
|
normalColor = color;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
colorDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void actionShowPressedColorChooser() {
|
||||||
|
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(AmbilWarnaDialog dialog) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOk(AmbilWarnaDialog dialog, int color) {
|
||||||
|
pressedColor = color;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
colorDialog.show();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected void actionEnableMove() {
|
||||||
|
currentMode = Mode.Move;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void actionEnableResize() {
|
||||||
|
currentMode = Mode.Resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void actionCancel() {
|
||||||
|
currentMode = Mode.Normal;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getDefaultColor() {
|
||||||
|
return (virtualController.getControllerMode() == VirtualController.ControllerMode.Configuration) ?
|
||||||
|
configNormalColor : normalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getDefaultStrokeWidth() {
|
||||||
|
DisplayMetrics screen = getResources().getDisplayMetrics();
|
||||||
|
return (int)(screen.heightPixels*0.004f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showConfigurationDialog() {
|
||||||
|
try {
|
||||||
|
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
|
||||||
|
|
||||||
|
alertBuilder.setTitle("Configuration");
|
||||||
|
|
||||||
|
CharSequence functions[] = new CharSequence[]{
|
||||||
|
"Move",
|
||||||
|
"Resize",
|
||||||
|
/*election
|
||||||
|
"Set n
|
||||||
|
Disable color sormal color",
|
||||||
|
"Set pressed color",
|
||||||
|
*/
|
||||||
|
"Cancel"
|
||||||
|
};
|
||||||
|
|
||||||
|
alertBuilder.setItems(functions, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
switch (which) {
|
||||||
|
case 0: { // move
|
||||||
|
actionEnableMove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: { // resize
|
||||||
|
actionEnableResize();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
case 2: { // set default color
|
||||||
|
actionShowNormalColorChooser();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: { // set pressed color
|
||||||
|
actionShowPressedColorChooser();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
default: { // cancel
|
||||||
|
actionCancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog alert = alertBuilder.create();
|
||||||
|
// show menu
|
||||||
|
alert.show();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (virtualController.getControllerMode() == VirtualController.ControllerMode.Active) {
|
||||||
|
return onElementTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.getActionMasked()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||||
|
position_pressed_x = event.getX();
|
||||||
|
position_pressed_y = event.getY();
|
||||||
|
startSize_x = getWidth();
|
||||||
|
startSize_y = getHeight();
|
||||||
|
|
||||||
|
actionEnableMove();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
switch (currentMode) {
|
||||||
|
case Move: {
|
||||||
|
moveElement(
|
||||||
|
(int) position_pressed_x,
|
||||||
|
(int) position_pressed_y,
|
||||||
|
(int) event.getX(),
|
||||||
|
(int) event.getY());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Resize: {
|
||||||
|
resizeElement(
|
||||||
|
(int) position_pressed_x,
|
||||||
|
(int) position_pressed_y,
|
||||||
|
(int) event.getX(),
|
||||||
|
(int) event.getY());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Normal: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_UP: {
|
||||||
|
actionCancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected void onElementDraw(Canvas canvas);
|
||||||
|
|
||||||
|
abstract public boolean onElementTouchEvent(MotionEvent event);
|
||||||
|
|
||||||
|
protected static final void _DBG(String text) {
|
||||||
|
if (_PRINT_DEBUG_INFORMATION) {
|
||||||
|
System.out.println(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColors(int normalColor, int pressedColor) {
|
||||||
|
this.normalColor = normalColor;
|
||||||
|
this.pressedColor = pressedColor;
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final float getPercent(float value, float percent) {
|
||||||
|
return value / 100 * percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final int getCorrectWidth() {
|
||||||
|
return getWidth() > getHeight() ? getHeight() : getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
public JSONObject getConfiguration () {
|
||||||
|
JSONObject configuration = new JSONObject();
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadConfiguration (JSONObject configuration) {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -109,6 +109,20 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
else {
|
else {
|
||||||
LimeLog.info("No HEVC decoder found");
|
LimeLog.info("No HEVC decoder found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set attributes that are queried in getCapabilities(). This must be done here
|
||||||
|
// because getCapabilities() may be called before setup() in current versions of the common
|
||||||
|
// library. The limitation of this is that we don't know whether we're using HEVC or AVC, so
|
||||||
|
// we just assume AVC. This isn't really a problem because the capabilities are usually
|
||||||
|
// shared between AVC and HEVC decoders on the same device.
|
||||||
|
if (avcDecoderName != null) {
|
||||||
|
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(avcDecoderName);
|
||||||
|
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(avcDecoderName);
|
||||||
|
|
||||||
|
if (directSubmit) {
|
||||||
|
LimeLog.info("Decoder "+avcDecoderName+" will use direct submit");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -171,14 +185,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set decoder-specific attributes
|
|
||||||
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(selectedDecoderName);
|
|
||||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderName);
|
|
||||||
|
|
||||||
if (directSubmit) {
|
|
||||||
LimeLog.info("Decoder "+selectedDecoderName+" will use direct submit");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
||||||
// due to implementation problems
|
// due to implementation problems
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -76,14 +76,31 @@ public class MediaCodecHelper {
|
|||||||
|
|
||||||
constrainedHighProfilePrefixes = new LinkedList<String>();
|
constrainedHighProfilePrefixes = new LinkedList<String>();
|
||||||
constrainedHighProfilePrefixes.add("omx.intel");
|
constrainedHighProfilePrefixes.add("omx.intel");
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
whitelistedHevcDecoders = new LinkedList<>();
|
whitelistedHevcDecoders = new LinkedList<>();
|
||||||
|
|
||||||
|
// Exynos seems to be the only HEVC decoder that works reliably
|
||||||
whitelistedHevcDecoders.add("omx.exynos");
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
// whitelistedHevcDecoders.add("omx.nvidia"); TODO: This needs a similar fixup to the Tegra 3
|
|
||||||
whitelistedHevcDecoders.add("omx.mtk");
|
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
||||||
whitelistedHevcDecoders.add("omx.amlogic");
|
//whitelistedHevcDecoders.add("omx.nvidia");
|
||||||
whitelistedHevcDecoders.add("omx.rk");
|
|
||||||
// omx.qcom added conditionally during initialization
|
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||||
|
// I know the Fire TV 2 works, so I'll just whitelist Amazon devices which seem
|
||||||
|
// to actually be tested. Ugh...
|
||||||
|
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||||
|
whitelistedHevcDecoders.add("omx.mtk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// These theoretically have good HEVC decoding capabilities (potentially better than
|
||||||
|
// their AVC decoders), but haven't been tested enough
|
||||||
|
//whitelistedHevcDecoders.add("omx.amlogic");
|
||||||
|
//whitelistedHevcDecoders.add("omx.rk");
|
||||||
|
|
||||||
|
// Based on GPU attributes queried at runtime, the omx.qcom prefix will be added
|
||||||
|
// during initialization to avoid SoCs with broken HEVC decoders.
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void initializeWithContext(Context context) {
|
public static void initializeWithContext(Context context) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class ComputerDatabaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deleteComputer(String name) {
|
public void deleteComputer(String name) {
|
||||||
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateComputer(ComputerDetails details) {
|
public boolean updateComputer(ComputerDetails details) {
|
||||||
@@ -118,7 +118,7 @@ public class ComputerDatabaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ComputerDetails getComputerByName(String name) {
|
public ComputerDetails getComputerByName(String name) {
|
||||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null);
|
||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = new ComputerDetails();
|
||||||
if (!c.moveToFirst()) {
|
if (!c.moveToFirst()) {
|
||||||
// No matching computer
|
// No matching computer
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final String ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround";
|
private static final String ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround";
|
||||||
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
||||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||||
|
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||||
|
|
||||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
@@ -42,6 +43,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_ENABLE_51_SURROUND = false;
|
private static final boolean DEFAULT_ENABLE_51_SURROUND = false;
|
||||||
private static final boolean DEFAULT_USB_DRIVER = true;
|
private static final boolean DEFAULT_USB_DRIVER = true;
|
||||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||||
|
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
||||||
|
|
||||||
public static final int FORCE_H265_ON = -1;
|
public static final int FORCE_H265_ON = -1;
|
||||||
public static final int AUTOSELECT_H265 = 0;
|
public static final int AUTOSELECT_H265 = 0;
|
||||||
@@ -54,6 +56,7 @@ public class PreferenceConfiguration {
|
|||||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||||
public String language;
|
public String language;
|
||||||
public boolean listMode, smallIconMode, multiController, enable51Surround, usbDriver;
|
public boolean listMode, smallIconMode, multiController, enable51Surround, usbDriver;
|
||||||
|
public boolean onscreenController;
|
||||||
|
|
||||||
public static int getDefaultBitrate(String resFpsString) {
|
public static int getDefaultBitrate(String resFpsString) {
|
||||||
if (resFpsString.equals("720p30")) {
|
if (resFpsString.equals("720p30")) {
|
||||||
@@ -183,6 +186,7 @@ public class PreferenceConfiguration {
|
|||||||
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
||||||
config.enable51Surround = prefs.getBoolean(ENABLE_51_SURROUND_PREF_STRING, DEFAULT_ENABLE_51_SURROUND);
|
config.enable51Surround = prefs.getBoolean(ENABLE_51_SURROUND_PREF_STRING, DEFAULT_ENABLE_51_SURROUND);
|
||||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
|
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package com.limelight.preferences;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
|
||||||
import com.limelight.PcView;
|
import com.limelight.PcView;
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
@@ -60,6 +63,15 @@ public class StreamSettings extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
PreferenceScreen screen = getPreferenceScreen();
|
||||||
|
|
||||||
|
// hide on-screen controls category on non touch screen devices
|
||||||
|
if (!getActivity().getPackageManager().
|
||||||
|
hasSystemFeature("android.hardware.touchscreen")) {
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||||
|
screen.removePreference(category);
|
||||||
|
}
|
||||||
|
|
||||||
// Add a listener to the FPS and resolution preference
|
// Add a listener to the FPS and resolution preference
|
||||||
// so the bitrate can be auto-adjusted
|
// so the bitrate can be auto-adjusted
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
@@ -11,9 +12,13 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#define EVDEV_MAX_EVENT_SIZE 24
|
||||||
|
|
||||||
#define REL_X 0x00
|
#define REL_X 0x00
|
||||||
#define REL_Y 0x01
|
#define REL_Y 0x01
|
||||||
#define KEY_Q 16
|
#define KEY_Q 16
|
||||||
@@ -30,9 +35,11 @@ struct DeviceEntry {
|
|||||||
static struct DeviceEntry *DeviceListHead;
|
static struct DeviceEntry *DeviceListHead;
|
||||||
static int grabbing = 1;
|
static int grabbing = 1;
|
||||||
static pthread_mutex_t DeviceListLock = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t DeviceListLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_mutex_t SocketSendLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static int sock;
|
||||||
|
|
||||||
// This is a small executable that runs in a root shell. It reads input
|
// This is a small executable that runs in a root shell. It reads input
|
||||||
// devices and writes the evdev output packets to stdout. This allows
|
// devices and writes the evdev output packets to a socket. This allows
|
||||||
// Moonlight to read input devices without having to muck with changing
|
// Moonlight to read input devices without having to muck with changing
|
||||||
// device permissions or modifying SELinux policy (which is prevented in
|
// device permissions or modifying SELinux policy (which is prevented in
|
||||||
// Marshmallow anyway).
|
// Marshmallow anyway).
|
||||||
@@ -56,20 +63,23 @@ static int hasKey(int fd, short key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void outputEvdevData(char *data, int dataSize) {
|
static void outputEvdevData(char *data, int dataSize) {
|
||||||
// We need to lock stdout before writing to prevent
|
char packetBuffer[EVDEV_MAX_EVENT_SIZE + sizeof(dataSize)];
|
||||||
// interleaving of data between threads.
|
|
||||||
flockfile(stdout);
|
// Copy the full packet into our buffer
|
||||||
fwrite(&dataSize, sizeof(dataSize), 1, stdout);
|
memcpy(packetBuffer, &dataSize, sizeof(dataSize));
|
||||||
fwrite(data, dataSize, 1, stdout);
|
memcpy(&packetBuffer[sizeof(dataSize)], data, dataSize);
|
||||||
fflush(stdout);
|
|
||||||
funlockfile(stdout);
|
// Lock to prevent other threads from sending at the same time
|
||||||
|
pthread_mutex_lock(&SocketSendLock);
|
||||||
|
send(sock, packetBuffer, dataSize + sizeof(dataSize), 0);
|
||||||
|
pthread_mutex_unlock(&SocketSendLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* pollThreadFunc(void* context) {
|
void* pollThreadFunc(void* context) {
|
||||||
struct DeviceEntry *device = context;
|
struct DeviceEntry *device = context;
|
||||||
struct pollfd pollinfo;
|
struct pollfd pollinfo;
|
||||||
int pollres, ret;
|
int pollres, ret;
|
||||||
char data[64];
|
char data[EVDEV_MAX_EVENT_SIZE];
|
||||||
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Polling /dev/input/%s", device->devName);
|
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Polling /dev/input/%s", device->devName);
|
||||||
|
|
||||||
@@ -94,7 +104,7 @@ void* pollThreadFunc(void* context) {
|
|||||||
|
|
||||||
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
|
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
|
||||||
// We'll have data available now
|
// We'll have data available now
|
||||||
ret = read(device->fd, data, sizeof(struct input_event));
|
ret = read(device->fd, data, EVDEV_MAX_EVENT_SIZE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
|
||||||
"read() failed: %d", errno);
|
"read() failed: %d", errno);
|
||||||
@@ -132,6 +142,9 @@ cleanup:
|
|||||||
{
|
{
|
||||||
struct DeviceEntry *lastEntry;
|
struct DeviceEntry *lastEntry;
|
||||||
|
|
||||||
|
// Lock the device list
|
||||||
|
pthread_mutex_lock(&DeviceListLock);
|
||||||
|
|
||||||
if (DeviceListHead == device) {
|
if (DeviceListHead == device) {
|
||||||
DeviceListHead = device->next;
|
DeviceListHead = device->next;
|
||||||
}
|
}
|
||||||
@@ -146,6 +159,9 @@ cleanup:
|
|||||||
lastEntry = lastEntry->next;
|
lastEntry = lastEntry->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unlock device list
|
||||||
|
pthread_mutex_unlock(&DeviceListLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free the context
|
// Free the context
|
||||||
@@ -254,6 +270,11 @@ static int enumerateDevices(void) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strstr(dirEnt->d_name, "event") == NULL) {
|
||||||
|
// Skip non-event devices
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
startPollForDevice(dirEnt->d_name);
|
startPollForDevice(dirEnt->d_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +282,39 @@ static int enumerateDevices(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int connectSocket(int port) {
|
||||||
|
struct sockaddr_in saddr;
|
||||||
|
int ret;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (sock < 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "socket() failed: %d", errno);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&saddr, 0, sizeof(saddr));
|
||||||
|
saddr.sin_family = AF_INET;
|
||||||
|
saddr.sin_port = htons(port);
|
||||||
|
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||||
|
ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr));
|
||||||
|
if (ret < 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "connect() failed: %d", errno);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = 1;
|
||||||
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
|
||||||
|
if (ret < 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "setsockopt() failed: %d", errno);
|
||||||
|
// We can continue anyways
|
||||||
|
}
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Connection established to port %d", port);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define UNGRAB_REQ 1
|
#define UNGRAB_REQ 1
|
||||||
#define REGRAB_REQ 2
|
#define REGRAB_REQ 2
|
||||||
|
|
||||||
@@ -268,6 +322,18 @@ int main(int argc, char* argv[]) {
|
|||||||
int ret;
|
int ret;
|
||||||
int pollres;
|
int pollres;
|
||||||
struct pollfd pollinfo;
|
struct pollfd pollinfo;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Entered main()");
|
||||||
|
|
||||||
|
port = atoi(argv[1]);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Requested port number: %d", port);
|
||||||
|
|
||||||
|
// Connect to the app's socket
|
||||||
|
ret = connectSocket(port);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// Perform initial enumeration
|
// Perform initial enumeration
|
||||||
ret = enumerateDevices();
|
ret = enumerateDevices();
|
||||||
@@ -282,7 +348,7 @@ int main(int argc, char* argv[]) {
|
|||||||
do {
|
do {
|
||||||
// Every second we poll again for new devices if
|
// Every second we poll again for new devices if
|
||||||
// we haven't received any new events
|
// we haven't received any new events
|
||||||
pollinfo.fd = STDIN_FILENO;
|
pollinfo.fd = sock;
|
||||||
pollinfo.events = POLLIN;
|
pollinfo.events = POLLIN;
|
||||||
pollinfo.revents = 0;
|
pollinfo.revents = 0;
|
||||||
pollres = poll(&pollinfo, 1, 1000);
|
pollres = poll(&pollinfo, 1, 1000);
|
||||||
@@ -293,33 +359,52 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
while (pollres == 0);
|
while (pollres == 0);
|
||||||
|
|
||||||
ret = fread(&requestId, sizeof(requestId), 1, stdin);
|
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
|
||||||
if (ret < sizeof(requestId)) {
|
// We'll have data available now
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Short read on input");
|
ret = recv(sock, &requestId, sizeof(requestId), 0);
|
||||||
return errno;
|
if (ret < sizeof(requestId)) {
|
||||||
}
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Short read on socket");
|
||||||
|
return errno;
|
||||||
if (requestId != UNGRAB_REQ && requestId != REGRAB_REQ) {
|
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Unknown request");
|
|
||||||
return requestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
struct DeviceEntry *currentEntry;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&DeviceListLock);
|
|
||||||
|
|
||||||
// Update state for future devices
|
|
||||||
grabbing = (requestId == REGRAB_REQ);
|
|
||||||
|
|
||||||
// Carry out the requested action on each device
|
|
||||||
currentEntry = DeviceListHead;
|
|
||||||
while (currentEntry != NULL) {
|
|
||||||
ioctl(currentEntry->fd, EVIOCGRAB, grabbing);
|
|
||||||
currentEntry = currentEntry->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&DeviceListLock);
|
if (requestId != UNGRAB_REQ && requestId != REGRAB_REQ) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Unknown request");
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct DeviceEntry *currentEntry;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&DeviceListLock);
|
||||||
|
|
||||||
|
// Update state for future devices
|
||||||
|
grabbing = (requestId == REGRAB_REQ);
|
||||||
|
|
||||||
|
// Carry out the requested action on each device
|
||||||
|
currentEntry = DeviceListHead;
|
||||||
|
while (currentEntry != NULL) {
|
||||||
|
ioctl(currentEntry->fd, EVIOCGRAB, grabbing);
|
||||||
|
currentEntry = currentEntry->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&DeviceListLock);
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "New grab status is: %s",
|
||||||
|
grabbing ? "enabled" : "disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Terminate this thread
|
||||||
|
if (pollres < 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
|
||||||
|
"Socket recv poll() failed: %d", errno);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
|
||||||
|
"Socket poll unexpected revents: %d", pollinfo.revents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/configure_virtual_controller_frameLayout"
|
||||||
|
>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -106,6 +106,10 @@
|
|||||||
<string name="title_checkbox_xb1_driver">Xbox One controller driver</string>
|
<string name="title_checkbox_xb1_driver">Xbox One controller driver</string>
|
||||||
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox One controller support.</string>
|
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox One controller support.</string>
|
||||||
|
|
||||||
|
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
||||||
|
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
||||||
|
<string name="summary_checkbox_show_onscreen_controls">Show virtual controller overlay on touchscreen</string>
|
||||||
|
|
||||||
<string name="category_ui_settings">UI Settings</string>
|
<string name="category_ui_settings">UI Settings</string>
|
||||||
<string name="title_language_list">Language</string>
|
<string name="title_language_list">Language</string>
|
||||||
<string name="summary_language_list">Language to use for Moonlight</string>
|
<string name="summary_language_list">Language to use for Moonlight</string>
|
||||||
|
|||||||
@@ -51,6 +51,14 @@
|
|||||||
android:summary="@string/summary_checkbox_xb1_driver"
|
android:summary="@string/summary_checkbox_xb1_driver"
|
||||||
android:defaultValue="true" />
|
android:defaultValue="true" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
|
||||||
|
android:key="category_onscreen_controls">
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_show_onscreen_controls"
|
||||||
|
android:title="@string/title_checkbox_show_onscreen_controls"
|
||||||
|
android:summary="@string/summary_checkbox_show_onscreen_controls"
|
||||||
|
android:defaultValue="false"/>
|
||||||
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_host_settings">
|
<PreferenceCategory android:title="@string/category_host_settings">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="checkbox_enable_sops"
|
android:key="checkbox_enable_sops"
|
||||||
@@ -90,5 +98,4 @@
|
|||||||
android:summary="@string/summary_video_format"
|
android:summary="@string/summary_video_format"
|
||||||
android:defaultValue="auto" />
|
android:defaultValue="auto" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user