2018-07-04 23:40:21 +00:00
import QtQuick 2.9
import QtQuick . Controls 2.2
2020-05-02 01:34:15 +00:00
import QtQuick . Layouts 1.3
2018-07-04 23:40:21 +00:00
import ComputerModel 1.0
2018-07-06 05:08:55 +00:00
import ComputerManager 1.0
2019-03-28 01:13:20 +00:00
import StreamingPreferences 1.0
2019-05-19 18:08:23 +00:00
import SdlGamepadKeyNavigation 1.0
2018-07-06 05:08:55 +00:00
2019-04-06 18:48:58 +00:00
CenteredGridView {
2018-07-06 06:12:55 +00:00
property ComputerModel computerModel : createModel ( )
2018-07-09 06:05:36 +00:00
id: pcGrid
2018-09-23 22:16:27 +00:00
focus: true
activeFocusOnTab: true
2019-03-27 08:28:46 +00:00
topMargin: 20
bottomMargin: 5
2019-04-19 04:03:37 +00:00
cellWidth: 310 ; cellHeight: 330 ;
2020-11-21 17:42:16 +00:00
objectName: qsTr ( "Computers" )
2018-07-06 03:11:35 +00:00
2019-02-23 06:44:09 +00:00
Component.onCompleted: {
// Don't show any highlighted item until interacting with them.
// We do this here instead of onActivated to avoid losing the user's
// selection when backing out of a different page of the app.
currentIndex = - 1
}
2020-08-29 19:59:13 +00:00
// Note: Any initialization done here that is critical for streaming must
// also be done in CliStartStreamSegue.qml, since this code does not run
// for command-line initiated streams.
2019-02-23 06:14:06 +00:00
StackView.onActivated: {
2018-07-06 07:34:16 +00:00
// Setup signals on CM
ComputerManager . computerAddCompleted . connect ( addComplete )
2019-05-19 18:08:23 +00:00
// This is a bit of a hack to do this here as opposed to main.qml, but
// we need it enabled before calling getConnectedGamepads() and PcView
// is never destroyed, so it should be okay.
SdlGamepadKeyNavigation . enable ( )
// Highlight the first item if a gamepad is connected
if ( currentIndex == - 1 && SdlGamepadKeyNavigation . getConnectedGamepads ( ) > 0 ) {
currentIndex = 0
}
2018-07-06 05:08:55 +00:00
}
2019-02-23 06:14:06 +00:00
StackView.onDeactivating: {
ComputerManager . computerAddCompleted . disconnect ( addComplete )
}
2018-07-06 06:12:55 +00:00
function pairingComplete ( error )
{
// Close the PIN dialog
pairDialog . close ( )
// Display a failed dialog if we got an error
2018-08-30 03:59:19 +00:00
if ( error !== undefined ) {
2018-07-06 07:34:16 +00:00
errorDialog . text = error
2019-03-23 20:51:34 +00:00
errorDialog . helpText = ""
2018-07-06 07:34:16 +00:00
errorDialog . open ( )
}
}
2020-08-09 01:11:25 +00:00
function addComplete ( success , detectedPortBlocking )
2018-07-06 07:34:16 +00:00
{
if ( ! success ) {
2020-11-21 17:42:16 +00:00
errorDialog . text = qsTr ( "Unable to connect to the specified PC." )
2020-08-09 01:11:25 +00:00
if ( detectedPortBlocking ) {
2020-11-21 19:15:54 +00:00
errorDialog . text += "\n\n" + qsTr ( "This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network." )
2020-08-09 01:11:25 +00:00
}
else {
2020-11-21 17:42:16 +00:00
errorDialog . helpText = qsTr ( "Click the Help button for possible solutions." )
2020-08-09 01:11:25 +00:00
}
2018-07-06 07:34:16 +00:00
errorDialog . open ( )
2018-07-06 06:12:55 +00:00
}
}
2018-07-06 05:08:55 +00:00
function createModel ( )
{
var model = Qt . createQmlObject ( 'import ComputerModel 1.0; ComputerModel {}' , parent , '' )
model . initialize ( ComputerManager )
2018-07-06 06:12:55 +00:00
model . pairingCompleted . connect ( pairingComplete )
2020-08-09 03:25:26 +00:00
model . connectionTestCompleted . connect ( testConnectionDialog . connectionTestComplete )
2018-07-06 05:08:55 +00:00
return model
}
2019-03-28 01:13:20 +00:00
Row {
anchors.centerIn: parent
spacing: 5
visible: pcGrid . count === 0
BusyIndicator {
id: searchSpinner
visible: StreamingPreferences . enableMdns
}
Label {
height: searchSpinner . height
elide: Label . ElideRight
2020-11-21 17:42:16 +00:00
text: StreamingPreferences . enableMdns ? qsTr ( "Searching for PCs with NVIDIA GameStream enabled..." )
: qsTr ( "Automatic PC discovery is disabled. Add your PC manually." )
2019-03-28 01:13:20 +00:00
font.pointSize: 20
verticalAlignment: Text . AlignVCenter
wrapMode: Text . Wrap
}
}
2018-08-02 04:29:03 +00:00
model: computerModel
2018-07-06 03:11:35 +00:00
2018-09-23 22:16:27 +00:00
delegate: NavigableItemDelegate {
2019-04-19 04:03:37 +00:00
width: 300 ; height: 320 ;
2018-09-23 22:16:27 +00:00
grid: pcGrid
2018-07-06 03:11:35 +00:00
Image {
id: pcIcon
2018-08-05 19:47:08 +00:00
anchors.horizontalCenter: parent . horizontalCenter
2019-11-06 01:06:57 +00:00
source: "qrc:/res/desktop_windows-48px.svg"
2018-07-06 03:11:35 +00:00
sourceSize {
width: 200
height: 200
2018-07-04 23:40:21 +00:00
}
2018-07-06 03:11:35 +00:00
}
2018-08-05 18:47:14 +00:00
Image {
// TODO: Tooltip
id: stateIcon
2018-08-05 19:47:08 +00:00
anchors.horizontalCenter: pcIcon . horizontalCenter
anchors.verticalCenter: pcIcon . verticalCenter
2019-11-06 01:06:57 +00:00
anchors.verticalCenterOffset: - 15
2019-03-27 04:31:51 +00:00
visible: ! model . statusUnknown && ( ! model . online || ! model . paired )
2018-08-05 18:47:14 +00:00
source: ! model . online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
sourceSize {
2018-08-05 19:47:08 +00:00
width: 75
height: 75
2018-08-05 18:47:14 +00:00
}
}
2018-08-05 19:13:08 +00:00
BusyIndicator {
id: statusUnknownSpinner
2018-08-05 19:47:08 +00:00
anchors.horizontalCenter: pcIcon . horizontalCenter
anchors.verticalCenter: pcIcon . verticalCenter
2019-11-06 01:06:57 +00:00
anchors.verticalCenterOffset: - 15
2018-08-05 19:47:08 +00:00
width: 75
height: 75
2019-03-27 04:31:51 +00:00
visible: model . statusUnknown
2018-08-05 19:13:08 +00:00
}
2018-11-22 10:35:25 +00:00
Label {
2018-07-06 03:11:35 +00:00
id: pcNameText
text: model . name
width: parent . width
anchors.top: pcIcon . bottom
2019-04-19 04:03:37 +00:00
anchors.bottom: parent . bottom
2018-07-07 23:47:39 +00:00
font.pointSize: 36
2018-07-06 03:11:35 +00:00
horizontalAlignment: Text . AlignHCenter
2018-07-07 23:47:39 +00:00
wrapMode: Text . Wrap
2019-04-19 04:03:37 +00:00
elide: Text . ElideRight
2018-07-06 03:11:35 +00:00
}
2018-07-04 23:40:21 +00:00
2019-02-10 03:59:01 +00:00
NavigableMenu {
2018-07-29 23:04:45 +00:00
id: pcContextMenu
2020-08-02 04:06:01 +00:00
NavigableMenuItem {
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "View Apps" )
2020-08-02 04:06:01 +00:00
onTriggered: {
var component = Qt . createComponent ( "AppView.qml" )
var appView = component . createObject ( stackView , { "computerIndex" : index , "objectName" : model . name } )
stackView . push ( appView )
}
visible: model . online && model . paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "View Hidden Apps" )
2020-08-02 04:06:01 +00:00
onTriggered: {
var component = Qt . createComponent ( "AppView.qml" )
var appView = component . createObject ( stackView , { "computerIndex" : index , "objectName" : model . name , "showHiddenGames" : true } )
stackView . push ( appView )
}
visible: model . online && model . paired
}
2018-09-24 02:06:26 +00:00
NavigableMenuItem {
2019-02-10 03:59:01 +00:00
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "Wake PC" )
2020-08-09 01:32:36 +00:00
onTriggered: computerModel . wakeComputer ( index )
visible: ! model . online && model . wakeable
2018-07-29 23:04:45 +00:00
}
2020-08-09 03:25:26 +00:00
NavigableMenuItem {
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "Test Network" )
2020-08-09 03:25:26 +00:00
onTriggered: {
computerModel . testConnectionForComputer ( index )
testConnectionDialog . open ( )
}
}
2020-05-02 01:34:15 +00:00
NavigableMenuItem {
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "Rename PC" )
2020-05-02 01:34:15 +00:00
onTriggered: {
renamePcDialog . pcIndex = index
renamePcDialog . originalName = model . name
renamePcDialog . open ( )
}
}
2019-02-10 03:59:01 +00:00
NavigableMenuItem {
parentMenu: pcContextMenu
2020-11-21 17:42:16 +00:00
text: qsTr ( "Delete PC" )
2020-08-09 01:32:36 +00:00
onTriggered: {
deletePcDialog . pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog . open ( )
}
2019-02-10 03:59:01 +00:00
}
2018-07-29 23:04:45 +00:00
}
2018-09-06 00:08:27 +00:00
onClicked: {
2019-03-27 04:31:51 +00:00
if ( model . online ) {
2018-09-06 00:08:27 +00:00
if ( model . paired ) {
// go to game view
var component = Qt . createComponent ( "AppView.qml" )
var appView = component . createObject ( stackView , { "computerIndex" : index , "objectName" : model . name } )
stackView . push ( appView )
}
else {
if ( ! model . busy ) {
2020-07-12 20:19:26 +00:00
var pin = computerModel . generatePinString ( )
2018-09-06 00:08:27 +00:00
// Kick off pairing in the background
computerModel . pairComputer ( index , pin )
// Display the pairing dialog
pairDialog . pin = pin
pairDialog . open ( )
2018-07-06 03:37:51 +00:00
}
2018-09-06 00:08:27 +00:00
else {
// cannot pair while something is streaming or attempting to pair
2020-11-21 17:42:16 +00:00
errorDialog . text = qsTr ( "You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again." )
2019-03-23 20:51:34 +00:00
errorDialog . helpText = ""
2018-09-06 00:08:27 +00:00
errorDialog . open ( )
2018-07-06 03:37:51 +00:00
}
}
2018-09-06 00:08:27 +00:00
} else if ( ! model . online ) {
2018-09-30 20:41:32 +00:00
// Using open() here because it may be activated by keyboard
2018-09-06 00:08:27 +00:00
pcContextMenu . open ( )
}
}
2019-01-27 07:11:09 +00:00
onPressAndHold: {
2019-03-27 04:31:51 +00:00
// popup() ensures the menu appears under the mouse cursor
if ( pcContextMenu . popup ) {
pcContextMenu . popup ( )
}
else {
// Qt 5.9 doesn't have popup()
pcContextMenu . open ( )
2019-01-27 07:11:09 +00:00
}
}
2018-09-06 00:08:27 +00:00
MouseArea {
anchors.fill: parent
acceptedButtons: Qt . RightButton ;
onClicked: {
2019-01-27 07:11:09 +00:00
parent . onPressAndHold ( )
2018-07-05 01:48:09 +00:00
}
}
2018-09-30 20:41:32 +00:00
Keys.onMenuPressed: {
2019-03-27 04:31:51 +00:00
// We must use open() here so the menu is positioned on
// the ItemDelegate and not where the mouse cursor is
pcContextMenu . open ( )
2018-09-30 20:41:32 +00:00
}
2018-07-05 01:48:09 +00:00
}
2019-03-23 20:51:34 +00:00
ErrorMessageDialog {
2018-07-06 07:34:16 +00:00
id: errorDialog
2019-03-31 22:16:48 +00:00
// Using Setup-Guide here instead of Troubleshooting because it's likely that users
// will arrive here by forgetting to enable GameStream or not forwarding ports.
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"
2018-07-06 03:49:10 +00:00
}
2019-03-31 20:57:57 +00:00
NavigableMessageDialog {
2018-07-05 01:48:09 +00:00
id: pairDialog
2019-11-05 03:30:38 +00:00
// Pairing dialog must be modal to prevent double-clicks from triggering
// pairing twice
modal: true
closePolicy: Popup . CloseOnEscape
2018-07-05 01:48:09 +00:00
// don't allow edits to the rest of the window while open
2018-07-06 04:16:32 +00:00
property string pin : "0000"
2020-11-21 19:15:54 +00:00
text: qsTr ( "Please enter %1 on your GameStream PC. This dialog will close when pairing is completed." ) . arg ( pin )
2019-04-01 00:24:25 +00:00
standardButtons: Dialog . Cancel
2018-07-05 01:48:09 +00:00
onRejected: {
2018-07-06 03:37:51 +00:00
// FIXME: We should interrupt pairing here
2018-07-05 01:48:09 +00:00
}
}
2019-03-31 20:57:57 +00:00
NavigableMessageDialog {
2018-07-29 23:04:45 +00:00
id: deletePcDialog
// don't allow edits to the rest of the window while open
property int pcIndex : - 1 ;
2020-11-21 17:42:16 +00:00
text: qsTr ( "Are you sure you want to remove this PC?" )
2019-04-01 00:24:25 +00:00
standardButtons: Dialog . Yes | Dialog . No
2018-10-01 05:20:19 +00:00
function deletePc ( ) {
2018-07-29 23:04:45 +00:00
console . log ( "deleting PC pairing for PC at index: " + pcIndex )
computerModel . deleteComputer ( pcIndex ) ;
}
2018-10-01 05:20:19 +00:00
onAccepted: deletePc ( )
2018-07-29 23:04:45 +00:00
}
2020-08-09 03:25:26 +00:00
NavigableMessageDialog {
id: testConnectionDialog
closePolicy: Popup . CloseOnEscape
standardButtons: Dialog . Ok
onAboutToShow: {
2020-11-21 19:15:54 +00:00
testConnectionDialog . text = qsTr ( "Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked." ) + "\n\n" + qsTr ( "This may take a few seconds…" )
2020-08-09 03:25:26 +00:00
showSpinner = true
}
function connectionTestComplete ( result , blockedPorts )
{
if ( result === - 1 ) {
2020-11-21 17:42:16 +00:00
text = qsTr ( "The network test could not be performed because none of Moonlight's connection testing servers were reachable from this PC. Check your Internet connection or try again later." )
2020-08-09 03:25:26 +00:00
imageSrc = "qrc:/res/baseline-warning-24px.svg"
}
else if ( result === 0 ) {
2020-11-21 19:15:54 +00:00
text = qsTr ( "This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC's firewall settings." ) + "\n\n" + qsTr ( "If you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC's Internet connection." )
2020-08-09 03:25:26 +00:00
imageSrc = "qrc:/res/baseline-check_circle_outline-24px.svg"
}
else {
2020-11-21 19:15:54 +00:00
text = qsTr ( "Your PC's current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network." ) + "\n\n" + qsTr ( "The following network ports were blocked:" ) + "\n"
2020-08-09 03:25:26 +00:00
text += blockedPorts
imageSrc = "qrc:/res/baseline-error_outline-24px.svg"
}
// Stop showing the spinner and show the image instead
showSpinner = false
}
}
2020-05-02 01:34:15 +00:00
NavigableDialog {
id: renamePcDialog
2020-11-21 17:42:16 +00:00
property string label: qsTr ( "Enter the new name for this PC:" )
2020-05-02 01:34:15 +00:00
property string originalName
property int pcIndex : - 1 ;
standardButtons: Dialog . Ok | Dialog . Cancel
onOpened: {
// Force keyboard focus on the textbox so keyboard navigation works
editText . forceActiveFocus ( )
}
onClosed: {
editText . clear ( )
}
onAccepted: {
if ( editText . text ) {
computerModel . renameComputer ( pcIndex , editText . text )
}
}
ColumnLayout {
Label {
text: renamePcDialog . label
font.bold: true
}
TextField {
id: editText
placeholderText: renamePcDialog . originalName
Layout.fillWidth: true
focus: true
Keys.onReturnPressed: {
renamePcDialog . accept ( )
}
}
}
}
2020-05-10 19:53:27 +00:00
ScrollBar.vertical: ScrollBar { }
2018-07-04 23:40:21 +00:00
}