mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-06-16 22:01:14 +00:00
Convert tabs to spaces
This commit is contained in:
@@ -5,21 +5,21 @@ import java.util.logging.FileHandler;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class LimeLog {
|
public class LimeLog {
|
||||||
private static final Logger LOGGER = Logger.getLogger(LimeLog.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(LimeLog.class.getName());
|
||||||
|
|
||||||
public static void info(String msg) {
|
public static void info(String msg) {
|
||||||
LOGGER.info(msg);
|
LOGGER.info(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warning(String msg) {
|
public static void warning(String msg) {
|
||||||
LOGGER.warning(msg);
|
LOGGER.warning(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void severe(String msg) {
|
public static void severe(String msg) {
|
||||||
LOGGER.severe(msg);
|
LOGGER.severe(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setFileHandler(String fileName) throws IOException {
|
public static void setFileHandler(String fileName) throws IOException {
|
||||||
LOGGER.addHandler(new FileHandler(fileName));
|
LOGGER.addHandler(new FileHandler(fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,290 +8,290 @@ import android.view.KeyEvent;
|
|||||||
* @author Cameron Gutman
|
* @author Cameron Gutman
|
||||||
*/
|
*/
|
||||||
public class KeyboardTranslator {
|
public class KeyboardTranslator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GFE's prefix for every key code
|
* GFE's prefix for every key code
|
||||||
*/
|
*/
|
||||||
private static final short KEY_PREFIX = (short) 0x80;
|
private static final short KEY_PREFIX = (short) 0x80;
|
||||||
|
|
||||||
public static final int VK_0 = 48;
|
public static final int VK_0 = 48;
|
||||||
public static final int VK_9 = 57;
|
public static final int VK_9 = 57;
|
||||||
public static final int VK_A = 65;
|
public static final int VK_A = 65;
|
||||||
public static final int VK_Z = 90;
|
public static final int VK_Z = 90;
|
||||||
public static final int VK_NUMPAD0 = 96;
|
public static final int VK_NUMPAD0 = 96;
|
||||||
public static final int VK_BACK_SLASH = 92;
|
public static final int VK_BACK_SLASH = 92;
|
||||||
public static final int VK_CAPS_LOCK = 20;
|
public static final int VK_CAPS_LOCK = 20;
|
||||||
public static final int VK_CLEAR = 12;
|
public static final int VK_CLEAR = 12;
|
||||||
public static final int VK_COMMA = 44;
|
public static final int VK_COMMA = 44;
|
||||||
public static final int VK_BACK_SPACE = 8;
|
public static final int VK_BACK_SPACE = 8;
|
||||||
public static final int VK_EQUALS = 61;
|
public static final int VK_EQUALS = 61;
|
||||||
public static final int VK_ESCAPE = 27;
|
public static final int VK_ESCAPE = 27;
|
||||||
public static final int VK_F1 = 112;
|
public static final int VK_F1 = 112;
|
||||||
public static final int VK_END = 35;
|
public static final int VK_END = 35;
|
||||||
public static final int VK_HOME = 36;
|
public static final int VK_HOME = 36;
|
||||||
public static final int VK_NUM_LOCK = 144;
|
public static final int VK_NUM_LOCK = 144;
|
||||||
public static final int VK_PAGE_UP = 33;
|
public static final int VK_PAGE_UP = 33;
|
||||||
public static final int VK_PAGE_DOWN = 34;
|
public static final int VK_PAGE_DOWN = 34;
|
||||||
public static final int VK_PLUS = 521;
|
public static final int VK_PLUS = 521;
|
||||||
public static final int VK_CLOSE_BRACKET = 93;
|
public static final int VK_CLOSE_BRACKET = 93;
|
||||||
public static final int VK_SCROLL_LOCK = 145;
|
public static final int VK_SCROLL_LOCK = 145;
|
||||||
public static final int VK_SEMICOLON = 59;
|
public static final int VK_SEMICOLON = 59;
|
||||||
public static final int VK_SLASH = 47;
|
public static final int VK_SLASH = 47;
|
||||||
public static final int VK_SPACE = 32;
|
public static final int VK_SPACE = 32;
|
||||||
public static final int VK_PRINTSCREEN = 154;
|
public static final int VK_PRINTSCREEN = 154;
|
||||||
public static final int VK_TAB = 9;
|
public static final int VK_TAB = 9;
|
||||||
public static final int VK_LEFT = 37;
|
public static final int VK_LEFT = 37;
|
||||||
public static final int VK_RIGHT = 39;
|
public static final int VK_RIGHT = 39;
|
||||||
public static final int VK_UP = 38;
|
public static final int VK_UP = 38;
|
||||||
public static final int VK_DOWN = 40;
|
public static final int VK_DOWN = 40;
|
||||||
public static final int VK_BACK_QUOTE = 192;
|
public static final int VK_BACK_QUOTE = 192;
|
||||||
public static final int VK_QUOTE = 222;
|
public static final int VK_QUOTE = 222;
|
||||||
public static final int VK_PAUSE = 19;
|
public static final int VK_PAUSE = 19;
|
||||||
|
|
||||||
public static boolean needsShift(int keycode) {
|
public static boolean needsShift(int keycode) {
|
||||||
switch (keycode)
|
switch (keycode)
|
||||||
{
|
{
|
||||||
case KeyEvent.KEYCODE_AT:
|
case KeyEvent.KEYCODE_AT:
|
||||||
case KeyEvent.KEYCODE_POUND:
|
case KeyEvent.KEYCODE_POUND:
|
||||||
case KeyEvent.KEYCODE_PLUS:
|
case KeyEvent.KEYCODE_PLUS:
|
||||||
case KeyEvent.KEYCODE_STAR:
|
case KeyEvent.KEYCODE_STAR:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given keycode and returns the GFE keycode
|
* Translates the given keycode and returns the GFE keycode
|
||||||
* @param keycode the code to be translated
|
* @param keycode the code to be translated
|
||||||
* @return a GFE keycode for the given keycode
|
* @return a GFE keycode for the given keycode
|
||||||
*/
|
*/
|
||||||
public static short translate(int keycode) {
|
public static short translate(int keycode) {
|
||||||
int translated;
|
int translated;
|
||||||
|
|
||||||
// This is a poor man's mapping between Android key codes
|
// This is a poor man's mapping between Android key codes
|
||||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||||
if (keycode >= KeyEvent.KEYCODE_0 &&
|
if (keycode >= KeyEvent.KEYCODE_0 &&
|
||||||
keycode <= KeyEvent.KEYCODE_9) {
|
keycode <= KeyEvent.KEYCODE_9) {
|
||||||
translated = (keycode - KeyEvent.KEYCODE_0) + VK_0;
|
translated = (keycode - KeyEvent.KEYCODE_0) + VK_0;
|
||||||
}
|
}
|
||||||
else if (keycode >= KeyEvent.KEYCODE_A &&
|
else if (keycode >= KeyEvent.KEYCODE_A &&
|
||||||
keycode <= KeyEvent.KEYCODE_Z) {
|
keycode <= KeyEvent.KEYCODE_Z) {
|
||||||
translated = (keycode - KeyEvent.KEYCODE_A) + VK_A;
|
translated = (keycode - KeyEvent.KEYCODE_A) + VK_A;
|
||||||
}
|
}
|
||||||
else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 &&
|
else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 &&
|
||||||
keycode <= KeyEvent.KEYCODE_NUMPAD_9) {
|
keycode <= KeyEvent.KEYCODE_NUMPAD_9) {
|
||||||
translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0;
|
translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0;
|
||||||
}
|
}
|
||||||
else if (keycode >= KeyEvent.KEYCODE_F1 &&
|
else if (keycode >= KeyEvent.KEYCODE_F1 &&
|
||||||
keycode <= KeyEvent.KEYCODE_F12) {
|
keycode <= KeyEvent.KEYCODE_F12) {
|
||||||
translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1;
|
translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case KeyEvent.KEYCODE_ALT_LEFT:
|
case KeyEvent.KEYCODE_ALT_LEFT:
|
||||||
translated = 0xA4;
|
translated = 0xA4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_ALT_RIGHT:
|
case KeyEvent.KEYCODE_ALT_RIGHT:
|
||||||
translated = 0xA5;
|
translated = 0xA5;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_BACKSLASH:
|
case KeyEvent.KEYCODE_BACKSLASH:
|
||||||
translated = 0xdc;
|
translated = 0xdc;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_CAPS_LOCK:
|
case KeyEvent.KEYCODE_CAPS_LOCK:
|
||||||
translated = VK_CAPS_LOCK;
|
translated = VK_CAPS_LOCK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_CLEAR:
|
case KeyEvent.KEYCODE_CLEAR:
|
||||||
translated = VK_CLEAR;
|
translated = VK_CLEAR;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_COMMA:
|
case KeyEvent.KEYCODE_COMMA:
|
||||||
translated = 0xbc;
|
translated = 0xbc;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_CTRL_LEFT:
|
case KeyEvent.KEYCODE_CTRL_LEFT:
|
||||||
translated = 0xA2;
|
translated = 0xA2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_CTRL_RIGHT:
|
case KeyEvent.KEYCODE_CTRL_RIGHT:
|
||||||
translated = 0xA3;
|
translated = 0xA3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_DEL:
|
case KeyEvent.KEYCODE_DEL:
|
||||||
translated = VK_BACK_SPACE;
|
translated = VK_BACK_SPACE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_ENTER:
|
case KeyEvent.KEYCODE_ENTER:
|
||||||
translated = 0x0d;
|
translated = 0x0d;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_PLUS:
|
case KeyEvent.KEYCODE_PLUS:
|
||||||
case KeyEvent.KEYCODE_EQUALS:
|
case KeyEvent.KEYCODE_EQUALS:
|
||||||
translated = 0xbb;
|
translated = 0xbb;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_ESCAPE:
|
case KeyEvent.KEYCODE_ESCAPE:
|
||||||
translated = VK_ESCAPE;
|
translated = VK_ESCAPE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_FORWARD_DEL:
|
case KeyEvent.KEYCODE_FORWARD_DEL:
|
||||||
translated = 0x2e;
|
translated = 0x2e;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_INSERT:
|
case KeyEvent.KEYCODE_INSERT:
|
||||||
translated = 0x2d;
|
translated = 0x2d;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_LEFT_BRACKET:
|
case KeyEvent.KEYCODE_LEFT_BRACKET:
|
||||||
translated = 0xdb;
|
translated = 0xdb;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_META_LEFT:
|
case KeyEvent.KEYCODE_META_LEFT:
|
||||||
translated = 0x5b;
|
translated = 0x5b;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_META_RIGHT:
|
case KeyEvent.KEYCODE_META_RIGHT:
|
||||||
translated = 0x5c;
|
translated = 0x5c;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_MINUS:
|
case KeyEvent.KEYCODE_MINUS:
|
||||||
translated = 0xbd;
|
translated = 0xbd;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_MOVE_END:
|
case KeyEvent.KEYCODE_MOVE_END:
|
||||||
translated = VK_END;
|
translated = VK_END;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_MOVE_HOME:
|
case KeyEvent.KEYCODE_MOVE_HOME:
|
||||||
translated = VK_HOME;
|
translated = VK_HOME;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUM_LOCK:
|
case KeyEvent.KEYCODE_NUM_LOCK:
|
||||||
translated = VK_NUM_LOCK;
|
translated = VK_NUM_LOCK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_PAGE_DOWN:
|
case KeyEvent.KEYCODE_PAGE_DOWN:
|
||||||
translated = VK_PAGE_DOWN;
|
translated = VK_PAGE_DOWN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_PAGE_UP:
|
case KeyEvent.KEYCODE_PAGE_UP:
|
||||||
translated = VK_PAGE_UP;
|
translated = VK_PAGE_UP;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_PERIOD:
|
case KeyEvent.KEYCODE_PERIOD:
|
||||||
translated = 0xbe;
|
translated = 0xbe;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_RIGHT_BRACKET:
|
case KeyEvent.KEYCODE_RIGHT_BRACKET:
|
||||||
translated = 0xdd;
|
translated = 0xdd;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SCROLL_LOCK:
|
case KeyEvent.KEYCODE_SCROLL_LOCK:
|
||||||
translated = VK_SCROLL_LOCK;
|
translated = VK_SCROLL_LOCK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SEMICOLON:
|
case KeyEvent.KEYCODE_SEMICOLON:
|
||||||
translated = 0xba;
|
translated = 0xba;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SHIFT_LEFT:
|
case KeyEvent.KEYCODE_SHIFT_LEFT:
|
||||||
translated = 0xA0;
|
translated = 0xA0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SHIFT_RIGHT:
|
case KeyEvent.KEYCODE_SHIFT_RIGHT:
|
||||||
translated = 0xA1;
|
translated = 0xA1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SLASH:
|
case KeyEvent.KEYCODE_SLASH:
|
||||||
translated = 0xbf;
|
translated = 0xbf;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SPACE:
|
case KeyEvent.KEYCODE_SPACE:
|
||||||
translated = VK_SPACE;
|
translated = VK_SPACE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SYSRQ:
|
case KeyEvent.KEYCODE_SYSRQ:
|
||||||
// Android defines this as SysRq/PrntScrn
|
// Android defines this as SysRq/PrntScrn
|
||||||
translated = VK_PRINTSCREEN;
|
translated = VK_PRINTSCREEN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_TAB:
|
case KeyEvent.KEYCODE_TAB:
|
||||||
translated = VK_TAB;
|
translated = VK_TAB;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||||
translated = VK_LEFT;
|
translated = VK_LEFT;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||||
translated = VK_RIGHT;
|
translated = VK_RIGHT;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_DPAD_UP:
|
case KeyEvent.KEYCODE_DPAD_UP:
|
||||||
translated = VK_UP;
|
translated = VK_UP;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||||
translated = VK_DOWN;
|
translated = VK_DOWN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_GRAVE:
|
case KeyEvent.KEYCODE_GRAVE:
|
||||||
translated = VK_BACK_QUOTE;
|
translated = VK_BACK_QUOTE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_APOSTROPHE:
|
case KeyEvent.KEYCODE_APOSTROPHE:
|
||||||
translated = 0xde;
|
translated = 0xde;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_BREAK:
|
case KeyEvent.KEYCODE_BREAK:
|
||||||
translated = VK_PAUSE;
|
translated = VK_PAUSE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUMPAD_DIVIDE:
|
case KeyEvent.KEYCODE_NUMPAD_DIVIDE:
|
||||||
translated = 0x6F;
|
translated = 0x6F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUMPAD_MULTIPLY:
|
case KeyEvent.KEYCODE_NUMPAD_MULTIPLY:
|
||||||
translated = 0x6A;
|
translated = 0x6A;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUMPAD_SUBTRACT:
|
case KeyEvent.KEYCODE_NUMPAD_SUBTRACT:
|
||||||
translated = 0x6D;
|
translated = 0x6D;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUMPAD_ADD:
|
case KeyEvent.KEYCODE_NUMPAD_ADD:
|
||||||
translated = 0x6B;
|
translated = 0x6B;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_NUMPAD_DOT:
|
case KeyEvent.KEYCODE_NUMPAD_DOT:
|
||||||
translated = 0x6E;
|
translated = 0x6E;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_AT:
|
case KeyEvent.KEYCODE_AT:
|
||||||
translated = 2 + VK_0;
|
translated = 2 + VK_0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_POUND:
|
case KeyEvent.KEYCODE_POUND:
|
||||||
translated = 3 + VK_0;
|
translated = 3 + VK_0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_STAR:
|
case KeyEvent.KEYCODE_STAR:
|
||||||
translated = 8 + VK_0;
|
translated = 8 + VK_0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
System.out.println("No key for "+keycode);
|
System.out.println("No key for "+keycode);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (short) ((KEY_PREFIX << 8) | translated);
|
return (short) ((KEY_PREFIX << 8) | translated);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -306,7 +306,7 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
prefEditor.apply();
|
prefEditor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadFromPreferences(final VirtualController controller, final Context context) {
|
public static void loadFromPreferences(final VirtualController controller, final Context context) {
|
||||||
SharedPreferences pref = context.getSharedPreferences(OSC_PREFERENCE, Activity.MODE_PRIVATE);
|
SharedPreferences pref = context.getSharedPreferences(OSC_PREFERENCE, Activity.MODE_PRIVATE);
|
||||||
|
|
||||||
for (VirtualControllerElement element : controller.getElements()) {
|
for (VirtualControllerElement element : controller.getElements()) {
|
||||||
@@ -324,5 +324,5 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+27
-27
@@ -112,34 +112,34 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
protected void actionShowNormalColorChooser() {
|
protected void actionShowNormalColorChooser() {
|
||||||
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(AmbilWarnaDialog dialog)
|
public void onCancel(AmbilWarnaDialog dialog)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOk(AmbilWarnaDialog dialog, int color) {
|
public void onOk(AmbilWarnaDialog dialog, int color) {
|
||||||
normalColor = color;
|
normalColor = color;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
colorDialog.show();
|
colorDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void actionShowPressedColorChooser() {
|
protected void actionShowPressedColorChooser() {
|
||||||
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
AmbilWarnaDialog colorDialog = new AmbilWarnaDialog(getContext(), normalColor, true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(AmbilWarnaDialog dialog) {
|
public void onCancel(AmbilWarnaDialog dialog) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOk(AmbilWarnaDialog dialog, int color) {
|
public void onOk(AmbilWarnaDialog dialog, int color) {
|
||||||
pressedColor = color;
|
pressedColor = color;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
colorDialog.show();
|
colorDialog.show();
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected void actionEnableMove() {
|
protected void actionEnableMove() {
|
||||||
@@ -195,11 +195,11 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
case 2: { // set default color
|
case 2: { // set default color
|
||||||
actionShowNormalColorChooser();
|
actionShowNormalColorChooser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: { // set pressed color
|
case 3: { // set pressed color
|
||||||
actionShowPressedColorChooser();
|
actionShowPressedColorChooser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,20 +5,20 @@ import java.security.cert.X509Certificate;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
public class ConnectionContext {
|
public class ConnectionContext {
|
||||||
public String serverAddress;
|
public String serverAddress;
|
||||||
public X509Certificate serverCert;
|
public X509Certificate serverCert;
|
||||||
public StreamConfiguration streamConfig;
|
public StreamConfiguration streamConfig;
|
||||||
public NvConnectionListener connListener;
|
public NvConnectionListener connListener;
|
||||||
public SecretKey riKey;
|
public SecretKey riKey;
|
||||||
public int riKeyId;
|
public int riKeyId;
|
||||||
|
|
||||||
// This is the version quad from the appversion tag of /serverinfo
|
// This is the version quad from the appversion tag of /serverinfo
|
||||||
public String serverAppVersion;
|
public String serverAppVersion;
|
||||||
public String serverGfeVersion;
|
public String serverGfeVersion;
|
||||||
|
|
||||||
public int negotiatedWidth, negotiatedHeight;
|
public int negotiatedWidth, negotiatedHeight;
|
||||||
public int negotiatedFps;
|
public int negotiatedFps;
|
||||||
public boolean negotiatedHdr;
|
public boolean negotiatedHdr;
|
||||||
|
|
||||||
public int videoCapabilities;
|
public int videoCapabilities;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,320 +26,320 @@ import com.limelight.nvstream.input.MouseButtonPacket;
|
|||||||
import com.limelight.nvstream.jni.MoonBridge;
|
import com.limelight.nvstream.jni.MoonBridge;
|
||||||
|
|
||||||
public class NvConnection {
|
public class NvConnection {
|
||||||
// Context parameters
|
// Context parameters
|
||||||
private String host;
|
private String host;
|
||||||
private LimelightCryptoProvider cryptoProvider;
|
private LimelightCryptoProvider cryptoProvider;
|
||||||
private String uniqueId;
|
private String uniqueId;
|
||||||
private ConnectionContext context;
|
private ConnectionContext context;
|
||||||
private static Semaphore connectionAllowed = new Semaphore(1);
|
private static Semaphore connectionAllowed = new Semaphore(1);
|
||||||
private final boolean isMonkey;
|
private final boolean isMonkey;
|
||||||
|
|
||||||
public NvConnection(String host, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert)
|
public NvConnection(String host, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert)
|
||||||
{
|
{
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.cryptoProvider = cryptoProvider;
|
this.cryptoProvider = cryptoProvider;
|
||||||
this.uniqueId = uniqueId;
|
this.uniqueId = uniqueId;
|
||||||
|
|
||||||
this.context = new ConnectionContext();
|
this.context = new ConnectionContext();
|
||||||
this.context.streamConfig = config;
|
this.context.streamConfig = config;
|
||||||
this.context.serverCert = serverCert;
|
this.context.serverCert = serverCert;
|
||||||
try {
|
try {
|
||||||
// This is unique per connection
|
// This is unique per connection
|
||||||
this.context.riKey = generateRiAesKey();
|
this.context.riKey = generateRiAesKey();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context.riKeyId = generateRiKeyId();
|
this.context.riKeyId = generateRiKeyId();
|
||||||
this.isMonkey = ActivityManager.isUserAMonkey();
|
this.isMonkey = ActivityManager.isUserAMonkey();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SecretKey generateRiAesKey() throws NoSuchAlgorithmException {
|
private static SecretKey generateRiAesKey() throws NoSuchAlgorithmException {
|
||||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||||
|
|
||||||
// RI keys are 128 bits
|
// RI keys are 128 bits
|
||||||
keyGen.init(128);
|
keyGen.init(128);
|
||||||
|
|
||||||
return keyGen.generateKey();
|
return keyGen.generateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int generateRiKeyId() {
|
private static int generateRiKeyId() {
|
||||||
return new SecureRandom().nextInt();
|
return new SecureRandom().nextInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
// Interrupt any pending connection. This is thread-safe.
|
// Interrupt any pending connection. This is thread-safe.
|
||||||
MoonBridge.interruptConnection();
|
MoonBridge.interruptConnection();
|
||||||
|
|
||||||
// Moonlight-core is not thread-safe with respect to connection start and stop, so
|
// Moonlight-core is not thread-safe with respect to connection start and stop, so
|
||||||
// we must not invoke that functionality in parallel.
|
// we must not invoke that functionality in parallel.
|
||||||
synchronized (MoonBridge.class) {
|
synchronized (MoonBridge.class) {
|
||||||
MoonBridge.stopConnection();
|
MoonBridge.stopConnection();
|
||||||
MoonBridge.cleanupBridge();
|
MoonBridge.cleanupBridge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now a pending connection can be processed
|
// Now a pending connection can be processed
|
||||||
connectionAllowed.release();
|
connectionAllowed.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean startApp() throws XmlPullParserException, IOException
|
private boolean startApp() throws XmlPullParserException, IOException
|
||||||
{
|
{
|
||||||
NvHTTP h = new NvHTTP(context.serverAddress, uniqueId, context.serverCert, cryptoProvider);
|
NvHTTP h = new NvHTTP(context.serverAddress, uniqueId, context.serverCert, cryptoProvider);
|
||||||
|
|
||||||
String serverInfo = h.getServerInfo();
|
String serverInfo = h.getServerInfo();
|
||||||
|
|
||||||
context.serverAppVersion = h.getServerVersion(serverInfo);
|
context.serverAppVersion = h.getServerVersion(serverInfo);
|
||||||
if (context.serverAppVersion == null) {
|
if (context.serverAppVersion == null) {
|
||||||
context.connListener.displayMessage("Server version malformed");
|
context.connListener.displayMessage("Server version malformed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// May be missing for older servers
|
// May be missing for older servers
|
||||||
context.serverGfeVersion = h.getGfeVersion(serverInfo);
|
context.serverGfeVersion = h.getGfeVersion(serverInfo);
|
||||||
|
|
||||||
if (h.getPairState(serverInfo) != PairingManager.PairState.PAIRED) {
|
if (h.getPairState(serverInfo) != PairingManager.PairState.PAIRED) {
|
||||||
context.connListener.displayMessage("Device not paired with computer");
|
context.connListener.displayMessage("Device not paired with computer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.negotiatedHdr = context.streamConfig.getEnableHdr();
|
context.negotiatedHdr = context.streamConfig.getEnableHdr();
|
||||||
if ((h.getServerCodecModeSupport(serverInfo) & 0x200) == 0 && context.negotiatedHdr) {
|
if ((h.getServerCodecModeSupport(serverInfo) & 0x200) == 0 && context.negotiatedHdr) {
|
||||||
context.connListener.displayTransientMessage("Your GPU does not support streaming HDR. The stream will be SDR.");
|
context.connListener.displayTransientMessage("Your GPU does not support streaming HDR. The stream will be SDR.");
|
||||||
context.negotiatedHdr = false;
|
context.negotiatedHdr = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Decide on negotiated stream parameters now
|
// Decide on negotiated stream parameters now
|
||||||
//
|
//
|
||||||
|
|
||||||
// Check for a supported stream resolution
|
// Check for a supported stream resolution
|
||||||
if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||||
// Client wants 4K but the server can't do it
|
// Client wants 4K but the server can't do it
|
||||||
context.connListener.displayTransientMessage("You must update GeForce Experience to stream in 4K. The stream will be 1080p.");
|
context.connListener.displayTransientMessage("You must update GeForce Experience to stream in 4K. The stream will be 1080p.");
|
||||||
|
|
||||||
// Lower resolution to 1080p
|
// Lower resolution to 1080p
|
||||||
context.negotiatedWidth = 1920;
|
context.negotiatedWidth = 1920;
|
||||||
context.negotiatedHeight = 1080;
|
context.negotiatedHeight = 1080;
|
||||||
context.negotiatedFps = context.streamConfig.getRefreshRate();
|
context.negotiatedFps = context.streamConfig.getRefreshRate();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Take what the client wanted
|
// Take what the client wanted
|
||||||
context.negotiatedWidth = context.streamConfig.getWidth();
|
context.negotiatedWidth = context.streamConfig.getWidth();
|
||||||
context.negotiatedHeight = context.streamConfig.getHeight();
|
context.negotiatedHeight = context.streamConfig.getHeight();
|
||||||
context.negotiatedFps = context.streamConfig.getRefreshRate();
|
context.negotiatedFps = context.streamConfig.getRefreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Video stream format will be decided during the RTSP handshake
|
// Video stream format will be decided during the RTSP handshake
|
||||||
//
|
//
|
||||||
|
|
||||||
NvApp app = context.streamConfig.getApp();
|
NvApp app = context.streamConfig.getApp();
|
||||||
|
|
||||||
// If the client did not provide an exact app ID, do a lookup with the applist
|
// If the client did not provide an exact app ID, do a lookup with the applist
|
||||||
if (!context.streamConfig.getApp().isInitialized()) {
|
if (!context.streamConfig.getApp().isInitialized()) {
|
||||||
LimeLog.info("Using deprecated app lookup method - Please specify an app ID in your StreamConfiguration instead");
|
LimeLog.info("Using deprecated app lookup method - Please specify an app ID in your StreamConfiguration instead");
|
||||||
app = h.getAppByName(context.streamConfig.getApp().getAppName());
|
app = h.getAppByName(context.streamConfig.getApp().getAppName());
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
context.connListener.displayMessage("The app " + context.streamConfig.getApp().getAppName() + " is not in GFE app list");
|
context.connListener.displayMessage("The app " + context.streamConfig.getApp().getAppName() + " is not in GFE app list");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a game running, resume it
|
// If there's a game running, resume it
|
||||||
if (h.getCurrentGame(serverInfo) != 0) {
|
if (h.getCurrentGame(serverInfo) != 0) {
|
||||||
try {
|
try {
|
||||||
if (h.getCurrentGame(serverInfo) == app.getAppId()) {
|
if (h.getCurrentGame(serverInfo) == app.getAppId()) {
|
||||||
if (!h.resumeApp(context)) {
|
if (!h.resumeApp(context)) {
|
||||||
context.connListener.displayMessage("Failed to resume existing session");
|
context.connListener.displayMessage("Failed to resume existing session");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return quitAndLaunch(h, context);
|
return quitAndLaunch(h, context);
|
||||||
}
|
}
|
||||||
} catch (GfeHttpResponseException e) {
|
} catch (GfeHttpResponseException e) {
|
||||||
if (e.getErrorCode() == 470) {
|
if (e.getErrorCode() == 470) {
|
||||||
// This is the error you get when you try to resume a session that's not yours.
|
// This is the error you get when you try to resume a session that's not yours.
|
||||||
// Because this is fairly common, we'll display a more detailed message.
|
// Because this is fairly common, we'll display a more detailed message.
|
||||||
context.connListener.displayMessage("This session wasn't started by this device," +
|
context.connListener.displayMessage("This session wasn't started by this device," +
|
||||||
" so it cannot be resumed. End streaming on the original " +
|
" so it cannot be resumed. End streaming on the original " +
|
||||||
"device or the PC itself and try again. (Error code: "+e.getErrorCode()+")");
|
"device or the PC itself and try again. (Error code: "+e.getErrorCode()+")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (e.getErrorCode() == 525) {
|
else if (e.getErrorCode() == 525) {
|
||||||
context.connListener.displayMessage("The application is minimized. Resume it on the PC manually or " +
|
context.connListener.displayMessage("The application is minimized. Resume it on the PC manually or " +
|
||||||
"quit the session and start streaming again.");
|
"quit the session and start streaming again.");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Resumed existing game session");
|
LimeLog.info("Resumed existing game session");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return launchNotRunningApp(h, context);
|
return launchNotRunningApp(h, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean quitAndLaunch(NvHTTP h, ConnectionContext context) throws IOException,
|
protected boolean quitAndLaunch(NvHTTP h, ConnectionContext context) throws IOException,
|
||||||
XmlPullParserException {
|
XmlPullParserException {
|
||||||
try {
|
try {
|
||||||
if (!h.quitApp()) {
|
if (!h.quitApp()) {
|
||||||
context.connListener.displayMessage("Failed to quit previous session! You must quit it manually");
|
context.connListener.displayMessage("Failed to quit previous session! You must quit it manually");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (GfeHttpResponseException e) {
|
} catch (GfeHttpResponseException e) {
|
||||||
if (e.getErrorCode() == 599) {
|
if (e.getErrorCode() == 599) {
|
||||||
context.connListener.displayMessage("This session wasn't started by this device," +
|
context.connListener.displayMessage("This session wasn't started by this device," +
|
||||||
" so it cannot be quit. End streaming on the original " +
|
" so it cannot be quit. End streaming on the original " +
|
||||||
"device or the PC itself. (Error code: "+e.getErrorCode()+")");
|
"device or the PC itself. (Error code: "+e.getErrorCode()+")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return launchNotRunningApp(h, context);
|
return launchNotRunningApp(h, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context)
|
private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
// Launch the app since it's not running
|
// Launch the app since it's not running
|
||||||
if (!h.launchApp(context, context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
if (!h.launchApp(context, context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
||||||
context.connListener.displayMessage("Failed to launch application");
|
context.connListener.displayMessage("Failed to launch application");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Launched new game session");
|
LimeLog.info("Launched new game session");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(final AudioRenderer audioRenderer, final VideoDecoderRenderer videoDecoderRenderer, final NvConnectionListener connectionListener)
|
public void start(final AudioRenderer audioRenderer, final VideoDecoderRenderer videoDecoderRenderer, final NvConnectionListener connectionListener)
|
||||||
{
|
{
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
context.connListener = connectionListener;
|
context.connListener = connectionListener;
|
||||||
context.videoCapabilities = videoDecoderRenderer.getCapabilities();
|
context.videoCapabilities = videoDecoderRenderer.getCapabilities();
|
||||||
|
|
||||||
String appName = context.streamConfig.getApp().getAppName();
|
String appName = context.streamConfig.getApp().getAppName();
|
||||||
|
|
||||||
context.serverAddress = host;
|
context.serverAddress = host;
|
||||||
context.connListener.stageStarting(appName);
|
context.connListener.stageStarting(appName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!startApp()) {
|
if (!startApp()) {
|
||||||
context.connListener.stageFailed(appName, 0);
|
context.connListener.stageFailed(appName, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.connListener.stageComplete(appName);
|
context.connListener.stageComplete(appName);
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
context.connListener.displayMessage(e.getMessage());
|
context.connListener.displayMessage(e.getMessage());
|
||||||
context.connListener.stageFailed(appName, 0);
|
context.connListener.stageFailed(appName, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer ib = ByteBuffer.allocate(16);
|
ByteBuffer ib = ByteBuffer.allocate(16);
|
||||||
ib.putInt(context.riKeyId);
|
ib.putInt(context.riKeyId);
|
||||||
|
|
||||||
// Acquire the connection semaphore to ensure we only have one
|
// Acquire the connection semaphore to ensure we only have one
|
||||||
// connection going at once.
|
// connection going at once.
|
||||||
try {
|
try {
|
||||||
connectionAllowed.acquire();
|
connectionAllowed.acquire();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
context.connListener.displayMessage(e.getMessage());
|
context.connListener.displayMessage(e.getMessage());
|
||||||
context.connListener.stageFailed(appName, 0);
|
context.connListener.stageFailed(appName, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Moonlight-core is not thread-safe with respect to connection start and stop, so
|
// Moonlight-core is not thread-safe with respect to connection start and stop, so
|
||||||
// we must not invoke that functionality in parallel.
|
// we must not invoke that functionality in parallel.
|
||||||
synchronized (MoonBridge.class) {
|
synchronized (MoonBridge.class) {
|
||||||
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
||||||
int ret = MoonBridge.startConnection(context.serverAddress,
|
int ret = MoonBridge.startConnection(context.serverAddress,
|
||||||
context.serverAppVersion, context.serverGfeVersion,
|
context.serverAppVersion, context.serverGfeVersion,
|
||||||
context.negotiatedWidth, context.negotiatedHeight,
|
context.negotiatedWidth, context.negotiatedHeight,
|
||||||
context.negotiatedFps, context.streamConfig.getBitrate(),
|
context.negotiatedFps, context.streamConfig.getBitrate(),
|
||||||
context.streamConfig.getMaxPacketSize(),
|
context.streamConfig.getMaxPacketSize(),
|
||||||
context.streamConfig.getRemote(), context.streamConfig.getAudioConfiguration(),
|
context.streamConfig.getRemote(), context.streamConfig.getAudioConfiguration(),
|
||||||
context.streamConfig.getHevcSupported(),
|
context.streamConfig.getHevcSupported(),
|
||||||
context.negotiatedHdr,
|
context.negotiatedHdr,
|
||||||
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
||||||
context.streamConfig.getClientRefreshRateX100(),
|
context.streamConfig.getClientRefreshRateX100(),
|
||||||
context.riKey.getEncoded(), ib.array(),
|
context.riKey.getEncoded(), ib.array(),
|
||||||
context.videoCapabilities);
|
context.videoCapabilities);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
// LiStartConnection() failed, so the caller is not expected
|
// LiStartConnection() failed, so the caller is not expected
|
||||||
// to stop the connection themselves. We need to release their
|
// to stop the connection themselves. We need to release their
|
||||||
// semaphore count for them.
|
// semaphore count for them.
|
||||||
connectionAllowed.release();
|
connectionAllowed.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMouseMove(final short deltaX, final short deltaY)
|
public void sendMouseMove(final short deltaX, final short deltaY)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendMouseMove(deltaX, deltaY);
|
MoonBridge.sendMouseMove(deltaX, deltaY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMouseButtonDown(final byte mouseButton)
|
public void sendMouseButtonDown(final byte mouseButton)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendMouseButton(MouseButtonPacket.PRESS_EVENT, mouseButton);
|
MoonBridge.sendMouseButton(MouseButtonPacket.PRESS_EVENT, mouseButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMouseButtonUp(final byte mouseButton)
|
public void sendMouseButtonUp(final byte mouseButton)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendMouseButton(MouseButtonPacket.RELEASE_EVENT, mouseButton);
|
MoonBridge.sendMouseButton(MouseButtonPacket.RELEASE_EVENT, mouseButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendControllerInput(final short controllerNumber,
|
public void sendControllerInput(final short controllerNumber,
|
||||||
final short activeGamepadMask, final short buttonFlags,
|
final short activeGamepadMask, final short buttonFlags,
|
||||||
final byte leftTrigger, final byte rightTrigger,
|
final byte leftTrigger, final byte rightTrigger,
|
||||||
final short leftStickX, final short leftStickY,
|
final short leftStickX, final short leftStickY,
|
||||||
final short rightStickX, final short rightStickY)
|
final short rightStickX, final short rightStickY)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendMultiControllerInput(controllerNumber, activeGamepadMask, buttonFlags,
|
MoonBridge.sendMultiControllerInput(controllerNumber, activeGamepadMask, buttonFlags,
|
||||||
leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY);
|
leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendControllerInput(final short buttonFlags,
|
public void sendControllerInput(final short buttonFlags,
|
||||||
final byte leftTrigger, final byte rightTrigger,
|
final byte leftTrigger, final byte rightTrigger,
|
||||||
final short leftStickX, final short leftStickY,
|
final short leftStickX, final short leftStickY,
|
||||||
final short rightStickX, final short rightStickY)
|
final short rightStickX, final short rightStickY)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendControllerInput(buttonFlags, leftTrigger, rightTrigger, leftStickX,
|
MoonBridge.sendControllerInput(buttonFlags, leftTrigger, rightTrigger, leftStickX,
|
||||||
leftStickY, rightStickX, rightStickY);
|
leftStickY, rightStickX, rightStickY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendKeyboardInput(final short keyMap, final byte keyDirection, final byte modifier) {
|
public void sendKeyboardInput(final short keyMap, final byte keyDirection, final byte modifier) {
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendKeyboardInput(keyMap, keyDirection, modifier);
|
MoonBridge.sendKeyboardInput(keyMap, keyDirection, modifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMouseScroll(final byte scrollClicks) {
|
public void sendMouseScroll(final byte scrollClicks) {
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
MoonBridge.sendMouseScroll(scrollClicks);
|
MoonBridge.sendMouseScroll(scrollClicks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
||||||
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package com.limelight.nvstream;
|
package com.limelight.nvstream;
|
||||||
|
|
||||||
public interface NvConnectionListener {
|
public interface NvConnectionListener {
|
||||||
void stageStarting(String stage);
|
void stageStarting(String stage);
|
||||||
void stageComplete(String stage);
|
void stageComplete(String stage);
|
||||||
void stageFailed(String stage, long errorCode);
|
void stageFailed(String stage, long errorCode);
|
||||||
|
|
||||||
void connectionStarted();
|
void connectionStarted();
|
||||||
void connectionTerminated(long errorCode);
|
void connectionTerminated(long errorCode);
|
||||||
void connectionStatusUpdate(int connectionStatus);
|
void connectionStatusUpdate(int connectionStatus);
|
||||||
|
|
||||||
void displayMessage(String message);
|
void displayMessage(String message);
|
||||||
void displayTransientMessage(String message);
|
void displayTransientMessage(String message);
|
||||||
|
|
||||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,230 +4,230 @@ import com.limelight.nvstream.http.NvApp;
|
|||||||
import com.limelight.nvstream.jni.MoonBridge;
|
import com.limelight.nvstream.jni.MoonBridge;
|
||||||
|
|
||||||
public class StreamConfiguration {
|
public class StreamConfiguration {
|
||||||
public static final int INVALID_APP_ID = 0;
|
public static final int INVALID_APP_ID = 0;
|
||||||
|
|
||||||
public static final int STREAM_CFG_LOCAL = 0;
|
public static final int STREAM_CFG_LOCAL = 0;
|
||||||
public static final int STREAM_CFG_REMOTE = 1;
|
public static final int STREAM_CFG_REMOTE = 1;
|
||||||
public static final int STREAM_CFG_AUTO = 2;
|
public static final int STREAM_CFG_AUTO = 2;
|
||||||
|
|
||||||
private static final int CHANNEL_COUNT_STEREO = 2;
|
private static final int CHANNEL_COUNT_STEREO = 2;
|
||||||
private static final int CHANNEL_COUNT_5_1 = 6;
|
private static final int CHANNEL_COUNT_5_1 = 6;
|
||||||
|
|
||||||
private static final int CHANNEL_MASK_STEREO = 0x3;
|
private static final int CHANNEL_MASK_STEREO = 0x3;
|
||||||
private static final int CHANNEL_MASK_5_1 = 0xFC;
|
private static final int CHANNEL_MASK_5_1 = 0xFC;
|
||||||
|
|
||||||
private NvApp app;
|
private NvApp app;
|
||||||
private int width, height;
|
private int width, height;
|
||||||
private int refreshRate;
|
private int refreshRate;
|
||||||
private int clientRefreshRateX100;
|
private int clientRefreshRateX100;
|
||||||
private int bitrate;
|
private int bitrate;
|
||||||
private boolean sops;
|
private boolean sops;
|
||||||
private boolean enableAdaptiveResolution;
|
private boolean enableAdaptiveResolution;
|
||||||
private boolean playLocalAudio;
|
private boolean playLocalAudio;
|
||||||
private int maxPacketSize;
|
private int maxPacketSize;
|
||||||
private int remote;
|
private int remote;
|
||||||
private int audioChannelMask;
|
private int audioChannelMask;
|
||||||
private int audioChannelCount;
|
private int audioChannelCount;
|
||||||
private int audioConfiguration;
|
private int audioConfiguration;
|
||||||
private boolean supportsHevc;
|
private boolean supportsHevc;
|
||||||
private int hevcBitratePercentageMultiplier;
|
private int hevcBitratePercentageMultiplier;
|
||||||
private boolean enableHdr;
|
private boolean enableHdr;
|
||||||
private int attachedGamepadMask;
|
private int attachedGamepadMask;
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private StreamConfiguration config = new StreamConfiguration();
|
private StreamConfiguration config = new StreamConfiguration();
|
||||||
|
|
||||||
public StreamConfiguration.Builder setApp(NvApp app) {
|
public StreamConfiguration.Builder setApp(NvApp app) {
|
||||||
config.app = app;
|
config.app = app;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setRemoteConfiguration(int remote) {
|
public StreamConfiguration.Builder setRemoteConfiguration(int remote) {
|
||||||
config.remote = remote;
|
config.remote = remote;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setResolution(int width, int height) {
|
public StreamConfiguration.Builder setResolution(int width, int height) {
|
||||||
config.width = width;
|
config.width = width;
|
||||||
config.height = height;
|
config.height = height;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setRefreshRate(int refreshRate) {
|
public StreamConfiguration.Builder setRefreshRate(int refreshRate) {
|
||||||
config.refreshRate = refreshRate;
|
config.refreshRate = refreshRate;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setBitrate(int bitrate) {
|
public StreamConfiguration.Builder setBitrate(int bitrate) {
|
||||||
config.bitrate = bitrate;
|
config.bitrate = bitrate;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setEnableSops(boolean enable) {
|
public StreamConfiguration.Builder setEnableSops(boolean enable) {
|
||||||
config.sops = enable;
|
config.sops = enable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder enableAdaptiveResolution(boolean enable) {
|
public StreamConfiguration.Builder enableAdaptiveResolution(boolean enable) {
|
||||||
config.enableAdaptiveResolution = enable;
|
config.enableAdaptiveResolution = enable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder enableLocalAudioPlayback(boolean enable) {
|
public StreamConfiguration.Builder enableLocalAudioPlayback(boolean enable) {
|
||||||
config.playLocalAudio = enable;
|
config.playLocalAudio = enable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setMaxPacketSize(int maxPacketSize) {
|
public StreamConfiguration.Builder setMaxPacketSize(int maxPacketSize) {
|
||||||
config.maxPacketSize = maxPacketSize;
|
config.maxPacketSize = maxPacketSize;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setHevcBitratePercentageMultiplier(int multiplier) {
|
public StreamConfiguration.Builder setHevcBitratePercentageMultiplier(int multiplier) {
|
||||||
config.hevcBitratePercentageMultiplier = multiplier;
|
config.hevcBitratePercentageMultiplier = multiplier;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setEnableHdr(boolean enableHdr) {
|
public StreamConfiguration.Builder setEnableHdr(boolean enableHdr) {
|
||||||
config.enableHdr = enableHdr;
|
config.enableHdr = enableHdr;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setAttachedGamepadMask(int attachedGamepadMask) {
|
public StreamConfiguration.Builder setAttachedGamepadMask(int attachedGamepadMask) {
|
||||||
config.attachedGamepadMask = attachedGamepadMask;
|
config.attachedGamepadMask = attachedGamepadMask;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setAttachedGamepadMaskByCount(int gamepadCount) {
|
public StreamConfiguration.Builder setAttachedGamepadMaskByCount(int gamepadCount) {
|
||||||
config.attachedGamepadMask = 0;
|
config.attachedGamepadMask = 0;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (gamepadCount > i) {
|
if (gamepadCount > i) {
|
||||||
config.attachedGamepadMask |= 1 << i;
|
config.attachedGamepadMask |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setClientRefreshRateX100(int refreshRateX100) {
|
public StreamConfiguration.Builder setClientRefreshRateX100(int refreshRateX100) {
|
||||||
config.clientRefreshRateX100 = refreshRateX100;
|
config.clientRefreshRateX100 = refreshRateX100;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setAudioConfiguration(int audioConfig) {
|
public StreamConfiguration.Builder setAudioConfiguration(int audioConfig) {
|
||||||
if (audioConfig == MoonBridge.AUDIO_CONFIGURATION_STEREO) {
|
if (audioConfig == MoonBridge.AUDIO_CONFIGURATION_STEREO) {
|
||||||
config.audioChannelCount = CHANNEL_COUNT_STEREO;
|
config.audioChannelCount = CHANNEL_COUNT_STEREO;
|
||||||
config.audioChannelMask = CHANNEL_MASK_STEREO;
|
config.audioChannelMask = CHANNEL_MASK_STEREO;
|
||||||
}
|
}
|
||||||
else if (audioConfig == MoonBridge.AUDIO_CONFIGURATION_51_SURROUND) {
|
else if (audioConfig == MoonBridge.AUDIO_CONFIGURATION_51_SURROUND) {
|
||||||
config.audioChannelCount = CHANNEL_COUNT_5_1;
|
config.audioChannelCount = CHANNEL_COUNT_5_1;
|
||||||
config.audioChannelMask = CHANNEL_MASK_5_1;
|
config.audioChannelMask = CHANNEL_MASK_5_1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalArgumentException("Invalid audio configuration");
|
throw new IllegalArgumentException("Invalid audio configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
config.audioConfiguration = audioConfig;
|
config.audioConfiguration = audioConfig;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setHevcSupported(boolean supportsHevc) {
|
public StreamConfiguration.Builder setHevcSupported(boolean supportsHevc) {
|
||||||
config.supportsHevc = supportsHevc;
|
config.supportsHevc = supportsHevc;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamConfiguration build() {
|
public StreamConfiguration build() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamConfiguration() {
|
private StreamConfiguration() {
|
||||||
// Set default attributes
|
// Set default attributes
|
||||||
this.app = new NvApp("Steam");
|
this.app = new NvApp("Steam");
|
||||||
this.width = 1280;
|
this.width = 1280;
|
||||||
this.height = 720;
|
this.height = 720;
|
||||||
this.refreshRate = 60;
|
this.refreshRate = 60;
|
||||||
this.bitrate = 10000;
|
this.bitrate = 10000;
|
||||||
this.maxPacketSize = 1024;
|
this.maxPacketSize = 1024;
|
||||||
this.remote = STREAM_CFG_AUTO;
|
this.remote = STREAM_CFG_AUTO;
|
||||||
this.sops = true;
|
this.sops = true;
|
||||||
this.enableAdaptiveResolution = false;
|
this.enableAdaptiveResolution = false;
|
||||||
this.audioChannelCount = CHANNEL_COUNT_STEREO;
|
this.audioChannelCount = CHANNEL_COUNT_STEREO;
|
||||||
this.audioChannelMask = CHANNEL_MASK_STEREO;
|
this.audioChannelMask = CHANNEL_MASK_STEREO;
|
||||||
this.supportsHevc = false;
|
this.supportsHevc = false;
|
||||||
this.enableHdr = false;
|
this.enableHdr = false;
|
||||||
this.attachedGamepadMask = 0;
|
this.attachedGamepadMask = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRefreshRate() {
|
public int getRefreshRate() {
|
||||||
return refreshRate;
|
return refreshRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBitrate() {
|
public int getBitrate() {
|
||||||
return bitrate;
|
return bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxPacketSize() {
|
public int getMaxPacketSize() {
|
||||||
return maxPacketSize;
|
return maxPacketSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NvApp getApp() {
|
public NvApp getApp() {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getSops() {
|
public boolean getSops() {
|
||||||
return sops;
|
return sops;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAdaptiveResolutionEnabled() {
|
public boolean getAdaptiveResolutionEnabled() {
|
||||||
return enableAdaptiveResolution;
|
return enableAdaptiveResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getPlayLocalAudio() {
|
public boolean getPlayLocalAudio() {
|
||||||
return playLocalAudio;
|
return playLocalAudio;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRemote() {
|
public int getRemote() {
|
||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAudioChannelCount() {
|
public int getAudioChannelCount() {
|
||||||
return audioChannelCount;
|
return audioChannelCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAudioChannelMask() {
|
public int getAudioChannelMask() {
|
||||||
return audioChannelMask;
|
return audioChannelMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAudioConfiguration() {
|
public int getAudioConfiguration() {
|
||||||
return audioConfiguration;
|
return audioConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getHevcSupported() {
|
public boolean getHevcSupported() {
|
||||||
return supportsHevc;
|
return supportsHevc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHevcBitratePercentageMultiplier() {
|
public int getHevcBitratePercentageMultiplier() {
|
||||||
return hevcBitratePercentageMultiplier;
|
return hevcBitratePercentageMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getEnableHdr() {
|
public boolean getEnableHdr() {
|
||||||
return enableHdr;
|
return enableHdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAttachedGamepadMask() {
|
public int getAttachedGamepadMask() {
|
||||||
return attachedGamepadMask;
|
return attachedGamepadMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getClientRefreshRateX100() {
|
public int getClientRefreshRateX100() {
|
||||||
return clientRefreshRateX100;
|
return clientRefreshRateX100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,57 @@
|
|||||||
package com.limelight.nvstream.av;
|
package com.limelight.nvstream.av;
|
||||||
|
|
||||||
public class ByteBufferDescriptor {
|
public class ByteBufferDescriptor {
|
||||||
public byte[] data;
|
public byte[] data;
|
||||||
public int offset;
|
public int offset;
|
||||||
public int length;
|
public int length;
|
||||||
|
|
||||||
public ByteBufferDescriptor nextDescriptor;
|
public ByteBufferDescriptor nextDescriptor;
|
||||||
|
|
||||||
public ByteBufferDescriptor(byte[] data, int offset, int length)
|
public ByteBufferDescriptor(byte[] data, int offset, int length)
|
||||||
{
|
{
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBufferDescriptor(ByteBufferDescriptor desc)
|
public ByteBufferDescriptor(ByteBufferDescriptor desc)
|
||||||
{
|
{
|
||||||
this.data = desc.data;
|
this.data = desc.data;
|
||||||
this.offset = desc.offset;
|
this.offset = desc.offset;
|
||||||
this.length = desc.length;
|
this.length = desc.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reinitialize(byte[] data, int offset, int length)
|
public void reinitialize(byte[] data, int offset, int length)
|
||||||
{
|
{
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.nextDescriptor = null;
|
this.nextDescriptor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void print()
|
public void print()
|
||||||
{
|
{
|
||||||
print(offset, length);
|
print(offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void print(int length)
|
public void print(int length)
|
||||||
{
|
{
|
||||||
print(this.offset, length);
|
print(this.offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void print(int offset, int length)
|
public void print(int offset, int length)
|
||||||
{
|
{
|
||||||
for (int i = offset; i < offset+length;) {
|
for (int i = offset; i < offset+length;) {
|
||||||
if (i + 8 <= offset+length) {
|
if (i + 8 <= offset+length) {
|
||||||
System.out.printf("%x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i,
|
System.out.printf("%x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i,
|
||||||
data[i], data[i+1], data[i+2], data[i+3], data[i+4], data[i+5], data[i+6], data[i+7]);
|
data[i], data[i+1], data[i+2], data[i+3], data[i+4], data[i+5], data[i+6], data[i+7]);
|
||||||
i += 8;
|
i += 8;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.out.printf("%x: %02x \n", i, data[i]);
|
System.out.printf("%x: %02x \n", i, data[i]);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.limelight.nvstream.av.audio;
|
package com.limelight.nvstream.av.audio;
|
||||||
|
|
||||||
public interface AudioRenderer {
|
public interface AudioRenderer {
|
||||||
int setup(int audioConfiguration);
|
int setup(int audioConfiguration);
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void playDecodedAudio(short[] audioData);
|
void playDecodedAudio(short[] audioData);
|
||||||
|
|
||||||
void cleanup();
|
void cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package com.limelight.nvstream.av.video;
|
package com.limelight.nvstream.av.video;
|
||||||
|
|
||||||
public abstract class VideoDecoderRenderer {
|
public abstract class VideoDecoderRenderer {
|
||||||
public abstract int setup(int format, int width, int height, int redrawRate);
|
public abstract int setup(int format, int width, int height, int redrawRate);
|
||||||
|
|
||||||
public abstract void start();
|
public abstract void start();
|
||||||
|
|
||||||
public abstract void stop();
|
public abstract void stop();
|
||||||
|
|
||||||
// This is called once for each frame-start NALU. This means it will be called several times
|
// This is called once for each frame-start NALU. This means it will be called several times
|
||||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||||
int frameNumber, long receiveTimeMs);
|
int frameNumber, long receiveTimeMs);
|
||||||
|
|
||||||
public abstract void cleanup();
|
public abstract void cleanup();
|
||||||
|
|
||||||
public abstract int getCapabilities();
|
public abstract int getCapabilities();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,82 +4,82 @@ import java.security.cert.X509Certificate;
|
|||||||
|
|
||||||
|
|
||||||
public class ComputerDetails {
|
public class ComputerDetails {
|
||||||
public enum State {
|
public enum State {
|
||||||
ONLINE, OFFLINE, UNKNOWN
|
ONLINE, OFFLINE, UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistent attributes
|
// Persistent attributes
|
||||||
public String uuid;
|
public String uuid;
|
||||||
public String name;
|
public String name;
|
||||||
public String localAddress;
|
public String localAddress;
|
||||||
public String remoteAddress;
|
public String remoteAddress;
|
||||||
public String manualAddress;
|
public String manualAddress;
|
||||||
public String ipv6Address;
|
public String ipv6Address;
|
||||||
public String macAddress;
|
public String macAddress;
|
||||||
public X509Certificate serverCert;
|
public X509Certificate serverCert;
|
||||||
|
|
||||||
// Transient attributes
|
// Transient attributes
|
||||||
public State state;
|
public State state;
|
||||||
public String activeAddress;
|
public String activeAddress;
|
||||||
public PairingManager.PairState pairState;
|
public PairingManager.PairState pairState;
|
||||||
public int runningGameId;
|
public int runningGameId;
|
||||||
public String rawAppList;
|
public String rawAppList;
|
||||||
|
|
||||||
public ComputerDetails() {
|
public ComputerDetails() {
|
||||||
// Use defaults
|
// Use defaults
|
||||||
state = State.UNKNOWN;
|
state = State.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerDetails(ComputerDetails details) {
|
public ComputerDetails(ComputerDetails details) {
|
||||||
// Copy details from the other computer
|
// Copy details from the other computer
|
||||||
update(details);
|
update(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(ComputerDetails details) {
|
public void update(ComputerDetails details) {
|
||||||
this.state = details.state;
|
this.state = details.state;
|
||||||
this.name = details.name;
|
this.name = details.name;
|
||||||
this.uuid = details.uuid;
|
this.uuid = details.uuid;
|
||||||
if (details.activeAddress != null) {
|
if (details.activeAddress != null) {
|
||||||
this.activeAddress = details.activeAddress;
|
this.activeAddress = details.activeAddress;
|
||||||
}
|
}
|
||||||
// We can get IPv4 loopback addresses with GS IPv6 Forwarder
|
// We can get IPv4 loopback addresses with GS IPv6 Forwarder
|
||||||
if (details.localAddress != null && !details.localAddress.startsWith("127.")) {
|
if (details.localAddress != null && !details.localAddress.startsWith("127.")) {
|
||||||
this.localAddress = details.localAddress;
|
this.localAddress = details.localAddress;
|
||||||
}
|
}
|
||||||
if (details.remoteAddress != null) {
|
if (details.remoteAddress != null) {
|
||||||
this.remoteAddress = details.remoteAddress;
|
this.remoteAddress = details.remoteAddress;
|
||||||
}
|
}
|
||||||
if (details.manualAddress != null) {
|
if (details.manualAddress != null) {
|
||||||
this.manualAddress = details.manualAddress;
|
this.manualAddress = details.manualAddress;
|
||||||
}
|
}
|
||||||
if (details.ipv6Address != null) {
|
if (details.ipv6Address != null) {
|
||||||
this.ipv6Address = details.ipv6Address;
|
this.ipv6Address = details.ipv6Address;
|
||||||
}
|
}
|
||||||
if (details.macAddress != null && !details.macAddress.equals("00:00:00:00:00:00")) {
|
if (details.macAddress != null && !details.macAddress.equals("00:00:00:00:00:00")) {
|
||||||
this.macAddress = details.macAddress;
|
this.macAddress = details.macAddress;
|
||||||
}
|
}
|
||||||
if (details.serverCert != null) {
|
if (details.serverCert != null) {
|
||||||
this.serverCert = details.serverCert;
|
this.serverCert = details.serverCert;
|
||||||
}
|
}
|
||||||
this.pairState = details.pairState;
|
this.pairState = details.pairState;
|
||||||
this.runningGameId = details.runningGameId;
|
this.runningGameId = details.runningGameId;
|
||||||
this.rawAppList = details.rawAppList;
|
this.rawAppList = details.rawAppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append("State: ").append(state).append("\n");
|
str.append("State: ").append(state).append("\n");
|
||||||
str.append("Active Address: ").append(activeAddress).append("\n");
|
str.append("Active Address: ").append(activeAddress).append("\n");
|
||||||
str.append("Name: ").append(name).append("\n");
|
str.append("Name: ").append(name).append("\n");
|
||||||
str.append("UUID: ").append(uuid).append("\n");
|
str.append("UUID: ").append(uuid).append("\n");
|
||||||
str.append("Local Address: ").append(localAddress).append("\n");
|
str.append("Local Address: ").append(localAddress).append("\n");
|
||||||
str.append("Remote Address: ").append(remoteAddress).append("\n");
|
str.append("Remote Address: ").append(remoteAddress).append("\n");
|
||||||
str.append("IPv6 Address: ").append(ipv6Address).append("\n");
|
str.append("IPv6 Address: ").append(ipv6Address).append("\n");
|
||||||
str.append("Manual Address: ").append(manualAddress).append("\n");
|
str.append("Manual Address: ").append(manualAddress).append("\n");
|
||||||
str.append("MAC Address: ").append(macAddress).append("\n");
|
str.append("MAC Address: ").append(macAddress).append("\n");
|
||||||
str.append("Pair State: ").append(pairState).append("\n");
|
str.append("Pair State: ").append(pairState).append("\n");
|
||||||
str.append("Running Game ID: ").append(runningGameId).append("\n");
|
str.append("Running Game ID: ").append(runningGameId).append("\n");
|
||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,26 @@ package com.limelight.nvstream.http;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class GfeHttpResponseException extends IOException {
|
public class GfeHttpResponseException extends IOException {
|
||||||
private static final long serialVersionUID = 1543508830807804222L;
|
private static final long serialVersionUID = 1543508830807804222L;
|
||||||
|
|
||||||
private int errorCode;
|
private int errorCode;
|
||||||
private String errorMsg;
|
private String errorMsg;
|
||||||
|
|
||||||
public GfeHttpResponseException(int errorCode, String errorMsg) {
|
public GfeHttpResponseException(int errorCode, String errorMsg) {
|
||||||
this.errorCode = errorCode;
|
this.errorCode = errorCode;
|
||||||
this.errorMsg = errorMsg;
|
this.errorMsg = errorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getErrorCode() {
|
public int getErrorCode() {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getErrorMessage() {
|
public String getErrorMessage() {
|
||||||
return errorMsg;
|
return errorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return "GeForce Experience returned error: "+errorMsg+" (Error code: "+errorCode+")";
|
return "GeForce Experience returned error: "+errorMsg+" (Error code: "+errorCode+")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
|
||||||
public interface LimelightCryptoProvider {
|
public interface LimelightCryptoProvider {
|
||||||
X509Certificate getClientCertificate();
|
X509Certificate getClientCertificate();
|
||||||
RSAPrivateKey getClientPrivateKey();
|
RSAPrivateKey getClientPrivateKey();
|
||||||
byte[] getPemEncodedClientCertificate();
|
byte[] getPemEncodedClientCertificate();
|
||||||
String encodeBase64String(byte[] data);
|
String encodeBase64String(byte[] data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,59 +3,59 @@ package com.limelight.nvstream.http;
|
|||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
public class NvApp {
|
public class NvApp {
|
||||||
private String appName = "";
|
private String appName = "";
|
||||||
private int appId;
|
private int appId;
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private boolean hdrSupported;
|
private boolean hdrSupported;
|
||||||
|
|
||||||
public NvApp() {}
|
public NvApp() {}
|
||||||
|
|
||||||
public NvApp(String appName) {
|
public NvApp(String appName) {
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NvApp(String appName, int appId, boolean hdrSupported) {
|
public NvApp(String appName, int appId, boolean hdrSupported) {
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
this.appId = appId;
|
this.appId = appId;
|
||||||
this.hdrSupported = hdrSupported;
|
this.hdrSupported = hdrSupported;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppName(String appName) {
|
public void setAppName(String appName) {
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppId(String appId) {
|
public void setAppId(String appId) {
|
||||||
try {
|
try {
|
||||||
this.appId = Integer.parseInt(appId);
|
this.appId = Integer.parseInt(appId);
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
LimeLog.warning("Malformed app ID: "+appId);
|
LimeLog.warning("Malformed app ID: "+appId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppId(int appId) {
|
public void setAppId(int appId) {
|
||||||
this.appId = appId;
|
this.appId = appId;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHdrSupported(boolean hdrSupported) {
|
public void setHdrSupported(boolean hdrSupported) {
|
||||||
this.hdrSupported = hdrSupported;
|
this.hdrSupported = hdrSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAppName() {
|
public String getAppName() {
|
||||||
return this.appName;
|
return this.appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAppId() {
|
public int getAppId() {
|
||||||
return this.appId;
|
return this.appId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHdrSupported() {
|
public boolean isHdrSupported() {
|
||||||
return this.hdrSupported;
|
return this.hdrSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return this.initialized;
|
return this.initialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,323 +18,323 @@ import java.util.Random;
|
|||||||
|
|
||||||
public class PairingManager {
|
public class PairingManager {
|
||||||
|
|
||||||
private NvHTTP http;
|
private NvHTTP http;
|
||||||
|
|
||||||
private PrivateKey pk;
|
private PrivateKey pk;
|
||||||
private X509Certificate cert;
|
private X509Certificate cert;
|
||||||
private SecretKey aesKey;
|
private SecretKey aesKey;
|
||||||
private byte[] pemCertBytes;
|
private byte[] pemCertBytes;
|
||||||
|
|
||||||
private X509Certificate serverCert;
|
private X509Certificate serverCert;
|
||||||
|
|
||||||
public enum PairState {
|
public enum PairState {
|
||||||
NOT_PAIRED,
|
NOT_PAIRED,
|
||||||
PAIRED,
|
PAIRED,
|
||||||
PIN_WRONG,
|
PIN_WRONG,
|
||||||
FAILED,
|
FAILED,
|
||||||
ALREADY_IN_PROGRESS
|
ALREADY_IN_PROGRESS
|
||||||
}
|
}
|
||||||
|
|
||||||
public PairingManager(NvHTTP http, LimelightCryptoProvider cryptoProvider) {
|
public PairingManager(NvHTTP http, LimelightCryptoProvider cryptoProvider) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.cert = cryptoProvider.getClientCertificate();
|
this.cert = cryptoProvider.getClientCertificate();
|
||||||
this.pemCertBytes = cryptoProvider.getPemEncodedClientCertificate();
|
this.pemCertBytes = cryptoProvider.getPemEncodedClientCertificate();
|
||||||
this.pk = cryptoProvider.getClientPrivateKey();
|
this.pk = cryptoProvider.getClientPrivateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
private static String bytesToHex(byte[] bytes) {
|
private static String bytesToHex(byte[] bytes) {
|
||||||
char[] hexChars = new char[bytes.length * 2];
|
char[] hexChars = new char[bytes.length * 2];
|
||||||
for ( int j = 0; j < bytes.length; j++ ) {
|
for ( int j = 0; j < bytes.length; j++ ) {
|
||||||
int v = bytes[j] & 0xFF;
|
int v = bytes[j] & 0xFF;
|
||||||
hexChars[j * 2] = hexArray[v >>> 4];
|
hexChars[j * 2] = hexArray[v >>> 4];
|
||||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||||
}
|
}
|
||||||
return new String(hexChars);
|
return new String(hexChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] hexToBytes(String s) {
|
private static byte[] hexToBytes(String s) {
|
||||||
int len = s.length();
|
int len = s.length();
|
||||||
byte[] data = new byte[len / 2];
|
byte[] data = new byte[len / 2];
|
||||||
for (int i = 0; i < len; i += 2) {
|
for (int i = 0; i < len; i += 2) {
|
||||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||||
+ Character.digit(s.charAt(i+1), 16));
|
+ Character.digit(s.charAt(i+1), 16));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
||||||
{
|
{
|
||||||
String certText = NvHTTP.getXmlString(text, "plaincert");
|
String certText = NvHTTP.getXmlString(text, "plaincert");
|
||||||
if (certText != null) {
|
if (certText != null) {
|
||||||
byte[] certBytes = hexToBytes(certText);
|
byte[] certBytes = hexToBytes(certText);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
return (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certBytes));
|
return (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateRandomBytes(int length)
|
private byte[] generateRandomBytes(int length)
|
||||||
{
|
{
|
||||||
byte[] rand = new byte[length];
|
byte[] rand = new byte[length];
|
||||||
new SecureRandom().nextBytes(rand);
|
new SecureRandom().nextBytes(rand);
|
||||||
return rand;
|
return rand;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] saltPin(byte[] salt, String pin) throws UnsupportedEncodingException {
|
private static byte[] saltPin(byte[] salt, String pin) throws UnsupportedEncodingException {
|
||||||
byte[] saltedPin = new byte[salt.length + pin.length()];
|
byte[] saltedPin = new byte[salt.length + pin.length()];
|
||||||
System.arraycopy(salt, 0, saltedPin, 0, salt.length);
|
System.arraycopy(salt, 0, saltedPin, 0, salt.length);
|
||||||
System.arraycopy(pin.getBytes("UTF-8"), 0, saltedPin, salt.length, pin.length());
|
System.arraycopy(pin.getBytes("UTF-8"), 0, saltedPin, salt.length, pin.length());
|
||||||
return saltedPin;
|
return saltedPin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verifySignature(byte[] data, byte[] signature, Certificate cert) {
|
private static boolean verifySignature(byte[] data, byte[] signature, Certificate cert) {
|
||||||
try {
|
try {
|
||||||
Signature sig = Signature.getInstance("SHA256withRSA");
|
Signature sig = Signature.getInstance("SHA256withRSA");
|
||||||
sig.initVerify(cert.getPublicKey());
|
sig.initVerify(cert.getPublicKey());
|
||||||
sig.update(data);
|
sig.update(data);
|
||||||
return sig.verify(signature);
|
return sig.verify(signature);
|
||||||
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] signData(byte[] data, PrivateKey key) {
|
private static byte[] signData(byte[] data, PrivateKey key) {
|
||||||
try {
|
try {
|
||||||
Signature sig = Signature.getInstance("SHA256withRSA");
|
Signature sig = Signature.getInstance("SHA256withRSA");
|
||||||
sig.initSign(key);
|
sig.initSign(key);
|
||||||
sig.update(data);
|
sig.update(data);
|
||||||
byte[] signature = new byte[256];
|
byte[] signature = new byte[256];
|
||||||
sig.sign(signature, 0, signature.length);
|
sig.sign(signature, 0, signature.length);
|
||||||
return signature;
|
return signature;
|
||||||
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
|
|
||||||
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
||||||
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
||||||
byte[] fullDecrypted = new byte[blockRoundedSize];
|
byte[] fullDecrypted = new byte[blockRoundedSize];
|
||||||
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
||||||
cipher.doFinal(blockRoundedEncrypted, 0,
|
cipher.doFinal(blockRoundedEncrypted, 0,
|
||||||
blockRoundedSize, fullDecrypted);
|
blockRoundedSize, fullDecrypted);
|
||||||
return fullDecrypted;
|
return fullDecrypted;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
|
|
||||||
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
||||||
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||||
return cipher.doFinal(blockRoundedData);
|
return cipher.doFinal(blockRoundedData);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||||
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||||
return new SecretKeySpec(aesTruncated, "AES");
|
return new SecretKeySpec(aesTruncated, "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] concatBytes(byte[] a, byte[] b) {
|
private static byte[] concatBytes(byte[] a, byte[] b) {
|
||||||
byte[] c = new byte[a.length + b.length];
|
byte[] c = new byte[a.length + b.length];
|
||||||
System.arraycopy(a, 0, c, 0, a.length);
|
System.arraycopy(a, 0, c, 0, a.length);
|
||||||
System.arraycopy(b, 0, c, a.length, b.length);
|
System.arraycopy(b, 0, c, a.length, b.length);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generatePinString() {
|
public static String generatePinString() {
|
||||||
Random r = new Random();
|
Random r = new Random();
|
||||||
return String.format((Locale)null, "%d%d%d%d",
|
return String.format((Locale)null, "%d%d%d%d",
|
||||||
r.nextInt(10), r.nextInt(10),
|
r.nextInt(10), r.nextInt(10),
|
||||||
r.nextInt(10), r.nextInt(10));
|
r.nextInt(10), r.nextInt(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public X509Certificate getPairedCert() {
|
public X509Certificate getPairedCert() {
|
||||||
return serverCert;
|
return serverCert;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PairState pair(String serverInfo, String pin) throws IOException, XmlPullParserException {
|
public PairState pair(String serverInfo, String pin) throws IOException, XmlPullParserException {
|
||||||
PairingHashAlgorithm hashAlgo;
|
PairingHashAlgorithm hashAlgo;
|
||||||
|
|
||||||
int serverMajorVersion = http.getServerMajorVersion(serverInfo);
|
int serverMajorVersion = http.getServerMajorVersion(serverInfo);
|
||||||
LimeLog.info("Pairing with server generation: "+serverMajorVersion);
|
LimeLog.info("Pairing with server generation: "+serverMajorVersion);
|
||||||
if (serverMajorVersion >= 7) {
|
if (serverMajorVersion >= 7) {
|
||||||
// Gen 7+ uses SHA-256 hashing
|
// Gen 7+ uses SHA-256 hashing
|
||||||
hashAlgo = new Sha256PairingHash();
|
hashAlgo = new Sha256PairingHash();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Prior to Gen 7, SHA-1 is used
|
// Prior to Gen 7, SHA-1 is used
|
||||||
hashAlgo = new Sha1PairingHash();
|
hashAlgo = new Sha1PairingHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a salt for hashing the PIN
|
// Generate a salt for hashing the PIN
|
||||||
byte[] salt = generateRandomBytes(16);
|
byte[] salt = generateRandomBytes(16);
|
||||||
|
|
||||||
// Combine the salt and pin, then create an AES key from them
|
// Combine the salt and pin, then create an AES key from them
|
||||||
byte[] saltAndPin = saltPin(salt, pin);
|
byte[] saltAndPin = saltPin(salt, pin);
|
||||||
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
||||||
|
|
||||||
// Send the salt and get the server cert. This doesn't have a read timeout
|
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||||
// because the user must enter the PIN before the server responds
|
// because the user must enter the PIN before the server responds
|
||||||
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
|
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
||||||
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
||||||
false);
|
false);
|
||||||
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save this cert for retrieval later
|
// Save this cert for retrieval later
|
||||||
serverCert = extractPlainCert(getCert);
|
serverCert = extractPlainCert(getCert);
|
||||||
if (serverCert == null) {
|
if (serverCert == null) {
|
||||||
// Attempting to pair while another device is pairing will cause GFE
|
// Attempting to pair while another device is pairing will cause GFE
|
||||||
// to give an empty cert in the response.
|
// to give an empty cert in the response.
|
||||||
return PairState.ALREADY_IN_PROGRESS;
|
return PairState.ALREADY_IN_PROGRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require this cert for TLS to this host
|
// Require this cert for TLS to this host
|
||||||
http.setServerCert(serverCert);
|
http.setServerCert(serverCert);
|
||||||
|
|
||||||
// Generate a random challenge and encrypt it with our AES key
|
// Generate a random challenge and encrypt it with our AES key
|
||||||
byte[] randomChallenge = generateRandomBytes(16);
|
byte[] randomChallenge = generateRandomBytes(16);
|
||||||
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
||||||
|
|
||||||
// Send the encrypted challenge to the server
|
// Send the encrypted challenge to the server
|
||||||
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
||||||
true);
|
true);
|
||||||
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the server's response and subsequent challenge
|
// Decode the server's response and subsequent challenge
|
||||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
|
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
|
||||||
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
||||||
|
|
||||||
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
||||||
byte[] serverChallenge = Arrays.copyOfRange(decServerChallengeResponse, hashAlgo.getHashLength(), hashAlgo.getHashLength() + 16);
|
byte[] serverChallenge = Arrays.copyOfRange(decServerChallengeResponse, hashAlgo.getHashLength(), hashAlgo.getHashLength() + 16);
|
||||||
|
|
||||||
// Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge
|
// Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge
|
||||||
byte[] clientSecret = generateRandomBytes(16);
|
byte[] clientSecret = generateRandomBytes(16);
|
||||||
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
||||||
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
||||||
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
||||||
true);
|
true);
|
||||||
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the server's signed secret
|
// Get the server's signed secret
|
||||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
|
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
|
||||||
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
||||||
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
||||||
|
|
||||||
// Ensure the authenticity of the data
|
// Ensure the authenticity of the data
|
||||||
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
|
|
||||||
// Looks like a MITM
|
// Looks like a MITM
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the server challenge matched what we expected (aka the PIN was correct)
|
// Ensure the server challenge matched what we expected (aka the PIN was correct)
|
||||||
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
||||||
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
|
|
||||||
// Probably got the wrong PIN
|
// Probably got the wrong PIN
|
||||||
return PairState.PIN_WRONG;
|
return PairState.PIN_WRONG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the server our signed secret
|
// Send the server our signed secret
|
||||||
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
||||||
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
||||||
true);
|
true);
|
||||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the initial challenge (seems neccessary for us to show as paired)
|
// Do the initial challenge (seems neccessary for us to show as paired)
|
||||||
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
|
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
||||||
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PairState.PAIRED;
|
return PairState.PAIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface PairingHashAlgorithm {
|
private interface PairingHashAlgorithm {
|
||||||
int getHashLength();
|
int getHashLength();
|
||||||
byte[] hashData(byte[] data);
|
byte[] hashData(byte[] data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Sha1PairingHash implements PairingHashAlgorithm {
|
private static class Sha1PairingHash implements PairingHashAlgorithm {
|
||||||
public int getHashLength() {
|
public int getHashLength() {
|
||||||
return 20;
|
return 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] hashData(byte[] data) {
|
public byte[] hashData(byte[] data) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
return md.digest(data);
|
return md.digest(data);
|
||||||
}
|
}
|
||||||
catch (NoSuchAlgorithmException e) {
|
catch (NoSuchAlgorithmException e) {
|
||||||
// Shouldn't ever happen
|
// Shouldn't ever happen
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Sha256PairingHash implements PairingHashAlgorithm {
|
private static class Sha256PairingHash implements PairingHashAlgorithm {
|
||||||
public int getHashLength() {
|
public int getHashLength() {
|
||||||
return 32;
|
return 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] hashData(byte[] data) {
|
public byte[] hashData(byte[] data) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
return md.digest(data);
|
return md.digest(data);
|
||||||
}
|
}
|
||||||
catch (NoSuchAlgorithmException e) {
|
catch (NoSuchAlgorithmException e) {
|
||||||
// Shouldn't ever happen
|
// Shouldn't ever happen
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
package com.limelight.nvstream.input;
|
package com.limelight.nvstream.input;
|
||||||
|
|
||||||
public class ControllerPacket {
|
public class ControllerPacket {
|
||||||
public static final short A_FLAG = 0x1000;
|
public static final short A_FLAG = 0x1000;
|
||||||
public static final short B_FLAG = 0x2000;
|
public static final short B_FLAG = 0x2000;
|
||||||
public static final short X_FLAG = 0x4000;
|
public static final short X_FLAG = 0x4000;
|
||||||
public static final short Y_FLAG = (short)0x8000;
|
public static final short Y_FLAG = (short)0x8000;
|
||||||
public static final short UP_FLAG = 0x0001;
|
public static final short UP_FLAG = 0x0001;
|
||||||
public static final short DOWN_FLAG = 0x0002;
|
public static final short DOWN_FLAG = 0x0002;
|
||||||
public static final short LEFT_FLAG = 0x0004;
|
public static final short LEFT_FLAG = 0x0004;
|
||||||
public static final short RIGHT_FLAG = 0x0008;
|
public static final short RIGHT_FLAG = 0x0008;
|
||||||
public static final short LB_FLAG = 0x0100;
|
public static final short LB_FLAG = 0x0100;
|
||||||
public static final short RB_FLAG = 0x0200;
|
public static final short RB_FLAG = 0x0200;
|
||||||
public static final short PLAY_FLAG = 0x0010;
|
public static final short PLAY_FLAG = 0x0010;
|
||||||
public static final short BACK_FLAG = 0x0020;
|
public static final short BACK_FLAG = 0x0020;
|
||||||
public static final short LS_CLK_FLAG = 0x0040;
|
public static final short LS_CLK_FLAG = 0x0040;
|
||||||
public static final short RS_CLK_FLAG = 0x0080;
|
public static final short RS_CLK_FLAG = 0x0080;
|
||||||
public static final short SPECIAL_BUTTON_FLAG = 0x0400;
|
public static final short SPECIAL_BUTTON_FLAG = 0x0400;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.limelight.nvstream.input;
|
package com.limelight.nvstream.input;
|
||||||
|
|
||||||
public class KeyboardPacket {
|
public class KeyboardPacket {
|
||||||
public static final byte KEY_DOWN = 0x03;
|
public static final byte KEY_DOWN = 0x03;
|
||||||
public static final byte KEY_UP = 0x04;
|
public static final byte KEY_UP = 0x04;
|
||||||
|
|
||||||
public static final byte MODIFIER_SHIFT = 0x01;
|
public static final byte MODIFIER_SHIFT = 0x01;
|
||||||
public static final byte MODIFIER_CTRL = 0x02;
|
public static final byte MODIFIER_CTRL = 0x02;
|
||||||
public static final byte MODIFIER_ALT = 0x04;
|
public static final byte MODIFIER_ALT = 0x04;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.limelight.nvstream.input;
|
package com.limelight.nvstream.input;
|
||||||
|
|
||||||
public class MouseButtonPacket {
|
public class MouseButtonPacket {
|
||||||
public static final byte PRESS_EVENT = 0x07;
|
public static final byte PRESS_EVENT = 0x07;
|
||||||
public static final byte RELEASE_EVENT = 0x08;
|
public static final byte RELEASE_EVENT = 0x08;
|
||||||
|
|
||||||
public static final byte BUTTON_LEFT = 0x01;
|
public static final byte BUTTON_LEFT = 0x01;
|
||||||
public static final byte BUTTON_MIDDLE = 0x02;
|
public static final byte BUTTON_MIDDLE = 0x02;
|
||||||
public static final byte BUTTON_RIGHT = 0x03;
|
public static final byte BUTTON_RIGHT = 0x03;
|
||||||
public static final byte BUTTON_X1 = 0x04;
|
public static final byte BUTTON_X1 = 0x04;
|
||||||
public static final byte BUTTON_X2 = 0x05;
|
public static final byte BUTTON_X2 = 0x05;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,62 +4,62 @@ import java.net.Inet6Address;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
|
||||||
public class MdnsComputer {
|
public class MdnsComputer {
|
||||||
private InetAddress localAddr;
|
private InetAddress localAddr;
|
||||||
private Inet6Address v6Addr;
|
private Inet6Address v6Addr;
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
public MdnsComputer(String name, InetAddress localAddress, Inet6Address v6Addr) {
|
public MdnsComputer(String name, InetAddress localAddress, Inet6Address v6Addr) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.localAddr = localAddress;
|
this.localAddr = localAddress;
|
||||||
this.v6Addr = v6Addr;
|
this.v6Addr = v6Addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetAddress getLocalAddress() {
|
public InetAddress getLocalAddress() {
|
||||||
return localAddr;
|
return localAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Inet6Address getIpv6Address() {
|
public Inet6Address getIpv6Address() {
|
||||||
return v6Addr;
|
return v6Addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return name.hashCode();
|
return name.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (o instanceof MdnsComputer) {
|
if (o instanceof MdnsComputer) {
|
||||||
MdnsComputer other = (MdnsComputer)o;
|
MdnsComputer other = (MdnsComputer)o;
|
||||||
|
|
||||||
if (!other.name.equals(name)) {
|
if (!other.name.equals(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((other.localAddr != null && localAddr == null) ||
|
if ((other.localAddr != null && localAddr == null) ||
|
||||||
(other.localAddr == null && localAddr != null) ||
|
(other.localAddr == null && localAddr != null) ||
|
||||||
(other.localAddr != null && !other.localAddr.equals(localAddr))) {
|
(other.localAddr != null && !other.localAddr.equals(localAddr))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((other.v6Addr != null && v6Addr == null) ||
|
if ((other.v6Addr != null && v6Addr == null) ||
|
||||||
(other.v6Addr == null && v6Addr != null) ||
|
(other.v6Addr == null && v6Addr != null) ||
|
||||||
(other.v6Addr != null && !other.v6Addr.equals(v6Addr))) {
|
(other.v6Addr != null && !other.v6Addr.equals(v6Addr))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "["+name+" - "+localAddr+" - "+v6Addr+"]";
|
return "["+name+" - "+localAddr+" - "+v6Addr+"]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,388 +21,388 @@ import javax.jmdns.impl.NetworkTopologyDiscoveryImpl;
|
|||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
public class MdnsDiscoveryAgent implements ServiceListener {
|
public class MdnsDiscoveryAgent implements ServiceListener {
|
||||||
public static final String SERVICE_TYPE = "_nvstream._tcp.local.";
|
public static final String SERVICE_TYPE = "_nvstream._tcp.local.";
|
||||||
|
|
||||||
private MdnsDiscoveryListener listener;
|
private MdnsDiscoveryListener listener;
|
||||||
private Thread discoveryThread;
|
private Thread discoveryThread;
|
||||||
private HashMap<InetAddress, MdnsComputer> computers = new HashMap<InetAddress, MdnsComputer>();
|
private HashMap<InetAddress, MdnsComputer> computers = new HashMap<InetAddress, MdnsComputer>();
|
||||||
private HashSet<String> pendingResolution = new HashSet<String>();
|
private HashSet<String> pendingResolution = new HashSet<String>();
|
||||||
|
|
||||||
// The resolver factory's instance member has a static lifetime which
|
// The resolver factory's instance member has a static lifetime which
|
||||||
// means our ref count and listener must be static also.
|
// means our ref count and listener must be static also.
|
||||||
private static int resolverRefCount = 0;
|
private static int resolverRefCount = 0;
|
||||||
private static HashSet<ServiceListener> listeners = new HashSet<ServiceListener>();
|
private static HashSet<ServiceListener> listeners = new HashSet<ServiceListener>();
|
||||||
private static ServiceListener nvstreamListener = new ServiceListener() {
|
private static ServiceListener nvstreamListener = new ServiceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void serviceAdded(ServiceEvent event) {
|
public void serviceAdded(ServiceEvent event) {
|
||||||
HashSet<ServiceListener> localListeners;
|
HashSet<ServiceListener> localListeners;
|
||||||
|
|
||||||
// Copy the listener set into a new set so we can invoke
|
// Copy the listener set into a new set so we can invoke
|
||||||
// the callbacks without holding the listeners monitor the
|
// the callbacks without holding the listeners monitor the
|
||||||
// whole time.
|
// whole time.
|
||||||
synchronized (listeners) {
|
synchronized (listeners) {
|
||||||
localListeners = new HashSet<ServiceListener>(listeners);
|
localListeners = new HashSet<ServiceListener>(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ServiceListener listener : localListeners) {
|
for (ServiceListener listener : localListeners) {
|
||||||
listener.serviceAdded(event);
|
listener.serviceAdded(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serviceRemoved(ServiceEvent event) {
|
public void serviceRemoved(ServiceEvent event) {
|
||||||
HashSet<ServiceListener> localListeners;
|
HashSet<ServiceListener> localListeners;
|
||||||
|
|
||||||
// Copy the listener set into a new set so we can invoke
|
// Copy the listener set into a new set so we can invoke
|
||||||
// the callbacks without holding the listeners monitor the
|
// the callbacks without holding the listeners monitor the
|
||||||
// whole time.
|
// whole time.
|
||||||
synchronized (listeners) {
|
synchronized (listeners) {
|
||||||
localListeners = new HashSet<ServiceListener>(listeners);
|
localListeners = new HashSet<ServiceListener>(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ServiceListener listener : localListeners) {
|
for (ServiceListener listener : localListeners) {
|
||||||
listener.serviceRemoved(event);
|
listener.serviceRemoved(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serviceResolved(ServiceEvent event) {
|
public void serviceResolved(ServiceEvent event) {
|
||||||
HashSet<ServiceListener> localListeners;
|
HashSet<ServiceListener> localListeners;
|
||||||
|
|
||||||
// Copy the listener set into a new set so we can invoke
|
// Copy the listener set into a new set so we can invoke
|
||||||
// the callbacks without holding the listeners monitor the
|
// the callbacks without holding the listeners monitor the
|
||||||
// whole time.
|
// whole time.
|
||||||
synchronized (listeners) {
|
synchronized (listeners) {
|
||||||
localListeners = new HashSet<ServiceListener>(listeners);
|
localListeners = new HashSet<ServiceListener>(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ServiceListener listener : localListeners) {
|
for (ServiceListener listener : localListeners) {
|
||||||
listener.serviceResolved(event);
|
listener.serviceResolved(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static class MyNetworkTopologyDiscovery extends NetworkTopologyDiscoveryImpl {
|
public static class MyNetworkTopologyDiscovery extends NetworkTopologyDiscoveryImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) {
|
public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) {
|
||||||
// This is an copy of jmDNS's implementation, except we omit the multicast check, since
|
// This is an copy of jmDNS's implementation, except we omit the multicast check, since
|
||||||
// it seems at least some devices lie about interfaces not supporting multicast when they really do.
|
// it seems at least some devices lie about interfaces not supporting multicast when they really do.
|
||||||
try {
|
try {
|
||||||
if (!networkInterface.isUp()) {
|
if (!networkInterface.isUp()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (!networkInterface.supportsMulticast()) {
|
if (!networkInterface.supportsMulticast()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (networkInterface.isLoopback()) {
|
if (networkInterface.isLoopback()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Override jmDNS's default topology discovery class with ours
|
// Override jmDNS's default topology discovery class with ours
|
||||||
NetworkTopologyDiscovery.Factory.setClassDelegate(new NetworkTopologyDiscovery.Factory.ClassDelegate() {
|
NetworkTopologyDiscovery.Factory.setClassDelegate(new NetworkTopologyDiscovery.Factory.ClassDelegate() {
|
||||||
@Override
|
@Override
|
||||||
public NetworkTopologyDiscovery newNetworkTopologyDiscovery() {
|
public NetworkTopologyDiscovery newNetworkTopologyDiscovery() {
|
||||||
return new MyNetworkTopologyDiscovery();
|
return new MyNetworkTopologyDiscovery();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JmmDNS referenceResolver() {
|
private static JmmDNS referenceResolver() {
|
||||||
synchronized (MdnsDiscoveryAgent.class) {
|
synchronized (MdnsDiscoveryAgent.class) {
|
||||||
JmmDNS instance = JmmDNS.Factory.getInstance();
|
JmmDNS instance = JmmDNS.Factory.getInstance();
|
||||||
if (++resolverRefCount == 1) {
|
if (++resolverRefCount == 1) {
|
||||||
// This will cause the listener to be invoked for known hosts immediately.
|
// This will cause the listener to be invoked for known hosts immediately.
|
||||||
// JmDNS only supports one listener per service, so we have to do this here
|
// JmDNS only supports one listener per service, so we have to do this here
|
||||||
// with a static listener.
|
// with a static listener.
|
||||||
instance.addServiceListener(SERVICE_TYPE, nvstreamListener);
|
instance.addServiceListener(SERVICE_TYPE, nvstreamListener);
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dereferenceResolver() {
|
private static void dereferenceResolver() {
|
||||||
synchronized (MdnsDiscoveryAgent.class) {
|
synchronized (MdnsDiscoveryAgent.class) {
|
||||||
if (--resolverRefCount == 0) {
|
if (--resolverRefCount == 0) {
|
||||||
try {
|
try {
|
||||||
JmmDNS.Factory.close();
|
JmmDNS.Factory.close();
|
||||||
} catch (IOException e) {}
|
} catch (IOException e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MdnsDiscoveryAgent(MdnsDiscoveryListener listener) {
|
public MdnsDiscoveryAgent(MdnsDiscoveryListener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleResolvedServiceInfo(ServiceInfo info) {
|
private void handleResolvedServiceInfo(ServiceInfo info) {
|
||||||
synchronized (pendingResolution) {
|
synchronized (pendingResolution) {
|
||||||
pendingResolution.remove(info.getName());
|
pendingResolution.remove(info.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handleServiceInfo(info);
|
handleServiceInfo(info);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
// Invalid DNS response
|
// Invalid DNS response
|
||||||
LimeLog.info("mDNS: Invalid response for machine: "+info.getName());
|
LimeLog.info("mDNS: Invalid response for machine: "+info.getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inet6Address getLocalAddress(Inet6Address[] addresses) {
|
private Inet6Address getLocalAddress(Inet6Address[] addresses) {
|
||||||
for (Inet6Address addr : addresses) {
|
for (Inet6Address addr : addresses) {
|
||||||
if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress()) {
|
if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress()) {
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
// fc00::/7 - ULAs
|
// fc00::/7 - ULAs
|
||||||
else if ((addr.getAddress()[0] & 0xfe) == 0xfc) {
|
else if ((addr.getAddress()[0] & 0xfe) == 0xfc) {
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inet6Address getLinkLocalAddress(Inet6Address[] addresses) {
|
private Inet6Address getLinkLocalAddress(Inet6Address[] addresses) {
|
||||||
for (Inet6Address addr : addresses) {
|
for (Inet6Address addr : addresses) {
|
||||||
if (addr.isLinkLocalAddress()) {
|
if (addr.isLinkLocalAddress()) {
|
||||||
LimeLog.info("Found link-local address: "+addr.getHostAddress());
|
LimeLog.info("Found link-local address: "+addr.getHostAddress());
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inet6Address getBestIpv6Address(Inet6Address[] addresses) {
|
private Inet6Address getBestIpv6Address(Inet6Address[] addresses) {
|
||||||
// First try to find a link local address, so we can match the interface identifier
|
// First try to find a link local address, so we can match the interface identifier
|
||||||
// with a global address (this will work for SLAAC but not DHCPv6).
|
// with a global address (this will work for SLAAC but not DHCPv6).
|
||||||
Inet6Address linkLocalAddr = getLinkLocalAddress(addresses);
|
Inet6Address linkLocalAddr = getLinkLocalAddress(addresses);
|
||||||
|
|
||||||
// We will try once to match a SLAAC interface suffix, then
|
// We will try once to match a SLAAC interface suffix, then
|
||||||
// pick the first matching address
|
// pick the first matching address
|
||||||
for (int tries = 0; tries < 2; tries++) {
|
for (int tries = 0; tries < 2; tries++) {
|
||||||
// We assume the addresses are already sorted in descending order
|
// We assume the addresses are already sorted in descending order
|
||||||
// of preference from Bonjour.
|
// of preference from Bonjour.
|
||||||
for (Inet6Address addr : addresses) {
|
for (Inet6Address addr : addresses) {
|
||||||
if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress() || addr.isLoopbackAddress()) {
|
if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress() || addr.isLoopbackAddress()) {
|
||||||
// Link-local, site-local, and loopback aren't global
|
// Link-local, site-local, and loopback aren't global
|
||||||
LimeLog.info("Ignoring non-global address: "+addr.getHostAddress());
|
LimeLog.info("Ignoring non-global address: "+addr.getHostAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] addrBytes = addr.getAddress();
|
byte[] addrBytes = addr.getAddress();
|
||||||
|
|
||||||
// 2002::/16
|
// 2002::/16
|
||||||
if (addrBytes[0] == 0x20 && addrBytes[1] == 0x02) {
|
if (addrBytes[0] == 0x20 && addrBytes[1] == 0x02) {
|
||||||
// 6to4 has horrible performance
|
// 6to4 has horrible performance
|
||||||
LimeLog.info("Ignoring 6to4 address: "+addr.getHostAddress());
|
LimeLog.info("Ignoring 6to4 address: "+addr.getHostAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 2001::/32
|
// 2001::/32
|
||||||
else if (addrBytes[0] == 0x20 && addrBytes[1] == 0x01 && addrBytes[2] == 0x00 && addrBytes[3] == 0x00) {
|
else if (addrBytes[0] == 0x20 && addrBytes[1] == 0x01 && addrBytes[2] == 0x00 && addrBytes[3] == 0x00) {
|
||||||
// Teredo also has horrible performance
|
// Teredo also has horrible performance
|
||||||
LimeLog.info("Ignoring Teredo address: "+addr.getHostAddress());
|
LimeLog.info("Ignoring Teredo address: "+addr.getHostAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// fc00::/7
|
// fc00::/7
|
||||||
else if ((addrBytes[0] & 0xfe) == 0xfc) {
|
else if ((addrBytes[0] & 0xfe) == 0xfc) {
|
||||||
// ULAs aren't global
|
// ULAs aren't global
|
||||||
LimeLog.info("Ignoring ULA: "+addr.getHostAddress());
|
LimeLog.info("Ignoring ULA: "+addr.getHostAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare the final 64-bit interface identifier and skip the address
|
// Compare the final 64-bit interface identifier and skip the address
|
||||||
// if it doesn't match our link-local address.
|
// if it doesn't match our link-local address.
|
||||||
if (linkLocalAddr != null && tries == 0) {
|
if (linkLocalAddr != null && tries == 0) {
|
||||||
boolean matched = true;
|
boolean matched = true;
|
||||||
|
|
||||||
for (int i = 8; i < 16; i++) {
|
for (int i = 8; i < 16; i++) {
|
||||||
if (linkLocalAddr.getAddress()[i] != addr.getAddress()[i]) {
|
if (linkLocalAddr.getAddress()[i] != addr.getAddress()[i]) {
|
||||||
matched = false;
|
matched = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
LimeLog.info("Ignoring non-matching global address: "+addr.getHostAddress());
|
LimeLog.info("Ignoring non-matching global address: "+addr.getHostAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleServiceInfo(ServiceInfo info) throws UnsupportedEncodingException {
|
private void handleServiceInfo(ServiceInfo info) throws UnsupportedEncodingException {
|
||||||
Inet4Address v4Addrs[] = info.getInet4Addresses();
|
Inet4Address v4Addrs[] = info.getInet4Addresses();
|
||||||
Inet6Address v6Addrs[] = info.getInet6Addresses();
|
Inet6Address v6Addrs[] = info.getInet6Addresses();
|
||||||
|
|
||||||
LimeLog.info("mDNS: "+info.getName()+" has "+v4Addrs.length+" IPv4 addresses");
|
LimeLog.info("mDNS: "+info.getName()+" has "+v4Addrs.length+" IPv4 addresses");
|
||||||
LimeLog.info("mDNS: "+info.getName()+" has "+v6Addrs.length+" IPv6 addresses");
|
LimeLog.info("mDNS: "+info.getName()+" has "+v6Addrs.length+" IPv6 addresses");
|
||||||
|
|
||||||
Inet6Address v6GlobalAddr = getBestIpv6Address(v6Addrs);
|
Inet6Address v6GlobalAddr = getBestIpv6Address(v6Addrs);
|
||||||
|
|
||||||
// Add a computer object for each IPv4 address reported by the PC
|
// Add a computer object for each IPv4 address reported by the PC
|
||||||
for (Inet4Address v4Addr : v4Addrs) {
|
for (Inet4Address v4Addr : v4Addrs) {
|
||||||
synchronized (computers) {
|
synchronized (computers) {
|
||||||
MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6GlobalAddr);
|
MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6GlobalAddr);
|
||||||
if (computers.put(computer.getLocalAddress(), computer) == null) {
|
if (computers.put(computer.getLocalAddress(), computer) == null) {
|
||||||
// This was a new entry
|
// This was a new entry
|
||||||
listener.notifyComputerAdded(computer);
|
listener.notifyComputerAdded(computer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there were no IPv4 addresses, use IPv6 for registration
|
// If there were no IPv4 addresses, use IPv6 for registration
|
||||||
if (v4Addrs.length == 0) {
|
if (v4Addrs.length == 0) {
|
||||||
Inet6Address v6LocalAddr = getLocalAddress(v6Addrs);
|
Inet6Address v6LocalAddr = getLocalAddress(v6Addrs);
|
||||||
|
|
||||||
if (v6LocalAddr != null || v6GlobalAddr != null) {
|
if (v6LocalAddr != null || v6GlobalAddr != null) {
|
||||||
MdnsComputer computer = new MdnsComputer(info.getName(), v6LocalAddr, v6GlobalAddr);
|
MdnsComputer computer = new MdnsComputer(info.getName(), v6LocalAddr, v6GlobalAddr);
|
||||||
if (computers.put(v6LocalAddr != null ?
|
if (computers.put(v6LocalAddr != null ?
|
||||||
computer.getLocalAddress() : computer.getIpv6Address(), computer) == null) {
|
computer.getLocalAddress() : computer.getIpv6Address(), computer) == null) {
|
||||||
// This was a new entry
|
// This was a new entry
|
||||||
listener.notifyComputerAdded(computer);
|
listener.notifyComputerAdded(computer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startDiscovery(final int discoveryIntervalMs) {
|
public void startDiscovery(final int discoveryIntervalMs) {
|
||||||
// Kill any existing discovery before starting a new one
|
// Kill any existing discovery before starting a new one
|
||||||
stopDiscovery();
|
stopDiscovery();
|
||||||
|
|
||||||
// Add our listener to the set
|
// Add our listener to the set
|
||||||
synchronized (listeners) {
|
synchronized (listeners) {
|
||||||
listeners.add(MdnsDiscoveryAgent.this);
|
listeners.add(MdnsDiscoveryAgent.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveryThread = new Thread() {
|
discoveryThread = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// This may result in listener callbacks so we must register
|
// This may result in listener callbacks so we must register
|
||||||
// our listener first.
|
// our listener first.
|
||||||
JmmDNS resolver = referenceResolver();
|
JmmDNS resolver = referenceResolver();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (!Thread.interrupted()) {
|
while (!Thread.interrupted()) {
|
||||||
// Start an mDNS request
|
// Start an mDNS request
|
||||||
resolver.requestServiceInfo(SERVICE_TYPE, null, discoveryIntervalMs);
|
resolver.requestServiceInfo(SERVICE_TYPE, null, discoveryIntervalMs);
|
||||||
|
|
||||||
// Run service resolution again for pending machines
|
// Run service resolution again for pending machines
|
||||||
ArrayList<String> pendingNames;
|
ArrayList<String> pendingNames;
|
||||||
synchronized (pendingResolution) {
|
synchronized (pendingResolution) {
|
||||||
pendingNames = new ArrayList<String>(pendingResolution);
|
pendingNames = new ArrayList<String>(pendingResolution);
|
||||||
}
|
}
|
||||||
for (String name : pendingNames) {
|
for (String name : pendingNames) {
|
||||||
LimeLog.info("mDNS: Retrying service resolution for machine: "+name);
|
LimeLog.info("mDNS: Retrying service resolution for machine: "+name);
|
||||||
ServiceInfo[] infos = resolver.getServiceInfos(SERVICE_TYPE, name, 500);
|
ServiceInfo[] infos = resolver.getServiceInfos(SERVICE_TYPE, name, 500);
|
||||||
if (infos != null && infos.length != 0) {
|
if (infos != null && infos.length != 0) {
|
||||||
LimeLog.info("mDNS: Resolved (retry) with "+infos.length+" service entries");
|
LimeLog.info("mDNS: Resolved (retry) with "+infos.length+" service entries");
|
||||||
for (ServiceInfo svcinfo : infos) {
|
for (ServiceInfo svcinfo : infos) {
|
||||||
handleResolvedServiceInfo(svcinfo);
|
handleResolvedServiceInfo(svcinfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the next polling interval
|
// Wait for the next polling interval
|
||||||
try {
|
try {
|
||||||
Thread.sleep(discoveryIntervalMs);
|
Thread.sleep(discoveryIntervalMs);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// Dereference the resolver
|
// Dereference the resolver
|
||||||
dereferenceResolver();
|
dereferenceResolver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
discoveryThread.setName("mDNS Discovery Thread");
|
discoveryThread.setName("mDNS Discovery Thread");
|
||||||
discoveryThread.start();
|
discoveryThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopDiscovery() {
|
public void stopDiscovery() {
|
||||||
// Remove our listener from the set
|
// Remove our listener from the set
|
||||||
synchronized (listeners) {
|
synchronized (listeners) {
|
||||||
listeners.remove(MdnsDiscoveryAgent.this);
|
listeners.remove(MdnsDiscoveryAgent.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's already a running thread, interrupt it
|
// If there's already a running thread, interrupt it
|
||||||
if (discoveryThread != null) {
|
if (discoveryThread != null) {
|
||||||
discoveryThread.interrupt();
|
discoveryThread.interrupt();
|
||||||
discoveryThread = null;
|
discoveryThread = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MdnsComputer> getComputerSet() {
|
public List<MdnsComputer> getComputerSet() {
|
||||||
synchronized (computers) {
|
synchronized (computers) {
|
||||||
return new ArrayList<MdnsComputer>(computers.values());
|
return new ArrayList<MdnsComputer>(computers.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serviceAdded(ServiceEvent event) {
|
public void serviceAdded(ServiceEvent event) {
|
||||||
LimeLog.info("mDNS: Machine appeared: "+event.getInfo().getName());
|
LimeLog.info("mDNS: Machine appeared: "+event.getInfo().getName());
|
||||||
|
|
||||||
ServiceInfo info = event.getDNS().getServiceInfo(SERVICE_TYPE, event.getInfo().getName(), 500);
|
ServiceInfo info = event.getDNS().getServiceInfo(SERVICE_TYPE, event.getInfo().getName(), 500);
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
// This machine is pending resolution
|
// This machine is pending resolution
|
||||||
synchronized (pendingResolution) {
|
synchronized (pendingResolution) {
|
||||||
pendingResolution.add(event.getInfo().getName());
|
pendingResolution.add(event.getInfo().getName());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("mDNS: Resolved (blocking)");
|
LimeLog.info("mDNS: Resolved (blocking)");
|
||||||
handleResolvedServiceInfo(info);
|
handleResolvedServiceInfo(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serviceRemoved(ServiceEvent event) {
|
public void serviceRemoved(ServiceEvent event) {
|
||||||
LimeLog.info("mDNS: Machine disappeared: "+event.getInfo().getName());
|
LimeLog.info("mDNS: Machine disappeared: "+event.getInfo().getName());
|
||||||
|
|
||||||
Inet4Address v4Addrs[] = event.getInfo().getInet4Addresses();
|
Inet4Address v4Addrs[] = event.getInfo().getInet4Addresses();
|
||||||
for (Inet4Address addr : v4Addrs) {
|
for (Inet4Address addr : v4Addrs) {
|
||||||
synchronized (computers) {
|
synchronized (computers) {
|
||||||
MdnsComputer computer = computers.remove(addr);
|
MdnsComputer computer = computers.remove(addr);
|
||||||
if (computer != null) {
|
if (computer != null) {
|
||||||
listener.notifyComputerRemoved(computer);
|
listener.notifyComputerRemoved(computer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Inet6Address v6Addrs[] = event.getInfo().getInet6Addresses();
|
Inet6Address v6Addrs[] = event.getInfo().getInet6Addresses();
|
||||||
for (Inet6Address addr : v6Addrs) {
|
for (Inet6Address addr : v6Addrs) {
|
||||||
synchronized (computers) {
|
synchronized (computers) {
|
||||||
MdnsComputer computer = computers.remove(addr);
|
MdnsComputer computer = computers.remove(addr);
|
||||||
if (computer != null) {
|
if (computer != null) {
|
||||||
listener.notifyComputerRemoved(computer);
|
listener.notifyComputerRemoved(computer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serviceResolved(ServiceEvent event) {
|
public void serviceResolved(ServiceEvent event) {
|
||||||
// We handle this synchronously
|
// We handle this synchronously
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.limelight.nvstream.mdns;
|
package com.limelight.nvstream.mdns;
|
||||||
|
|
||||||
public interface MdnsDiscoveryListener {
|
public interface MdnsDiscoveryListener {
|
||||||
void notifyComputerAdded(MdnsComputer computer);
|
void notifyComputerAdded(MdnsComputer computer);
|
||||||
void notifyComputerRemoved(MdnsComputer computer);
|
void notifyComputerRemoved(MdnsComputer computer);
|
||||||
void notifyDiscoveryFailure(Exception e);
|
void notifyDiscoveryFailure(Exception e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,90 +10,90 @@ import com.limelight.LimeLog;
|
|||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
public class WakeOnLanSender {
|
public class WakeOnLanSender {
|
||||||
private static final int[] PORTS_TO_TRY = new int[] {
|
private static final int[] PORTS_TO_TRY = new int[] {
|
||||||
7, 9, // Standard WOL ports
|
7, 9, // Standard WOL ports
|
||||||
47998, 47999, 48000, 48002, 48010 // Ports opened by GFE
|
47998, 47999, 48000, 48002, 48010 // Ports opened by GFE
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void sendWolPacket(ComputerDetails computer) throws IOException {
|
public static void sendWolPacket(ComputerDetails computer) throws IOException {
|
||||||
DatagramSocket sock = new DatagramSocket(0);
|
DatagramSocket sock = new DatagramSocket(0);
|
||||||
byte[] payload = createWolPayload(computer);
|
byte[] payload = createWolPayload(computer);
|
||||||
IOException lastException = null;
|
IOException lastException = null;
|
||||||
boolean sentWolPacket = false;
|
boolean sentWolPacket = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try all resolved remote and local addresses and IPv4 broadcast address.
|
// Try all resolved remote and local addresses and IPv4 broadcast address.
|
||||||
// The broadcast address is required to avoid stale ARP cache entries
|
// The broadcast address is required to avoid stale ARP cache entries
|
||||||
// making the sleeping machine unreachable.
|
// making the sleeping machine unreachable.
|
||||||
for (String unresolvedAddress : new String[] {
|
for (String unresolvedAddress : new String[] {
|
||||||
computer.localAddress, computer.remoteAddress, computer.manualAddress, computer.ipv6Address, "255.255.255.255"
|
computer.localAddress, computer.remoteAddress, computer.manualAddress, computer.ipv6Address, "255.255.255.255"
|
||||||
}) {
|
}) {
|
||||||
if (unresolvedAddress == null) {
|
if (unresolvedAddress == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(unresolvedAddress)) {
|
for (InetAddress resolvedAddress : InetAddress.getAllByName(unresolvedAddress)) {
|
||||||
// Try all the ports for each resolved address
|
// Try all the ports for each resolved address
|
||||||
for (int port : PORTS_TO_TRY) {
|
for (int port : PORTS_TO_TRY) {
|
||||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||||
dp.setAddress(resolvedAddress);
|
dp.setAddress(resolvedAddress);
|
||||||
dp.setPort(port);
|
dp.setPort(port);
|
||||||
sock.send(dp);
|
sock.send(dp);
|
||||||
sentWolPacket = true;
|
sentWolPacket = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// We may have addresses that don't resolve on this subnet,
|
// We may have addresses that don't resolve on this subnet,
|
||||||
// but don't throw and exit the whole function if that happens.
|
// but don't throw and exit the whole function if that happens.
|
||||||
// We'll throw it at the end if we didn't send a single packet.
|
// We'll throw it at the end if we didn't send a single packet.
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
lastException = e;
|
lastException = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sock.close();
|
sock.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate the DNS resolution exception if we didn't
|
// Propagate the DNS resolution exception if we didn't
|
||||||
// manage to get a single packet out to the host.
|
// manage to get a single packet out to the host.
|
||||||
if (!sentWolPacket && lastException != null) {
|
if (!sentWolPacket && lastException != null) {
|
||||||
throw lastException;
|
throw lastException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] macStringToBytes(String macAddress) {
|
private static byte[] macStringToBytes(String macAddress) {
|
||||||
byte[] macBytes = new byte[6];
|
byte[] macBytes = new byte[6];
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
Scanner scan = new Scanner(macAddress).useDelimiter(":");
|
Scanner scan = new Scanner(macAddress).useDelimiter(":");
|
||||||
for (int i = 0; i < macBytes.length && scan.hasNext(); i++) {
|
for (int i = 0; i < macBytes.length && scan.hasNext(); i++) {
|
||||||
try {
|
try {
|
||||||
macBytes[i] = (byte) Integer.parseInt(scan.next(), 16);
|
macBytes[i] = (byte) Integer.parseInt(scan.next(), 16);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
LimeLog.warning("Malformed MAC address: "+macAddress+" (index: "+i+")");
|
LimeLog.warning("Malformed MAC address: "+macAddress+" (index: "+i+")");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scan.close();
|
scan.close();
|
||||||
return macBytes;
|
return macBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] createWolPayload(ComputerDetails computer) {
|
private static byte[] createWolPayload(ComputerDetails computer) {
|
||||||
byte[] payload = new byte[102];
|
byte[] payload = new byte[102];
|
||||||
byte[] macAddress = macStringToBytes(computer.macAddress);
|
byte[] macAddress = macStringToBytes(computer.macAddress);
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
// 6 bytes of FF
|
// 6 bytes of FF
|
||||||
for (i = 0; i < 6; i++) {
|
for (i = 0; i < 6; i++) {
|
||||||
payload[i] = (byte)0xFF;
|
payload[i] = (byte)0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16 repetitions of the MAC address
|
// 16 repetitions of the MAC address
|
||||||
for (int j = 0; j < 16; j++) {
|
for (int j = 0; j < 16; j++) {
|
||||||
System.arraycopy(macAddress, 0, payload, i, macAddress.length);
|
System.arraycopy(macAddress, 0, payload, i, macAddress.length);
|
||||||
i += macAddress.length;
|
i += macAddress.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
package com.limelight.utils;
|
package com.limelight.utils;
|
||||||
|
|
||||||
public class Vector2d {
|
public class Vector2d {
|
||||||
private float x;
|
private float x;
|
||||||
private float y;
|
private float y;
|
||||||
private double magnitude;
|
private double magnitude;
|
||||||
|
|
||||||
public static final Vector2d ZERO = new Vector2d();
|
public static final Vector2d ZERO = new Vector2d();
|
||||||
|
|
||||||
public Vector2d() {
|
public Vector2d() {
|
||||||
initialize(0, 0);
|
initialize(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize(float x, float y) {
|
public void initialize(float x, float y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.magnitude = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
this.magnitude = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getMagnitude() {
|
public double getMagnitude() {
|
||||||
return magnitude;
|
return magnitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getNormalized(Vector2d vector) {
|
public void getNormalized(Vector2d vector) {
|
||||||
vector.initialize((float)(x / magnitude), (float)(y / magnitude));
|
vector.initialize((float)(x / magnitude), (float)(y / magnitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scalarMultiply(double factor) {
|
public void scalarMultiply(double factor) {
|
||||||
initialize((float)(x * factor), (float)(y * factor));
|
initialize((float)(x * factor), (float)(y * factor));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setX(float x) {
|
public void setX(float x) {
|
||||||
initialize(x, this.y);
|
initialize(x, this.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setY(float y) {
|
public void setY(float y) {
|
||||||
initialize(this.x, y);
|
initialize(this.x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getX() {
|
public float getX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getY() {
|
public float getY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.limelight;
|
package com.limelight;
|
||||||
|
|
||||||
public class LimelightBuildProps {
|
public class LimelightBuildProps {
|
||||||
public static final boolean ROOT_BUILD = false;
|
public static final boolean ROOT_BUILD = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.limelight;
|
package com.limelight;
|
||||||
|
|
||||||
public class LimelightBuildProps {
|
public class LimelightBuildProps {
|
||||||
public static final boolean ROOT_BUILD = true;
|
public static final boolean ROOT_BUILD = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user