mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-20 15:27:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3de9765eaa | ||
|
|
d4072eb295 | ||
|
|
cac2bdbb81 | ||
|
|
66f0aee3f8 | ||
|
|
b690dc5474 | ||
|
|
c2fbe6ad91 | ||
|
|
cf07c02398 | ||
|
|
42dc928ad5 | ||
|
|
11597f0aa7 | ||
|
|
cdcd4d48f2 | ||
|
|
a9af4e54a9 | ||
|
|
7eac609219 | ||
|
|
fa761debc4 | ||
|
|
62e175f069 | ||
|
|
d7d8c40565 | ||
|
|
64de13ab50 | ||
|
|
2f02939638 | ||
|
|
1d7c8697e9 | ||
|
|
7dea322bbd | ||
|
|
349ecb16ab | ||
|
|
a3867735c1 | ||
|
|
5b087e9f70 | ||
|
|
eed18223eb | ||
|
|
30d4d2a918 | ||
|
|
30f666c70e | ||
|
|
209fead0e8 | ||
|
|
5c6889bf6d | ||
|
|
7d24900756 | ||
|
|
79a75b9d19 | ||
|
|
29b64992bd | ||
|
|
c9b14540f2 | ||
|
|
546843a26c | ||
|
|
d03d260535 | ||
|
|
6946e3c7a2 |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
# ProBot No Response (https://probot.github.io/apps/no-response/)
|
||||||
|
|
||||||
|
daysUntilClose: 7
|
||||||
|
responseRequiredLabel: 'need more info'
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there was no response to a
|
||||||
|
request for more information from the issue opener. Please leave a comment or
|
||||||
|
open a new issue if you have additional information related to this issue.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ProBot Stale (https://probot.github.io/apps/stale/)
|
||||||
|
|
||||||
|
daysUntilStale: 90
|
||||||
|
daysUntilClose: 7
|
||||||
|
exemptLabels:
|
||||||
|
- accepted
|
||||||
|
- bug
|
||||||
|
- enhancement
|
||||||
|
- meta
|
||||||
|
staleLabel: stale
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs.
|
||||||
|
closeComment: false
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Moonlight
|
# Moonlight Android
|
||||||
|
|
||||||
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||||
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
|
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
|
||||||
@@ -6,8 +6,6 @@ We reverse engineered the Shield streaming software and created a version that c
|
|||||||
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
|
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
|
||||||
whether in your own home or over the internet.
|
whether in your own home or over the internet.
|
||||||
|
|
||||||
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
|
|
||||||
|
|
||||||
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
|
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
+3
-3
@@ -1,15 +1,15 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
buildToolsVersion '28.0.1'
|
buildToolsVersion '28.0.3'
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
|
|
||||||
versionName "5.9"
|
versionName "5.10"
|
||||||
versionCode = 168
|
versionCode = 175
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "root"
|
flavorDimensions "root"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.content.Intent;
|
|||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -257,10 +258,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
String computerName = getIntent().getStringExtra(NAME_EXTRA);
|
String computerName = getIntent().getStringExtra(NAME_EXTRA);
|
||||||
|
|
||||||
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
|
|
||||||
TextView label = findViewById(R.id.appListText);
|
TextView label = findViewById(R.id.appListText);
|
||||||
setTitle(labelText);
|
setTitle(computerName);
|
||||||
label.setText(labelText);
|
label.setText(computerName);
|
||||||
|
|
||||||
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
||||||
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
|
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
|
||||||
@@ -344,7 +344,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
|
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
|
// Only add an option to create shortcut if box art is loaded
|
||||||
|
// and when we're in grid-mode (not list-mode).
|
||||||
|
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
|
||||||
|
if (appImageView != null) {
|
||||||
|
// We have a grid ImageView, so we must be in grid-mode
|
||||||
|
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
|
||||||
|
if (drawable != null && drawable.getBitmap() != null) {
|
||||||
|
// We have a bitmap loaded too
|
||||||
|
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private boolean grabbedInput = true;
|
private boolean grabbedInput = true;
|
||||||
private boolean grabComboDown = false;
|
private boolean grabComboDown = false;
|
||||||
private StreamView streamView;
|
private StreamView streamView;
|
||||||
|
private boolean gotBackPointerEvent = false;
|
||||||
|
private boolean syntheticBackDown = false;
|
||||||
|
|
||||||
private ShortcutHelper shortcutHelper;
|
private ShortcutHelper shortcutHelper;
|
||||||
|
|
||||||
@@ -838,9 +840,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// the right mouse button is held down, so we ignore those.
|
// the right mouse button is held down, so we ignore those.
|
||||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
|
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||||
event.getRepeatCount() == 0) {
|
// It appears this may get turned into a right-click pointer event
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
// if we don't return true to indicate that we handled it on
|
||||||
|
// some devices. https://github.com/moonlight-stream/moonlight-android/issues/634
|
||||||
|
if (event.getRepeatCount() == 0 && !gotBackPointerEvent) {
|
||||||
|
syntheticBackDown = true;
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,7 +899,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
// It appears this may get turned into a right-click pointer event
|
||||||
|
// if we don't return true to indicate that we handled it on
|
||||||
|
// some devices. https://github.com/moonlight-stream/moonlight-android/issues/634
|
||||||
|
if (!gotBackPointerEvent || syntheticBackDown) {
|
||||||
|
// We need to raise the button if gotBackPointerEvent is true
|
||||||
|
// in the case where it transitioned to true after we already
|
||||||
|
// sent the right click down event.
|
||||||
|
syntheticBackDown = false;
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,6 +1015,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
else {
|
else {
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use the KEYCODE_BACK hack (which interferes with mice
|
||||||
|
// with actual back buttons) since we're getting right clicks
|
||||||
|
// using this callback.
|
||||||
|
gotBackPointerEvent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||||
@@ -1010,6 +1031,32 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: Disable mouse back button press on Xiaomi due to reported
|
||||||
|
// issues with right clicks triggering it.
|
||||||
|
if (!("Xiaomi".equalsIgnoreCase(Build.MANUFACTURER))) {
|
||||||
|
if ((changedButtons & MotionEvent.BUTTON_BACK) != 0) {
|
||||||
|
if ((event.getButtonState() & MotionEvent.BUTTON_BACK) != 0) {
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_X1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use the KEYCODE_BACK hack. That will cause this
|
||||||
|
// button press to trigger a right-click.
|
||||||
|
gotBackPointerEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changedButtons & MotionEvent.BUTTON_FORWARD) != 0) {
|
||||||
|
if ((event.getButtonState() & MotionEvent.BUTTON_FORWARD) != 0) {
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_X2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get relative axis values if we can
|
// Get relative axis values if we can
|
||||||
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
|
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
|
||||||
// Send the deltas straight from the motion event
|
// Send the deltas straight from the motion event
|
||||||
@@ -1360,6 +1407,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
case EvdevListener.BUTTON_RIGHT:
|
case EvdevListener.BUTTON_RIGHT:
|
||||||
buttonIndex = MouseButtonPacket.BUTTON_RIGHT;
|
buttonIndex = MouseButtonPacket.BUTTON_RIGHT;
|
||||||
break;
|
break;
|
||||||
|
case EvdevListener.BUTTON_X1:
|
||||||
|
buttonIndex = MouseButtonPacket.BUTTON_X1;
|
||||||
|
break;
|
||||||
|
case EvdevListener.BUTTON_X2:
|
||||||
|
buttonIndex = MouseButtonPacket.BUTTON_X2;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LimeLog.warning("Unhandled button: "+buttonId);
|
LimeLog.warning("Unhandled button: "+buttonId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -313,8 +313,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
||||||
|
|
||||||
// Inflate the context menu
|
// Inflate the context menu
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE ||
|
if (computer.details.state == ComputerDetails.State.OFFLINE ||
|
||||||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
computer.details.state == ComputerDetails.State.UNKNOWN) {
|
||||||
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
|
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
|
||||||
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
|
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doPair(final ComputerDetails computer) {
|
private void doPair(final ComputerDetails computer) {
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
if (computer.state == ComputerDetails.State.OFFLINE) {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -478,7 +478,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doUnpair(final ComputerDetails computer) {
|
private void doUnpair(final ComputerDetails computer) {
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
if (computer.state == ComputerDetails.State.OFFLINE) {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doAppList(ComputerDetails computer) {
|
private void doAppList(ComputerDetails computer) {
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
if (computer.state == ComputerDetails.State.OFFLINE) {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -567,12 +567,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
LimeLog.info("Ignoring delete PC request from monkey");
|
LimeLog.info("Ignoring delete PC request from monkey");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (managerBinder == null) {
|
UiHelper.displayDeletePcConfirmationDialog(this, computer.details, new Runnable() {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
@Override
|
||||||
return true;
|
public void run() {
|
||||||
}
|
if (managerBinder == null) {
|
||||||
managerBinder.removeComputer(computer.details.name);
|
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||||
removeComputer(computer.details);
|
return;
|
||||||
|
}
|
||||||
|
managerBinder.removeComputer(computer.details.name);
|
||||||
|
removeComputer(computer.details);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case APP_LIST_ID:
|
case APP_LIST_ID:
|
||||||
@@ -685,8 +690,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||||
long id) {
|
long id) {
|
||||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN ||
|
if (computer.details.state == ComputerDetails.State.UNKNOWN ||
|
||||||
computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
computer.details.state == ComputerDetails.State.OFFLINE) {
|
||||||
// Open the context menu if a PC is offline or refreshing
|
// Open the context menu if a PC is offline or refreshing
|
||||||
openContextMenu(arg1);
|
openContextMenu(arg1);
|
||||||
} else if (computer.details.pairState != PairState.PAIRED) {
|
} else if (computer.details.pairState != PairState.PAIRED) {
|
||||||
|
|||||||
@@ -112,11 +112,15 @@ public class ShortcutTrampoline extends Activity {
|
|||||||
// Now start the activities
|
// Now start the activities
|
||||||
startActivities(intentStack.toArray(new Intent[]{}));
|
startActivities(intentStack.toArray(new Intent[]{}));
|
||||||
} else {
|
} else {
|
||||||
|
// Create the start intent immediately, so we can safely unbind the managerBinder
|
||||||
|
// below before we return.
|
||||||
|
final Intent startIntent = ServerHelper.createStartIntent(ShortcutTrampoline.this,
|
||||||
|
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder);
|
||||||
|
|
||||||
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
|
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
intentStack.add(ServerHelper.createStartIntent(ShortcutTrampoline.this,
|
intentStack.add(startIntent);
|
||||||
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder));
|
|
||||||
|
|
||||||
// Close this activity
|
// Close this activity
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ public interface EvdevListener {
|
|||||||
int BUTTON_LEFT = 1;
|
int BUTTON_LEFT = 1;
|
||||||
int BUTTON_MIDDLE = 2;
|
int BUTTON_MIDDLE = 2;
|
||||||
int BUTTON_RIGHT = 3;
|
int BUTTON_RIGHT = 3;
|
||||||
|
int BUTTON_X1 = 4;
|
||||||
|
int BUTTON_X2 = 5;
|
||||||
|
|
||||||
void mouseMove(int deltaX, int deltaY);
|
void mouseMove(int deltaX, int deltaY);
|
||||||
void mouseButtonEvent(int buttonId, boolean down);
|
void mouseButtonEvent(int buttonId, boolean down);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.limelight.computers;
|
package com.limelight.computers;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -17,15 +15,14 @@ import android.database.sqlite.SQLiteDatabase;
|
|||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
|
|
||||||
public class ComputerDatabaseManager {
|
public class ComputerDatabaseManager {
|
||||||
private static final String COMPUTER_DB_NAME = "computers.db";
|
private static final String COMPUTER_DB_NAME = "computers2.db";
|
||||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||||
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
|
||||||
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
||||||
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
|
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
||||||
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
private static final String LOCAL_ADDRESS_COLUMN_NAME = "LocalAddress";
|
||||||
private static final String MAC_COLUMN_NAME = "Mac";
|
private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress";
|
||||||
|
private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress";
|
||||||
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__";
|
private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress";
|
||||||
|
|
||||||
private SQLiteDatabase computerDb;
|
private SQLiteDatabase computerDb;
|
||||||
|
|
||||||
@@ -38,20 +35,27 @@ public class ComputerDatabaseManager {
|
|||||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||||
}
|
}
|
||||||
initializeDb();
|
initializeDb(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
computerDb.close();
|
computerDb.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeDb() {
|
private void initializeDb(Context c) {
|
||||||
// Create tables if they aren't already there
|
// Create tables if they aren't already there
|
||||||
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," +
|
computerDb.execSQL(String.format((Locale)null,
|
||||||
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
|
"CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT)",
|
||||||
COMPUTER_TABLE_NAME,
|
COMPUTER_TABLE_NAME,
|
||||||
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
|
COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME,
|
||||||
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
|
LOCAL_ADDRESS_COLUMN_NAME, REMOTE_ADDRESS_COLUMN_NAME, MANUAL_ADDRESS_COLUMN_NAME,
|
||||||
|
MAC_ADDRESS_COLUMN_NAME));
|
||||||
|
|
||||||
|
// Move all computers from the old DB (if any) to the new one
|
||||||
|
List<ComputerDetails> oldComputers = LegacyDatabaseReader.migrateAllComputers(c);
|
||||||
|
for (ComputerDetails computer : oldComputers) {
|
||||||
|
updateComputer(computer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteComputer(String name) {
|
public void deleteComputer(String name) {
|
||||||
@@ -60,20 +64,19 @@ public class ComputerDatabaseManager {
|
|||||||
|
|
||||||
public boolean updateComputer(ComputerDetails details) {
|
public boolean updateComputer(ComputerDetails details) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
|
||||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||||
values.put(LOCAL_IP_COLUMN_NAME, ADDRESS_PREFIX+details.localAddress);
|
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||||
values.put(REMOTE_IP_COLUMN_NAME, ADDRESS_PREFIX+details.remoteAddress);
|
values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
|
||||||
values.put(MAC_COLUMN_NAME, details.macAddress);
|
values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
|
||||||
|
values.put(MANUAL_ADDRESS_COLUMN_NAME, details.manualAddress);
|
||||||
|
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
|
||||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails getComputerFromCursor(Cursor c) {
|
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = new ComputerDetails();
|
||||||
|
|
||||||
details.name = c.getString(0);
|
String uuidStr = c.getString(0);
|
||||||
|
|
||||||
String uuidStr = c.getString(1);
|
|
||||||
try {
|
try {
|
||||||
details.uuid = UUID.fromString(uuidStr);
|
details.uuid = UUID.fromString(uuidStr);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
@@ -81,43 +84,14 @@ public class ComputerDatabaseManager {
|
|||||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An earlier schema defined addresses as byte blobs. We'll
|
details.name = c.getString(1);
|
||||||
// gracefully migrate those to strings so we can store DNS names
|
details.localAddress = c.getString(2);
|
||||||
// too. To disambiguate, we'll need to prefix them with a string
|
details.remoteAddress = c.getString(3);
|
||||||
// greater than the allowable IP address length.
|
details.manualAddress = c.getString(4);
|
||||||
try {
|
details.macAddress = c.getString(5);
|
||||||
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
|
|
||||||
LimeLog.warning("DB: Legacy local address for "+details.name);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// This is probably a hostname/address with the prefix string
|
|
||||||
String stringData = c.getString(2);
|
|
||||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
|
||||||
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LimeLog.severe("DB: Corrupted local address for "+details.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
|
|
||||||
LimeLog.warning("DB: Legacy remote address for "+details.name);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// This is probably a hostname/address with the prefix string
|
|
||||||
String stringData = c.getString(3);
|
|
||||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
|
||||||
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LimeLog.severe("DB: Corrupted local address for "+details.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
details.macAddress = c.getString(4);
|
|
||||||
|
|
||||||
// This signifies we don't have dynamic state (like pair state)
|
// This signifies we don't have dynamic state (like pair state)
|
||||||
details.state = ComputerDetails.State.UNKNOWN;
|
details.state = ComputerDetails.State.UNKNOWN;
|
||||||
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
@@ -128,13 +102,11 @@ public class ComputerDatabaseManager {
|
|||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
ComputerDetails details = getComputerFromCursor(c);
|
ComputerDetails details = getComputerFromCursor(c);
|
||||||
|
|
||||||
// If a field is corrupt or missing, skip the database entry
|
// If a critical field is corrupt or missing, skip the database entry
|
||||||
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null ||
|
if (details.uuid == null) {
|
||||||
details.macAddress == null) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
computerList.add(details);
|
computerList.add(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +115,8 @@ public class ComputerDatabaseManager {
|
|||||||
return computerList;
|
return computerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerDetails getComputerByName(String name) {
|
public ComputerDetails getComputerByUUID(UUID uuid) {
|
||||||
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null);
|
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null);
|
||||||
if (!c.moveToFirst()) {
|
if (!c.moveToFirst()) {
|
||||||
// No matching computer
|
// No matching computer
|
||||||
c.close();
|
c.close();
|
||||||
@@ -154,9 +126,8 @@ public class ComputerDatabaseManager {
|
|||||||
ComputerDetails details = getComputerFromCursor(c);
|
ComputerDetails details = getComputerFromCursor(c);
|
||||||
c.close();
|
c.close();
|
||||||
|
|
||||||
// If a field is corrupt or missing, delete the database entry
|
// If a critical field is corrupt or missing, delete the database entry
|
||||||
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null ||
|
if (details.uuid == null) {
|
||||||
details.macAddress == null) {
|
|
||||||
deleteComputer(details.name);
|
deleteComputer(details.name);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ package com.limelight.computers;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -15,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.discovery.DiscoveryService;
|
import com.limelight.discovery.DiscoveryService;
|
||||||
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
@@ -95,7 +95,6 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
details.state = ComputerDetails.State.OFFLINE;
|
details.state = ComputerDetails.State.OFFLINE;
|
||||||
details.reachability = ComputerDetails.Reachability.OFFLINE;
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
releaseLocalDatabaseReference();
|
releaseLocalDatabaseReference();
|
||||||
@@ -109,7 +108,7 @@ public class ComputerManagerService extends Service {
|
|||||||
if (!newPc) {
|
if (!newPc) {
|
||||||
// Check if it's in the database because it could have been
|
// Check if it's in the database because it could have been
|
||||||
// removed after this was issued
|
// removed after this was issued
|
||||||
if (dbManager.getComputerByName(details.name) == null) {
|
if (dbManager.getComputerByUUID(details.uuid) == null) {
|
||||||
// It's gone
|
// It's gone
|
||||||
releaseLocalDatabaseReference();
|
releaseLocalDatabaseReference();
|
||||||
return false;
|
return false;
|
||||||
@@ -156,7 +155,7 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
t.setName("Polling thread for " + tuple.computer.localAddress);
|
t.setName("Polling thread for " + tuple.computer.name);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +176,6 @@ public class ComputerManagerService extends Service {
|
|||||||
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
|
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
|
||||||
LimeLog.info("Timing out polled state for "+tuple.computer.name);
|
LimeLog.info("Timing out polled state for "+tuple.computer.name);
|
||||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report this computer initially
|
// Report this computer initially
|
||||||
@@ -253,7 +251,6 @@ public class ComputerManagerService extends Service {
|
|||||||
// from wiping this change out
|
// from wiping this change out
|
||||||
synchronized (tuple.networkLock) {
|
synchronized (tuple.networkLock) {
|
||||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,17 +309,8 @@ public class ComputerManagerService extends Service {
|
|||||||
for (PollingTuple tuple : pollingTuples) {
|
for (PollingTuple tuple : pollingTuples) {
|
||||||
// Check if this is the same computer
|
// Check if this is the same computer
|
||||||
if (tuple.computer.uuid.equals(details.uuid)) {
|
if (tuple.computer.uuid.equals(details.uuid)) {
|
||||||
if (manuallyAdded) {
|
// Update the saved computer with potentially new details
|
||||||
// Update details anyway in case this machine has been re-added by IP
|
tuple.computer.update(details);
|
||||||
// after not being reachable by our existing information
|
|
||||||
tuple.computer.localAddress = details.localAddress;
|
|
||||||
tuple.computer.remoteAddress = details.remoteAddress;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// This indicates that mDNS discovered this address, so we
|
|
||||||
// should only apply the local address.
|
|
||||||
tuple.computer.localAddress = details.localAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a polling thread if polling is active
|
// Start a polling thread if polling is active
|
||||||
if (pollingActive && tuple.thread == null) {
|
if (pollingActive && tuple.thread == null) {
|
||||||
@@ -350,8 +338,20 @@ public class ComputerManagerService extends Service {
|
|||||||
public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
|
public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
|
||||||
// Setup a placeholder
|
// Setup a placeholder
|
||||||
ComputerDetails fakeDetails = new ComputerDetails();
|
ComputerDetails fakeDetails = new ComputerDetails();
|
||||||
fakeDetails.localAddress = addr;
|
|
||||||
fakeDetails.remoteAddress = addr;
|
if (manuallyAdded) {
|
||||||
|
// Add PC UI
|
||||||
|
fakeDetails.manualAddress = addr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// mDNS
|
||||||
|
fakeDetails.localAddress = addr;
|
||||||
|
|
||||||
|
// Since we're on the same network, we can use STUN to find
|
||||||
|
// our WAN address, which is also very likely the WAN address
|
||||||
|
// of the PC. We can use this later to connect remotely.
|
||||||
|
fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns();
|
||||||
|
}
|
||||||
|
|
||||||
// Block while we try to fill the details
|
// Block while we try to fill the details
|
||||||
try {
|
try {
|
||||||
@@ -438,6 +438,9 @@ public class ComputerManagerService extends Service {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the new active address
|
||||||
|
newDetails.activeAddress = address;
|
||||||
|
|
||||||
return newDetails;
|
return newDetails;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -447,6 +450,11 @@ public class ComputerManagerService extends Service {
|
|||||||
// Just try to establish a TCP connection to speculatively detect a running
|
// Just try to establish a TCP connection to speculatively detect a running
|
||||||
// GFE server
|
// GFE server
|
||||||
private boolean fastPollIp(String address) {
|
private boolean fastPollIp(String address) {
|
||||||
|
if (address == null) {
|
||||||
|
// Don't bother if our address is null
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Socket s = new Socket();
|
Socket s = new Socket();
|
||||||
try {
|
try {
|
||||||
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
||||||
@@ -475,12 +483,14 @@ public class ComputerManagerService extends Service {
|
|||||||
t.start();
|
t.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails.Reachability fastPollPc(final String localAddress, final String remoteAddress) throws InterruptedException {
|
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress) throws InterruptedException {
|
||||||
final boolean[] remoteInfo = new boolean[2];
|
final boolean[] remoteInfo = new boolean[2];
|
||||||
final boolean[] localInfo = new boolean[2];
|
final boolean[] localInfo = new boolean[2];
|
||||||
|
final boolean[] manualInfo = new boolean[2];
|
||||||
|
|
||||||
startFastPollThread(localAddress, localInfo);
|
startFastPollThread(localAddress, localInfo);
|
||||||
startFastPollThread(remoteAddress, remoteInfo);
|
startFastPollThread(remoteAddress, remoteInfo);
|
||||||
|
startFastPollThread(manualAddress, manualInfo);
|
||||||
|
|
||||||
// Check local first
|
// Check local first
|
||||||
synchronized (localInfo) {
|
synchronized (localInfo) {
|
||||||
@@ -489,174 +499,78 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (localInfo[1]) {
|
if (localInfo[1]) {
|
||||||
return ComputerDetails.Reachability.LOCAL;
|
return localAddress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now remote
|
// Now manual
|
||||||
|
synchronized (manualInfo) {
|
||||||
|
while (!manualInfo[0]) {
|
||||||
|
manualInfo.wait(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manualInfo[1]) {
|
||||||
|
return manualAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally, remote
|
||||||
synchronized (remoteInfo) {
|
synchronized (remoteInfo) {
|
||||||
while (!remoteInfo[0]) {
|
while (!remoteInfo[0]) {
|
||||||
remoteInfo.wait(500);
|
remoteInfo.wait(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteInfo[1]) {
|
if (remoteInfo[1]) {
|
||||||
return ComputerDetails.Reachability.REMOTE;
|
return remoteAddress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ComputerDetails.Reachability.OFFLINE;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAddressLikelyLocal(String str) {
|
|
||||||
try {
|
|
||||||
// This will tend to be wrong for IPv6 but falling back to
|
|
||||||
// remote will be fine in that case. For IPv4, it should be
|
|
||||||
// pretty accurate due to NAT prevalence.
|
|
||||||
InetAddress addr = InetAddress.getByName(str);
|
|
||||||
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
|
|
||||||
ComputerDetails polledDetails;
|
|
||||||
ComputerDetails.Reachability reachability;
|
|
||||||
|
|
||||||
if (details.localAddress.equals(details.remoteAddress)) {
|
|
||||||
reachability = isAddressLikelyLocal(details.localAddress) ?
|
|
||||||
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Do a TCP-level connection to the HTTP server to see if it's listening
|
|
||||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +")");
|
|
||||||
reachability = fastPollPc(details.localAddress, details.remoteAddress);
|
|
||||||
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
|
|
||||||
|
|
||||||
// If no connection could be established to either IP address, there's nothing we can do
|
|
||||||
if (reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
|
|
||||||
|
|
||||||
if (localFirst) {
|
|
||||||
polledDetails = tryPollIp(details, details.localAddress);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
polledDetails = tryPollIp(details, details.remoteAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
String reachableAddr = null;
|
|
||||||
if (polledDetails == null && !details.localAddress.equals(details.remoteAddress)) {
|
|
||||||
// Failed, so let's try the fallback
|
|
||||||
if (!localFirst) {
|
|
||||||
polledDetails = tryPollIp(details, details.localAddress);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
polledDetails = tryPollIp(details, details.remoteAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (polledDetails != null) {
|
|
||||||
// The fallback poll worked
|
|
||||||
reachableAddr = !localFirst ? details.localAddress : details.remoteAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (polledDetails != null) {
|
|
||||||
reachableAddr = localFirst ? details.localAddress : details.remoteAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reachableAddr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both addresses are the same, guess whether we're local based on
|
|
||||||
// IP address heuristics.
|
|
||||||
if (reachableAddr.equals(polledDetails.localAddress) &&
|
|
||||||
reachableAddr.equals(polledDetails.remoteAddress)) {
|
|
||||||
polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ?
|
|
||||||
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
else if (polledDetails.remoteAddress.equals(reachableAddr)) {
|
|
||||||
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
else if (polledDetails.localAddress.equals(reachableAddr)) {
|
|
||||||
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReachabilityTuple(polledDetails, reachableAddr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
||||||
ReachabilityTuple initialReachTuple = pollForReachability(details);
|
ComputerDetails polledDetails;
|
||||||
if (initialReachTuple == null) {
|
|
||||||
|
// Do a TCP-level connection to the HTTP server to see if it's listening.
|
||||||
|
// Do not write this address to details.activeAddress because:
|
||||||
|
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
|
||||||
|
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
|
||||||
|
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress +")");
|
||||||
|
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress);
|
||||||
|
LimeLog.info("Fast poll for "+details.name+" returned active address: "+details.activeAddress);
|
||||||
|
|
||||||
|
// If no connection could be established to either IP address, there's nothing we can do
|
||||||
|
if (candidateAddress == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
// Try using the active address from fast-poll
|
||||||
// Neither IP address reported in the serverinfo response was the one we used.
|
polledDetails = tryPollIp(details, candidateAddress);
|
||||||
// Poll again to see if we can contact this machine on either of its reported addresses.
|
if (polledDetails == null) {
|
||||||
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
|
// If that failed, try all unique addresses except what we've
|
||||||
if (confirmationReachTuple == null) {
|
// already tried
|
||||||
// Neither of those seem to work, so we'll hold onto the address that did work
|
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||||
initialReachTuple.computer.localAddress = initialReachTuple.reachableAddress;
|
uniqueAddresses.add(details.localAddress);
|
||||||
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
|
uniqueAddresses.add(details.remoteAddress);
|
||||||
}
|
uniqueAddresses.add(details.manualAddress);
|
||||||
else {
|
for (String addr : uniqueAddresses) {
|
||||||
// We got it on one of the returned addresses; replace the original reach tuple
|
if (addr == null || addr.equals(candidateAddress)) {
|
||||||
// with the new one
|
continue;
|
||||||
initialReachTuple = confirmationReachTuple;
|
}
|
||||||
|
polledDetails = tryPollIp(details, addr);
|
||||||
|
if (polledDetails != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save some details about the old state of the PC that we may wish
|
if (polledDetails != null) {
|
||||||
// to restore later.
|
details.update(polledDetails);
|
||||||
String savedMacAddress = details.macAddress;
|
return true;
|
||||||
String savedLocalAddress = details.localAddress;
|
|
||||||
String savedRemoteAddress = details.remoteAddress;
|
|
||||||
|
|
||||||
// If we got here, it's reachable
|
|
||||||
details.update(initialReachTuple.computer);
|
|
||||||
|
|
||||||
// If the new MAC address is empty, restore the old one (workaround for GFE bug)
|
|
||||||
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
|
|
||||||
LimeLog.info("MAC address was empty; using existing value: "+savedMacAddress);
|
|
||||||
details.macAddress = savedMacAddress;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// We never want to lose IP addresses by polling server info. If we get a poll back
|
return false;
|
||||||
// where localAddress == remoteAddress but savedLocalAddress != savedRemoteAddress,
|
|
||||||
// then we've lost an address in the polling and we should restore the one that's missing.
|
|
||||||
if (details.localAddress.equals(details.remoteAddress) &&
|
|
||||||
!savedLocalAddress.equals(savedRemoteAddress)) {
|
|
||||||
if (details.localAddress.equals(savedLocalAddress)) {
|
|
||||||
// Local addresses are identical, so put the old remote address back
|
|
||||||
details.remoteAddress = savedRemoteAddress;
|
|
||||||
}
|
|
||||||
else if (details.remoteAddress.equals(savedRemoteAddress)) {
|
|
||||||
// Remote addresses are identical, so put the old local address back
|
|
||||||
details.localAddress = savedLocalAddress;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Neither IP address match. Let's restore the remote address to be safe.
|
|
||||||
details.remoteAddress = savedRemoteAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update the reachability so the correct address is used
|
|
||||||
if (details.localAddress.equals(initialReachTuple.reachableAddress)) {
|
|
||||||
details.reachability = ComputerDetails.Reachability.LOCAL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
details.reachability = ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -841,7 +755,7 @@ public class ComputerManagerService extends Service {
|
|||||||
} while (waitPollingDelay());
|
} while (waitPollingDelay());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
thread.setName("App list polling thread for " + computer.localAddress);
|
thread.setName("App list polling thread for " + computer.name);
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.limelight.computers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class LegacyDatabaseReader {
|
||||||
|
private static final String COMPUTER_DB_NAME = "computers.db";
|
||||||
|
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||||
|
|
||||||
|
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__";
|
||||||
|
|
||||||
|
private static ComputerDetails getComputerFromCursor(Cursor c) {
|
||||||
|
ComputerDetails details = new ComputerDetails();
|
||||||
|
|
||||||
|
details.name = c.getString(0);
|
||||||
|
|
||||||
|
String uuidStr = c.getString(1);
|
||||||
|
try {
|
||||||
|
details.uuid = UUID.fromString(uuidStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted UUID for " + details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An earlier schema defined addresses as byte blobs. We'll
|
||||||
|
// gracefully migrate those to strings so we can store DNS names
|
||||||
|
// too. To disambiguate, we'll need to prefix them with a string
|
||||||
|
// greater than the allowable IP address length.
|
||||||
|
try {
|
||||||
|
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
|
||||||
|
LimeLog.warning("DB: Legacy local address for " + details.name);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// This is probably a hostname/address with the prefix string
|
||||||
|
String stringData = c.getString(2);
|
||||||
|
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||||
|
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
|
||||||
|
} else {
|
||||||
|
LimeLog.severe("DB: Corrupted local address for " + details.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
|
||||||
|
LimeLog.warning("DB: Legacy remote address for " + details.name);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// This is probably a hostname/address with the prefix string
|
||||||
|
String stringData = c.getString(3);
|
||||||
|
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||||
|
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
|
||||||
|
} else {
|
||||||
|
LimeLog.severe("DB: Corrupted remote address for " + details.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On older versions of Moonlight, this is typically where manual addresses got stored,
|
||||||
|
// so let's initialize it just to be safe.
|
||||||
|
details.manualAddress = details.remoteAddress;
|
||||||
|
|
||||||
|
details.macAddress = c.getString(4);
|
||||||
|
|
||||||
|
// This signifies we don't have dynamic state (like pair state)
|
||||||
|
details.state = ComputerDetails.State.UNKNOWN;
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ComputerDetails> getAllComputers(SQLiteDatabase db) {
|
||||||
|
Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null);
|
||||||
|
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
ComputerDetails details = getComputerFromCursor(c);
|
||||||
|
|
||||||
|
// If a critical field is corrupt or missing, skip the database entry
|
||||||
|
if (details.uuid == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
computerList.add(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.close();
|
||||||
|
|
||||||
|
return computerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ComputerDetails> migrateAllComputers(Context c) {
|
||||||
|
SQLiteDatabase computerDb = null;
|
||||||
|
try {
|
||||||
|
// Open the existing database
|
||||||
|
computerDb = SQLiteDatabase.openDatabase(c.getDatabasePath(COMPUTER_DB_NAME).getPath(), null, SQLiteDatabase.OPEN_READONLY);
|
||||||
|
return getAllComputers(computerDb);
|
||||||
|
} catch (SQLiteException e) {
|
||||||
|
return new LinkedList<ComputerDetails>();
|
||||||
|
} finally {
|
||||||
|
// Close and delete the old DB
|
||||||
|
if (computerDb != null) {
|
||||||
|
computerDb.close();
|
||||||
|
}
|
||||||
|
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
imgView.setAlpha(0.4f);
|
imgView.setAlpha(0.4f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
if (obj.details.state == ComputerDetails.State.UNKNOWN) {
|
||||||
prgView.setVisibility(View.VISIBLE);
|
prgView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ public class StreamView extends SurfaceView {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||||
// This callbacks allows us to override dumb IME behavior like when
|
// This callbacks allows us to override dumb IME behavior like when
|
||||||
// Samsung's default keyboard consumes Shift+Space. We'll process
|
// Samsung's default keyboard consumes Shift+Space.
|
||||||
// the input event directly if any modifier keys are down.
|
if (inputCallbacks != null) {
|
||||||
if (inputCallbacks != null && event.getModifiers() != 0) {
|
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
if (inputCallbacks.handleKeyDown(event)) {
|
if (inputCallbacks.handleKeyDown(event)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import java.net.UnknownHostException;
|
|||||||
|
|
||||||
public class ServerHelper {
|
public class ServerHelper {
|
||||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
||||||
return computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
return computer.activeAddress;
|
||||||
computer.localAddress : computer.remoteAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||||
@@ -30,8 +29,7 @@ public class ServerHelper {
|
|||||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, getCurrentAddressFromComputer(computer).equals(computer.remoteAddress));
|
||||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
|
||||||
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
|
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
|
||||||
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
|
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
|
||||||
return intent;
|
return intent;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.view.View;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -135,4 +136,32 @@ public class UiHelper {
|
|||||||
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
|
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void displayDeletePcConfirmationDialog(Activity parent, ComputerDetails computer, final Runnable onYes, final Runnable onNo) {
|
||||||
|
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
switch (which){
|
||||||
|
case DialogInterface.BUTTON_POSITIVE:
|
||||||
|
if (onYes != null) {
|
||||||
|
onYes.run();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DialogInterface.BUTTON_NEGATIVE:
|
||||||
|
if (onNo != null) {
|
||||||
|
onNo.run();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
||||||
|
builder.setMessage(parent.getResources().getString(R.string.delete_pc_msg))
|
||||||
|
.setTitle(computer.name)
|
||||||
|
.setPositiveButton(parent.getResources().getString(R.string.yes), dialogClickListener)
|
||||||
|
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,6 @@
|
|||||||
<string name="lost_connection">Conexión perdida</string>
|
<string name="lost_connection">Conexión perdida</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Aplicaciones en</string>
|
|
||||||
<string name="applist_menu_resume">Reanudar sesión</string>
|
<string name="applist_menu_resume">Reanudar sesión</string>
|
||||||
<string name="applist_menu_quit">Cerrar sesión</string>
|
<string name="applist_menu_quit">Cerrar sesión</string>
|
||||||
<string name="applist_menu_quit_and_start">Cerrar juego actual e iniciar</string>
|
<string name="applist_menu_quit_and_start">Cerrar juego actual e iniciar</string>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
<!-- Shortcut strings -->
|
<!-- Shortcut strings -->
|
||||||
<string name="scut_deleted_pc">PC supprimé</string>
|
<string name="scut_deleted_pc">PC supprimé</string>
|
||||||
<string name="scut_not_paired">PC non appairé</string>
|
<string name="scut_not_paired">PC non appairé</string>
|
||||||
|
<string name="scut_pc_not_found">PC non trouvé</string>
|
||||||
|
<string name="scut_invalid_uuid">Le PC fourni n\'est pas valide</string>
|
||||||
|
<string name="scut_invalid_app_id">L\'application fournie n\'est pas valide</string>
|
||||||
|
|
||||||
<!-- Help strings -->
|
<!-- Help strings -->
|
||||||
<string name="help_loading_title">Visionneuse d\'aide</string>
|
<string name="help_loading_title">Visionneuse d\'aide</string>
|
||||||
@@ -14,6 +17,7 @@
|
|||||||
<string name="pcview_menu_unpair_pc">Désappairer</string>
|
<string name="pcview_menu_unpair_pc">Désappairer</string>
|
||||||
<string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string>
|
<string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string>
|
||||||
<string name="pcview_menu_delete_pc">Supprimer PC</string>
|
<string name="pcview_menu_delete_pc">Supprimer PC</string>
|
||||||
|
<string name="pcview_menu_details">Voir les détails</string>
|
||||||
|
|
||||||
<!-- Pair messages -->
|
<!-- Pair messages -->
|
||||||
<string name="pairing">Appariement…</string>
|
<string name="pairing">Appariement…</string>
|
||||||
@@ -47,6 +51,12 @@
|
|||||||
<string name="error_404">GFE renvoi une erreur HTTP 404. Assurez-vous que votre PC exécute un GPU pris en charge.
|
<string name="error_404">GFE renvoi une erreur HTTP 404. Assurez-vous que votre PC exécute un GPU pris en charge.
|
||||||
L\'utilisation d\'un logiciel de bureau à distance peut également provoquer cette erreur. Essayez de redémarrer votre machine ou de réinstaller GFE.
|
L\'utilisation d\'un logiciel de bureau à distance peut également provoquer cette erreur. Essayez de redémarrer votre machine ou de réinstaller GFE.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="title_decoding_error">Le décodeur vidéo s\'est écrasé</string>
|
||||||
|
<string name="message_decoding_error">Moonlight s\'est écrasé en raison d\'une incompatibilité avec le décodeur vidéo de cet appareil. Assurez-vous que GeForce Experience soit mis à jour vers la dernière version sur votre PC. Essayez de régler les paramètres de diffusion si les plantages continuent.</string>
|
||||||
|
<string name="title_decoding_reset">Paramètres vidéo réinitialiser</string>
|
||||||
|
<string name="message_decoding_reset">Le décodeur vidéo de votre appareil continue de planter avec les paramètres de diffusion sélectionnés. Vos paramètres de diffusion ont été réinitialisés par défaut.</string>
|
||||||
|
<string name="error_usb_prohibited">L\'accès USB est interdit par votre appareil. Vérifiez vos paramètres Knox ou MDM.</string>
|
||||||
|
<string name="unable_to_pin_shortcut">Votre lanceur actuel ne permet pas de créer des raccourcis épinglés.</string>
|
||||||
|
|
||||||
<!-- Start application messages -->
|
<!-- Start application messages -->
|
||||||
<string name="conn_establishing_title">Établissement de la connexion</string>
|
<string name="conn_establishing_title">Établissement de la connexion</string>
|
||||||
@@ -63,20 +73,22 @@
|
|||||||
|
|
||||||
<!-- General strings -->
|
<!-- General strings -->
|
||||||
<string name="ip_hint">Adresse IP de GeForce PC</string>
|
<string name="ip_hint">Adresse IP de GeForce PC</string>
|
||||||
<string name="searching_pc">Recherche de PC avec GameStream en cours...\n\n
|
<string name="searching_pc">Recherche de PC avec GameStream en cours…\n\n
|
||||||
Assurez-vous que GameStream est activé dans les paramètres GeForce Experience SHIELD.</string>
|
Assurez-vous que GameStream est activé dans les paramètres GeForce Experience SHIELD.</string>
|
||||||
<string name="yes">Oui</string>
|
<string name="yes">Oui</string>
|
||||||
<string name="no">Non</string>
|
<string name="no">Non</string>
|
||||||
<string name="lost_connection">Perte de connexion avec le PC</string>
|
<string name="lost_connection">Perte de connexion avec le PC</string>
|
||||||
|
<string name="title_details">Détails</string>
|
||||||
<string name="help">Aide</string>
|
<string name="help">Aide</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Applications sur</string>
|
|
||||||
<string name="applist_connect_msg">Connexion au PC…</string>
|
<string name="applist_connect_msg">Connexion au PC…</string>
|
||||||
<string name="applist_menu_resume">Reprise de la session</string>
|
<string name="applist_menu_resume">Reprise de la session</string>
|
||||||
<string name="applist_menu_quit">Quitter la session</string>
|
<string name="applist_menu_quit">Quitter la session</string>
|
||||||
<string name="applist_menu_quit_and_start">Quitter le jeu actuel et démarrer</string>
|
<string name="applist_menu_quit_and_start">Quitter le jeu actuel et démarrer</string>
|
||||||
<string name="applist_menu_cancel">Annuler</string>
|
<string name="applist_menu_cancel">Annuler</string>
|
||||||
|
<string name="applist_menu_details">Voir les détails</string>
|
||||||
|
<string name="applist_menu_scut">Créer un raccourci</string>
|
||||||
<string name="applist_refresh_title">Liste des applications</string>
|
<string name="applist_refresh_title">Liste des applications</string>
|
||||||
<string name="applist_refresh_msg">Actualisation des applications…</string>
|
<string name="applist_refresh_msg">Actualisation des applications…</string>
|
||||||
<string name="applist_refresh_error_title">Erreur</string>
|
<string name="applist_refresh_error_title">Erreur</string>
|
||||||
@@ -85,6 +97,7 @@
|
|||||||
<string name="applist_quit_success">Fermeture avec succès</string>
|
<string name="applist_quit_success">Fermeture avec succès</string>
|
||||||
<string name="applist_quit_fail">Échec de la fermeture</string>
|
<string name="applist_quit_fail">Échec de la fermeture</string>
|
||||||
<string name="applist_quit_confirmation">Voulez-vous vraiment quitter l\'application en cours d\'exécution? Toutes les données non enregistrées seront perdues.</string>
|
<string name="applist_quit_confirmation">Voulez-vous vraiment quitter l\'application en cours d\'exécution? Toutes les données non enregistrées seront perdues.</string>
|
||||||
|
<string name="applist_details_id">ID app:</string>
|
||||||
|
|
||||||
<!-- Add computer manually activity -->
|
<!-- Add computer manually activity -->
|
||||||
<string name="title_add_pc">Ajouter un PC manuellement</string>
|
<string name="title_add_pc">Ajouter un PC manuellement</string>
|
||||||
@@ -93,6 +106,7 @@
|
|||||||
<string name="addpc_success">Ajouté avec succès de l\'ordinateur</string>
|
<string name="addpc_success">Ajouté avec succès de l\'ordinateur</string>
|
||||||
<string name="addpc_unknown_host">Impossible de résoudre l\'adresse du PC. Assurez-vous que vous n\'avez pas fait une faute de frappe dans l\'adresse.</string>
|
<string name="addpc_unknown_host">Impossible de résoudre l\'adresse du PC. Assurez-vous que vous n\'avez pas fait une faute de frappe dans l\'adresse.</string>
|
||||||
<string name="addpc_enter_ip">Vous devez entrer une adresse IP</string>
|
<string name="addpc_enter_ip">Vous devez entrer une adresse IP</string>
|
||||||
|
<string name="addpc_wrong_sitelocal">Cette adresse ne semble pas correcte. Vous devez utiliser l\'adresse IP publique de votre routeur pour la diffusion en continu sur Internet..</string>
|
||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="category_basic_settings">Paramètres de base</string>
|
<string name="category_basic_settings">Paramètres de base</string>
|
||||||
@@ -104,6 +118,8 @@
|
|||||||
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
|
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
|
||||||
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
|
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
|
||||||
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>
|
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>
|
||||||
|
<string name="title_checkbox_enable_pip">Activer le mode observateur dans l\'image</string>
|
||||||
|
<string name="summary_checkbox_enable_pip">Permet de visualiser le flux (sans le contrôleur) tout en multitâche</string>
|
||||||
|
|
||||||
<string name="category_audio_settings">Paramètres audio</string>
|
<string name="category_audio_settings">Paramètres audio</string>
|
||||||
<string name="title_checkbox_51_surround">Activer son surround 5.1</string>
|
<string name="title_checkbox_51_surround">Activer son surround 5.1</string>
|
||||||
@@ -116,12 +132,21 @@
|
|||||||
<string name="suffix_seekbar_deadzone">%</string>
|
<string name="suffix_seekbar_deadzone">%</string>
|
||||||
<string name="title_checkbox_xb1_driver">Pilote de contrôleur Xbox 360/One</string>
|
<string name="title_checkbox_xb1_driver">Pilote de contrôleur Xbox 360/One</string>
|
||||||
<string name="summary_checkbox_xb1_driver">Active un pilote USB intégré pour les périphériques sans prise en charge du contrôleur Xbox natif.</string>
|
<string name="summary_checkbox_xb1_driver">Active un pilote USB intégré pour les périphériques sans prise en charge du contrôleur Xbox natif.</string>
|
||||||
|
<string name="title_checkbox_usb_bind_all">Ignorer le support du contrôleur Android</string>
|
||||||
|
<string name="summary_checkbox_usb_bind_all">Force le pilote USB de Moonlight à prendre en charge tous les gamepads Xbox pris en charge</string>
|
||||||
|
<string name="title_checkbox_mouse_emulation">Emulation de la souris via le gamepad</string>
|
||||||
|
<string name="summary_checkbox_mouse_emulation">Appuyez longuement sur le bouton Start pour faire basculer la manette de jeu en mode souris.</string>
|
||||||
|
|
||||||
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
|
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
|
||||||
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
|
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
|
||||||
<string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string>
|
<string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string>
|
||||||
<string name="title_only_l3r3">Montre seulement L3 et R3</string>
|
<string name="title_only_l3r3">Montre seulement L3 et R3</string>
|
||||||
<string name="summary_only_l3r3">Cacher tout sauf L3 et R3</string>
|
<string name="summary_only_l3r3">Cacher tout sauf L3 et R3</string>
|
||||||
|
<string name="title_reset_osc">Effacer la disposition des commandes à l\'écran sauvegardée</string>
|
||||||
|
<string name="summary_reset_osc">Rétablit la taille et la position par défaut de tous les contrôles à l\'écran</string>
|
||||||
|
<string name="dialog_title_reset_osc">Réinitialiser la mise en page</string>
|
||||||
|
<string name="dialog_text_reset_osc">Êtes-vous sûr de vouloir supprimer la disposition des commandes à l\'écran que vous avez sauvegardée?</string>
|
||||||
|
<string name="toast_reset_osc_success">Les contrôles à l\'écran sont réinitialisés</string>
|
||||||
|
|
||||||
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
|
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
|
||||||
<string name="title_language_list">Langue</string>
|
<string name="title_language_list">Langue</string>
|
||||||
@@ -139,5 +164,8 @@
|
|||||||
|
|
||||||
<string name="category_advanced_settings">Réglages avancés</string>
|
<string name="category_advanced_settings">Réglages avancés</string>
|
||||||
<string name="title_video_format">Modifier les paramètres H.265</string>
|
<string name="title_video_format">Modifier les paramètres H.265</string>
|
||||||
<string name="summary_video_format">H.265 réduit les exigences de bande passante vidéo, mais requiert un périphérique très récent.</string>
|
<string name="summary_video_format">H.265 réduit les besoins en bande passante vidéo mais nécessite un périphérique très récent</string>
|
||||||
|
<string name="title_enable_hdr">Activer le HDR (expérimental)</string>
|
||||||
|
<string name="summary_enable_hdr">Diffuser du HDR lorsque le jeu et le processeur graphique du PC le prennent en charge. HDR nécessite un GPU série GTX 1000 ou une version ultérieure.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -76,7 +76,6 @@
|
|||||||
<string name="help">Assistenza</string>
|
<string name="help">Assistenza</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Applicazioni su</string>
|
|
||||||
<string name="applist_connect_msg">Connessione al PC in corso…</string>
|
<string name="applist_connect_msg">Connessione al PC in corso…</string>
|
||||||
<string name="applist_menu_resume">Riprendi sessione</string>
|
<string name="applist_menu_resume">Riprendi sessione</string>
|
||||||
<string name="applist_menu_quit">Chiudi sessione</string>
|
<string name="applist_menu_quit">Chiudi sessione</string>
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
<string name="lost_connection">コンピュータとの接続が失われました</string>
|
<string name="lost_connection">コンピュータとの接続が失われました</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">ゲーム</string>
|
|
||||||
<string name="applist_menu_resume">セッションを続ける</string>
|
<string name="applist_menu_resume">セッションを続ける</string>
|
||||||
<string name="applist_menu_quit">セッションを終了する</string>
|
<string name="applist_menu_quit">セッションを終了する</string>
|
||||||
<string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string>
|
<string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string>
|
||||||
|
|||||||
@@ -71,7 +71,6 @@
|
|||||||
<string name="help">도움말</string>
|
<string name="help">도움말</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">앱 사용 가능</string>
|
|
||||||
<string name="applist_connect_msg">PC에 연결중…</string>
|
<string name="applist_connect_msg">PC에 연결중…</string>
|
||||||
<string name="applist_menu_resume">세션 계속</string>
|
<string name="applist_menu_resume">세션 계속</string>
|
||||||
<string name="applist_menu_quit">세션 종료</string>
|
<string name="applist_menu_quit">세션 종료</string>
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
<string name="lost_connection">Verbinding met PC verloren</string>
|
<string name="lost_connection">Verbinding met PC verloren</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Apps op</string>
|
|
||||||
<string name="applist_menu_resume">Hervat Sessie</string>
|
<string name="applist_menu_resume">Hervat Sessie</string>
|
||||||
<string name="applist_menu_quit">Stop Sessie</string>
|
<string name="applist_menu_quit">Stop Sessie</string>
|
||||||
<string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string>
|
<string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string>
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
<string name="lost_connection">Потеряно соединение с PC</string>
|
<string name="lost_connection">Потеряно соединение с PC</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Приложения на</string>
|
|
||||||
<string name="applist_menu_resume">Возобновить сессию</string>
|
<string name="applist_menu_resume">Возобновить сессию</string>
|
||||||
<string name="applist_menu_quit">Выйти из сессии</string>
|
<string name="applist_menu_quit">Выйти из сессии</string>
|
||||||
<string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string>
|
<string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
-->
|
-->
|
||||||
<style name="AppBaseTheme" parent="android:Theme.Material">
|
<style name="AppBaseTheme" parent="android:Theme.Material">
|
||||||
<!-- API 21 theme customizations can go here. -->
|
<!-- API 21 theme customizations can go here. -->
|
||||||
|
<item name="android:statusBarColor">#212121</item>
|
||||||
|
<item name="android:navigationBarColor">#212121</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -71,7 +71,6 @@
|
|||||||
<string name="help">帮助</string>
|
<string name="help">帮助</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Apps on</string>
|
|
||||||
<string name="applist_connect_msg"> 连接到电脑中…… </string>
|
<string name="applist_connect_msg"> 连接到电脑中…… </string>
|
||||||
<string name="applist_menu_resume"> 恢复串流 </string>
|
<string name="applist_menu_resume"> 恢复串流 </string>
|
||||||
<string name="applist_menu_quit"> 退出串流 </string>
|
<string name="applist_menu_quit"> 退出串流 </string>
|
||||||
|
|||||||
@@ -71,7 +71,6 @@
|
|||||||
<string name="help">幫助</string>
|
<string name="help">幫助</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Apps on</string>
|
|
||||||
<string name="applist_connect_msg"> 連接到電腦中…… </string>
|
<string name="applist_connect_msg"> 連接到電腦中…… </string>
|
||||||
<string name="applist_menu_resume"> 恢復串流 </string>
|
<string name="applist_menu_resume"> 恢復串流 </string>
|
||||||
<string name="applist_menu_quit"> 退出串流 </string>
|
<string name="applist_menu_quit"> 退出串流 </string>
|
||||||
|
|||||||
@@ -83,9 +83,9 @@
|
|||||||
<string name="lost_connection">Lost connection to PC</string>
|
<string name="lost_connection">Lost connection to PC</string>
|
||||||
<string name="title_details">Details</string>
|
<string name="title_details">Details</string>
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
|
<string name="delete_pc_msg">Are you sure you want to delete this PC?</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
<string name="title_applist">Apps on</string>
|
|
||||||
<string name="applist_connect_msg">Connecting to PC…</string>
|
<string name="applist_connect_msg">Connecting to PC…</string>
|
||||||
<string name="applist_menu_resume">Resume Session</string>
|
<string name="applist_menu_resume">Resume Session</string>
|
||||||
<string name="applist_menu_quit">Quit Session</string>
|
<string name="applist_menu_quit">Quit Session</string>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
// Launch evdev_reader directly via SU
|
// Launch evdev_reader directly via SU
|
||||||
try {
|
try {
|
||||||
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd);
|
su = new ProcessBuilder("su", "-c", evdevReaderCmd).start();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
reportDeviceNotRooted();
|
reportDeviceNotRooted();
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -151,7 +151,15 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EvdevEvent.BTN_SIDE:
|
case EvdevEvent.BTN_SIDE:
|
||||||
|
listener.mouseButtonEvent(EvdevListener.BUTTON_X1,
|
||||||
|
event.value != 0);
|
||||||
|
break;
|
||||||
|
|
||||||
case EvdevEvent.BTN_EXTRA:
|
case EvdevEvent.BTN_EXTRA:
|
||||||
|
listener.mouseButtonEvent(EvdevListener.BUTTON_X2,
|
||||||
|
event.value != 0);
|
||||||
|
break;
|
||||||
|
|
||||||
case EvdevEvent.BTN_FORWARD:
|
case EvdevEvent.BTN_FORWARD:
|
||||||
case EvdevEvent.BTN_BACK:
|
case EvdevEvent.BTN_BACK:
|
||||||
case EvdevEvent.BTN_TASK:
|
case EvdevEvent.BTN_TASK:
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.2.0-beta04'
|
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule moonlight-common updated: 416105b12f...dd6e76c0f2.
Reference in new issue
Block a user