Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69c7b5a0d5 | |||
| d1ad3115fa | |||
| 770af402a4 | |||
| 3236c0b93a | |||
| 51aacc3f38 | |||
| 397c6f46f9 | |||
| d00f78f859 | |||
| 29fec2e0de | |||
| 88d28665ef | |||
| de1f4da258 | |||
| 7985be57ab | |||
| a835e7aaa2 | |||
| c4dc5eb9e1 | |||
| db758f386e | |||
| 3fb3eefa94 | |||
| 2d6c756e70 | |||
| 03e965d449 | |||
| 4cea483a87 |
+2
-2
@@ -11,8 +11,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 21
|
||||
|
||||
versionName "3.0.1"
|
||||
versionCode = 47
|
||||
versionName "3.0.2"
|
||||
versionCode = 48
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
|
||||
Binary file not shown.
@@ -171,6 +171,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION) != 0)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
.setMaxPacketSize(remote ? 1024 : 1292)
|
||||
.setRemote(remote)
|
||||
.build();
|
||||
|
||||
// Initialize the connection
|
||||
|
||||
@@ -527,6 +527,7 @@ public class PcView extends Activity {
|
||||
|
||||
if (details.equals(computer.details)) {
|
||||
pcGridAdapter.removeComputer(computer);
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ public class ControllerHandler {
|
||||
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||
|
||||
private Vector2d inputVector = new Vector2d();
|
||||
private Vector2d normalizedInputVector = new Vector2d();
|
||||
|
||||
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
|
||||
|
||||
@@ -236,6 +235,11 @@ public class ControllerHandler {
|
||||
mapping.isRemote = true;
|
||||
}
|
||||
}
|
||||
// NYKO Playpad has a fake hat that mimics the left stick for some reason
|
||||
else if (devName.contains("NYKO PLAYPAD")) {
|
||||
mapping.hatXAxis = -1;
|
||||
mapping.hatYAxis = -1;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Analog stick deadzone: "+mapping.leftStickDeadzoneRadius+" "+mapping.rightStickDeadzoneRadius);
|
||||
@@ -374,60 +378,40 @@ public class ControllerHandler {
|
||||
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
private Vector2d populateCachedVector(float x, float y) {
|
||||
// Reinitialize our cached Vector2d object
|
||||
inputVector.initialize(x, y);
|
||||
return inputVector;
|
||||
}
|
||||
|
||||
private Vector2d handleDeadZone(float x, float y, float deadzoneRadius) {
|
||||
// Reinitialize our cached Vector2d object
|
||||
inputVector.initialize(x, y);
|
||||
|
||||
if (inputVector.getMagnitude() <= deadzoneRadius) {
|
||||
// Deadzone -- return the zero vector
|
||||
return Vector2d.ZERO;
|
||||
private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) {
|
||||
if (stickVector.getMagnitude() <= deadzoneRadius) {
|
||||
// Deadzone
|
||||
stickVector.initialize(0, 0);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
FIXME: We're not normalizing here because we let the computer handle the deadzones.
|
||||
Normalizing can make the deadzones larger than they should be after the computer also
|
||||
evaluates the deadzone
|
||||
|
||||
// Scale the input based on the distance from the deadzone
|
||||
inputVector.getNormalized(normalizedInputVector);
|
||||
normalizedInputVector.scalarMultiply((inputVector.getMagnitude() - deadzoneRadius) / (1.0f - deadzoneRadius));
|
||||
|
||||
// Bound the X value to -1.0 to 1.0
|
||||
if (normalizedInputVector.getX() > 1.0f) {
|
||||
normalizedInputVector.setX(1.0f);
|
||||
}
|
||||
else if (normalizedInputVector.getX() < -1.0f) {
|
||||
normalizedInputVector.setX(-1.0f);
|
||||
}
|
||||
|
||||
// Bound the Y value to -1.0 to 1.0
|
||||
if (normalizedInputVector.getY() > 1.0f) {
|
||||
normalizedInputVector.setY(1.0f);
|
||||
}
|
||||
else if (normalizedInputVector.getY() < -1.0f) {
|
||||
normalizedInputVector.setY(-1.0f);
|
||||
}
|
||||
|
||||
return normalizedInputVector;
|
||||
*/
|
||||
|
||||
return inputVector;
|
||||
}
|
||||
}
|
||||
// We're not normalizing here because we let the computer handle the deadzones.
|
||||
// Normalizing can make the deadzones larger than they should be after the computer also
|
||||
// evaluates the deadzone.
|
||||
}
|
||||
|
||||
private void handleAxisSet(ControllerMapping mapping, float lsX, float lsY, float rsX,
|
||||
float rsY, float lt, float rt, float hatX, float hatY) {
|
||||
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
Vector2d leftStickVector = handleDeadZone(lsX, lsY, mapping.leftStickDeadzoneRadius);
|
||||
Vector2d leftStickVector = populateCachedVector(lsX, lsY);
|
||||
|
||||
handleDeadZone(leftStickVector, mapping.leftStickDeadzoneRadius);
|
||||
|
||||
leftStickX = (short) (leftStickVector.getX() * 0x7FFE);
|
||||
leftStickY = (short) (-leftStickVector.getY() * 0x7FFE);
|
||||
}
|
||||
|
||||
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||
Vector2d rightStickVector = handleDeadZone(rsX, rsY, mapping.rightStickDeadzoneRadius);
|
||||
Vector2d rightStickVector = populateCachedVector(rsX, rsY);
|
||||
|
||||
handleDeadZone(rightStickVector, mapping.rightStickDeadzoneRadius);
|
||||
|
||||
rightStickX = (short) (rightStickVector.getX() * 0x7FFE);
|
||||
rightStickY = (short) (-rightStickVector.getY() * 0x7FFE);
|
||||
@@ -475,32 +459,9 @@ public class ControllerHandler {
|
||||
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
|
||||
|
||||
// Replay the full history before getting the current values
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
lsX = event.getHistoricalAxisValue(mapping.leftStickXAxis, i);
|
||||
lsY = event.getHistoricalAxisValue(mapping.leftStickYAxis, i);
|
||||
}
|
||||
// We purposefully ignore the historical values in the motion event as it makes
|
||||
// the controller feel sluggish for some users.
|
||||
|
||||
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||
rsX = event.getHistoricalAxisValue(mapping.rightStickXAxis, i);
|
||||
rsY = event.getHistoricalAxisValue(mapping.rightStickYAxis, i);
|
||||
}
|
||||
|
||||
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
|
||||
lt = event.getHistoricalAxisValue(mapping.leftTriggerAxis, i);
|
||||
rt = event.getHistoricalAxisValue(mapping.rightTriggerAxis, i);
|
||||
}
|
||||
|
||||
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||
hatX = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, i);
|
||||
hatY = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, i);
|
||||
}
|
||||
|
||||
handleAxisSet(mapping, lsX, lsY, rsX, rsY, lt, rt, hatX, hatY);
|
||||
}
|
||||
|
||||
// Now handle the current set of values
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
lsX = event.getAxisValue(mapping.leftStickXAxis);
|
||||
lsY = event.getAxisValue(mapping.leftStickYAxis);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import android.os.Build;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -11,31 +11,33 @@ public class EvdevReader {
|
||||
static {
|
||||
System.loadLibrary("evdev_reader");
|
||||
}
|
||||
|
||||
public static void patchSeLinuxPolicies() {
|
||||
//
|
||||
// FIXME: We REALLY shouldn't being changing permissions on the input devices like this.
|
||||
// We should probably do something clever with a separate daemon and talk via a localhost
|
||||
// socket. We don't return the SELinux policies back to default after we're done which I feel
|
||||
// bad about, but we do chmod the input devices back so I don't think any additional attack surface
|
||||
// remains opened after streaming other than listing the /dev/input directory which you wouldn't
|
||||
// normally be able to do with SELinux enforcing on Lollipop.
|
||||
//
|
||||
// We need to modify SELinux policies to allow us to capture input devices on Lollipop and possibly other
|
||||
// more restrictive ROMs. Per Chainfire's SuperSU documentation, the supolicy binary is provided on
|
||||
// 4.4 and later to do live SELinux policy changes.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
EvdevShell shell = EvdevShell.getInstance();
|
||||
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir getattr\" " +
|
||||
"\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Requires root to chmod /dev/input/eventX
|
||||
public static boolean setPermissions(String[] files, int octalPermissions) {
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
|
||||
try {
|
||||
Process p = builder.start();
|
||||
|
||||
OutputStream stdin = p.getOutputStream();
|
||||
for (String file : files) {
|
||||
stdin.write(String.format((Locale)null, "chmod %o %s\n", octalPermissions, file).getBytes("UTF-8"));
|
||||
}
|
||||
stdin.write("exit\n".getBytes("UTF-8"));
|
||||
stdin.flush();
|
||||
|
||||
p.waitFor();
|
||||
p.destroy();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
public static void setPermissions(String[] files, int octalPermissions) {
|
||||
EvdevShell shell = EvdevShell.getInstance();
|
||||
|
||||
for (String file : files) {
|
||||
shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the fd to be passed to other function or -1 on error
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EvdevShell {
|
||||
private OutputStream stdin;
|
||||
private InputStream stdout;
|
||||
private Process shell;
|
||||
private final String uuidString = UUID.randomUUID().toString();
|
||||
|
||||
private static final EvdevShell globalShell = new EvdevShell();
|
||||
|
||||
public static EvdevShell getInstance() {
|
||||
return globalShell;
|
||||
}
|
||||
|
||||
public void startShell() {
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
|
||||
try {
|
||||
// Redirect stderr to stdout
|
||||
builder.redirectErrorStream(true);
|
||||
shell = builder.start();
|
||||
|
||||
stdin = shell.getOutputStream();
|
||||
stdout = shell.getInputStream();
|
||||
} catch (IOException e) {
|
||||
// This is unexpected
|
||||
e.printStackTrace();
|
||||
|
||||
// Kill the shell if it spawned
|
||||
if (stdin != null) {
|
||||
try {
|
||||
stdin.close();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
} finally {
|
||||
stdin = null;
|
||||
}
|
||||
}
|
||||
if (stdout != null) {
|
||||
try {
|
||||
stdout.close();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
} finally {
|
||||
stdout = null;
|
||||
}
|
||||
}
|
||||
if (shell != null) {
|
||||
shell.destroy();
|
||||
shell = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void runCommand(String command) {
|
||||
if (shell == null) {
|
||||
// Shell never started
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Write the command followed by an echo with our UUID
|
||||
stdin.write((command+'\n').getBytes("UTF-8"));
|
||||
stdin.write(("echo "+uuidString+'\n').getBytes("UTF-8"));
|
||||
stdin.flush();
|
||||
|
||||
// This is the only command in flight so we can use a scanner
|
||||
// without worrying about it eating too many characters
|
||||
Scanner scanner = new Scanner(stdout);
|
||||
while (scanner.hasNext()) {
|
||||
if (scanner.next().contains(uuidString)) {
|
||||
// Our command ran
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopShell() throws InterruptedException {
|
||||
boolean exitWritten = false;
|
||||
|
||||
if (shell == null) {
|
||||
// Shell never started
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
stdin.write("exit\n".getBytes("UTF-8"));
|
||||
exitWritten = true;
|
||||
} catch (IOException e) {
|
||||
// We'll destroy the process without
|
||||
// waiting for it to terminate since
|
||||
// we don't know whether our exit command made it
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (exitWritten) {
|
||||
try {
|
||||
shell.waitFor();
|
||||
} finally {
|
||||
shell.destroy();
|
||||
}
|
||||
}
|
||||
else {
|
||||
shell.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ public class EvdevWatcher {
|
||||
private boolean ungrabbed = false;
|
||||
private EvdevListener listener;
|
||||
private Thread startThread;
|
||||
|
||||
private static boolean patchedSeLinuxPolicies = false;
|
||||
|
||||
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
|
||||
@Override
|
||||
@@ -117,6 +119,15 @@ public class EvdevWatcher {
|
||||
startThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Initialize the root shell
|
||||
EvdevShell.getInstance().startShell();
|
||||
|
||||
// Patch SELinux policies (if needed)
|
||||
if (!patchedSeLinuxPolicies) {
|
||||
EvdevReader.patchSeLinuxPolicies();
|
||||
patchedSeLinuxPolicies = true;
|
||||
}
|
||||
|
||||
// List all files and allow us access
|
||||
File[] files = rundownWithPermissionsChange(0666);
|
||||
|
||||
@@ -139,7 +150,12 @@ public class EvdevWatcher {
|
||||
}
|
||||
|
||||
// Giveup eventX permissions
|
||||
rundownWithPermissionsChange(066);
|
||||
rundownWithPermissionsChange(0660);
|
||||
|
||||
// Kill the root shell
|
||||
try {
|
||||
EvdevShell.getInstance().stopShell();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
};
|
||||
startThread.start();
|
||||
|
||||
@@ -410,6 +410,23 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
spsBuf.position(header.offset+5);
|
||||
|
||||
SeqParameterSet sps = SeqParameterSet.read(spsBuf);
|
||||
|
||||
// Some decoders rely on H264 level to decide how many buffers are needed
|
||||
// Since we only need one frame buffered, we'll set the level as low as we can
|
||||
// for known resolution combinations
|
||||
if (initialWidth == 1280 && initialHeight == 720) {
|
||||
// Max 5 buffered frames at 1280x720x60
|
||||
LimeLog.info("Patching level_idc to 32");
|
||||
sps.level_idc = 32;
|
||||
}
|
||||
else if (initialWidth == 1920 && initialHeight == 1080) {
|
||||
// Max 4 buffered frames at 1920x1080x64
|
||||
LimeLog.info("Patching level_idc to 42");
|
||||
sps.level_idc = 42;
|
||||
}
|
||||
else {
|
||||
// Leave the profile alone (currently 5.0)
|
||||
}
|
||||
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
|
||||
// also requires this fixup.
|
||||
|
||||
@@ -355,6 +355,12 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
|
||||
ComputerDetails polledDetails;
|
||||
|
||||
// If the local address is routable across the Internet,
|
||||
// always consider this PC remote to be conservative
|
||||
if (details.localIp.equals(details.remoteIp)) {
|
||||
localFirst = false;
|
||||
}
|
||||
|
||||
if (localFirst) {
|
||||
polledDetails = tryPollIp(details, details.localIp);
|
||||
|
||||
@@ -17,7 +17,7 @@ public class PreferenceConfiguration {
|
||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||
private static final int BITRATE_DEFAULT_1080_30 = 10;
|
||||
private static final int BITRATE_DEFAULT_1080_60 = 30;
|
||||
private static final int BITRATE_DEFAULT_1080_60 = 20;
|
||||
|
||||
private static final String DEFAULT_RES_FPS = "720p60";
|
||||
private static final String DEFAULT_DECODER = "auto";
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<string name="summary_seekbar_bitrate">Abbassa il bitrate per ridurre lo stuttering; alza il bitrate per aumenteare la qualità dell\'immagine</string>
|
||||
<string name="suffix_seekbar_bitrate">Mbps</string>
|
||||
<string name="title_checkbox_stretch_video">Forza video in full-screen</string>
|
||||
<string name="title_checkbox_disable_warnings">Disabilita i messaggi di warning</string>
|
||||
<string name="title_checkbox_disable_warnings">Disabilita messaggi di warning</string>
|
||||
<string name="summary_checkbox_disable_warnings">Disabilita i messaggi di warning sullo schermo durante lo streaming</string>
|
||||
|
||||
<string name="category_gamepad_settings">Impostazioni Gamepad</string>
|
||||
@@ -102,5 +102,5 @@
|
||||
|
||||
<string name="category_advanced_settings">Impostazioni Avanzate</string>
|
||||
<string name="title_decoder_list">Cambia decoder</string>
|
||||
<string name="summary_decoder_list">Il decoder software può aumentare la latenza video quando si usano impostazioni streaming basse</string>
|
||||
<string name="summary_decoder_list">Il decoder software può ridurre la latenza video quando si usano impostazioni streaming basse</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user