2018-07-04 21:16:25 +00:00
import QtQuick 2.9
import QtQuick . Controls 2.2
2018-07-08 05:15:02 +00:00
import QtQuick . Layouts 1.3
2018-08-14 02:47:42 +00:00
import QtQuick . Window 2.2
2018-07-04 21:16:25 +00:00
2018-08-14 02:47:42 +00:00
import ComputerManager 1.0
2018-08-10 05:51:27 +00:00
import AutoUpdateChecker 1.0
2018-10-01 05:39:51 +00:00
import StreamingPreferences 1.0
2019-03-23 19:05:08 +00:00
import SystemProperties 1.0
2019-03-23 21:15:55 +00:00
import SdlGamepadKeyNavigation 1.0
2018-08-10 05:51:27 +00:00
2018-07-04 21:16:25 +00:00
ApplicationWindow {
2018-08-14 02:47:42 +00:00
property bool pollingActive: false
2021-03-05 23:44:31 +00:00
// Set by SettingsView to force the back operation to pop all
// pages except the initial view. This is required when doing
// a retranslate() because AppView breaks for some reason.
property bool clearOnBack: false
2018-07-04 21:16:25 +00:00
id: window
visible: true
2018-07-06 05:08:55 +00:00
width: 1280
2018-09-09 20:21:11 +00:00
height: 600
2018-07-09 06:24:26 +00:00
2021-01-10 15:50:12 +00:00
visibility: {
2021-03-13 21:20:58 +00:00
if ( SystemProperties . hasDesktopEnvironment ) {
2021-01-10 15:50:12 +00:00
if ( StreamingPreferences . uiDisplayMode == StreamingPreferences . UI_WINDOWED ) return "Windowed"
2021-01-10 16:09:31 +00:00
else if ( StreamingPreferences . uiDisplayMode == StreamingPreferences . UI_MAXIMIZED ) return "Maximized"
2021-01-10 15:50:12 +00:00
else if ( StreamingPreferences . uiDisplayMode == StreamingPreferences . UI_FULLSCREEN ) return "FullScreen"
} else {
2021-01-10 16:09:31 +00:00
return "FullScreen"
2021-01-10 15:50:12 +00:00
}
}
2020-12-27 21:34:49 +00:00
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
// it will never insert a line break and just extend on forever.
ToolTip.toolTip.contentWidth: ToolTip . toolTip . implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
2021-03-05 23:44:31 +00:00
function goBack ( ) {
if ( clearOnBack ) {
// Pop all items except the first one
stackView . pop ( null )
clearOnBack = false
}
else {
stackView . pop ( )
}
}
2018-07-06 03:37:51 +00:00
StackView {
id: stackView
2018-09-29 21:06:55 +00:00
initialItem: initialView
2018-07-06 03:37:51 +00:00
anchors.fill: parent
2018-09-23 22:16:27 +00:00
focus: true
onCurrentItemChanged: {
2018-09-24 02:06:26 +00:00
// Ensure focus travels to the next view when going back
2018-09-23 22:16:27 +00:00
if ( currentItem ) {
currentItem . forceActiveFocus ( )
}
}
Keys.onEscapePressed: {
if ( depth > 1 ) {
2021-03-05 23:44:31 +00:00
goBack ( )
2018-09-23 22:16:27 +00:00
}
2018-10-13 07:41:22 +00:00
else {
quitConfirmationDialog . open ( )
}
2018-09-23 22:16:27 +00:00
}
2018-09-24 02:06:26 +00:00
Keys.onBackPressed: {
if ( depth > 1 ) {
2021-03-05 23:44:31 +00:00
goBack ( )
2018-09-24 02:06:26 +00:00
}
2018-10-13 07:41:22 +00:00
else {
quitConfirmationDialog . open ( )
}
2018-09-24 02:06:26 +00:00
}
2018-09-30 20:41:32 +00:00
Keys.onMenuPressed: {
settingsButton . clicked ( )
}
2018-09-30 20:52:38 +00:00
// This is a keypress we've reserved for letting the
// SdlGamepadKeyNavigation object tell us to show settings
// when Menu is consumed by a focused control.
Keys.onHangupPressed: {
settingsButton . clicked ( )
}
2018-07-04 21:16:25 +00:00
}
2018-07-08 05:15:02 +00:00
2018-10-13 02:58:29 +00:00
// This timer keeps us polling for 5 minutes of inactivity
// to allow the user to work with Moonlight on a second display
// while dealing with configuration issues. This will ensure
// machines come online even if the input focus isn't on Moonlight.
Timer {
id: inactivityTimer
interval: 5 * 60000
onTriggered: {
if ( ! active && pollingActive ) {
ComputerManager . stopPollingAsync ( )
pollingActive = false
}
}
}
2018-10-29 00:53:49 +00:00
onVisibleChanged: {
// When we become invisible while streaming is going on,
// stop polling immediately.
if ( ! visible ) {
inactivityTimer . stop ( )
if ( pollingActive ) {
ComputerManager . stopPollingAsync ( )
pollingActive = false
}
}
else if ( active ) {
// When we become visible and active again, start polling
inactivityTimer . stop ( )
// Restart polling if it was stopped
if ( ! pollingActive ) {
ComputerManager . startPolling ( )
pollingActive = true
}
}
}
2018-10-13 02:58:29 +00:00
onActiveChanged: {
if ( active ) {
// Stop the inactivity timer
inactivityTimer . stop ( )
// Restart polling if it was stopped
if ( ! pollingActive ) {
ComputerManager . startPolling ( )
pollingActive = true
}
2018-08-14 02:47:42 +00:00
}
2018-10-13 02:58:29 +00:00
else {
// Start the inactivity timer to stop polling
// if focus does not return within a few minutes.
inactivityTimer . restart ( )
2018-08-14 02:47:42 +00:00
}
}
2018-10-01 05:39:51 +00:00
property bool initialized: false
2018-10-06 02:54:13 +00:00
// BUG: Using onAfterSynchronizing: here causes very strange
// failures on Linux. Many shaders fail to compile and we
// eventually segfault deep inside the Qt OpenGL code.
onAfterRendering: {
2018-10-01 05:39:51 +00:00
// We use this callback to trigger dialog display because
// it only happens once the window is fully constructed.
// Doing it earlier can lead to the dialog appearing behind
// the window or otherwise without input focus.
if ( ! initialized ) {
2018-11-19 06:04:44 +00:00
// Set initialized before calling anything else, because
// pumping the event loop can cause us to get another
// onAfterRendering call and potentially reenter this code.
initialized = true ;
2019-04-22 01:31:11 +00:00
if ( SystemProperties . isWow64 ) {
2018-10-01 05:39:51 +00:00
wow64Dialog . open ( )
}
2019-03-23 19:05:08 +00:00
else if ( ! SystemProperties . hasHardwareAcceleration ) {
2019-04-22 01:31:11 +00:00
if ( SystemProperties . isRunningXWayland ) {
xWaylandDialog . open ( )
}
else {
noHwDecoderDialog . open ( )
}
2018-10-01 05:39:51 +00:00
}
2019-03-23 19:05:08 +00:00
if ( SystemProperties . unmappedGamepads ) {
unmappedGamepadDialog . unmappedGamepads = SystemProperties . unmappedGamepads
2018-10-01 05:39:51 +00:00
unmappedGamepadDialog . open ( )
}
}
}
2022-04-10 20:42:50 +00:00
// Workaround for lack of instanceof in Qt 5.9.
//
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
function qmltypeof ( obj , className ) { // QtObject, string -> bool
// className plus "(" is the class instance without modification
// className plus "_QML" is the class instance with user-defined properties
var str = obj . toString ( ) ;
return str . startsWith ( className + "(" ) || str . startsWith ( className + "_QML" ) ;
}
function navigateTo ( url , objectType )
2018-07-09 05:37:29 +00:00
{
var existingItem = stackView . find ( function ( item , index ) {
2022-04-10 20:42:50 +00:00
return qmltypeof ( item , objectType )
2018-07-09 05:37:29 +00:00
} )
if ( existingItem !== null ) {
// Pop to the existing item
stackView . pop ( existingItem )
}
else {
// Create a new item
stackView . push ( url )
}
}
2018-07-08 05:15:02 +00:00
header: ToolBar {
2018-07-09 07:12:27 +00:00
id: toolBar
2019-04-25 03:22:39 +00:00
height: 60
2018-08-31 06:28:26 +00:00
anchors.topMargin: 5
anchors.bottomMargin: 5
2018-07-08 05:15:02 +00:00
2020-05-10 18:48:24 +00:00
Label {
id: titleLabel
2020-05-10 19:38:56 +00:00
visible: toolBar . width > 700
2020-05-10 18:48:24 +00:00
anchors.fill: parent
text: stackView . currentItem . objectName
font.pointSize: 20
elide: Label . ElideRight
horizontalAlignment: Qt . AlignHCenter
verticalAlignment: Qt . AlignVCenter
}
2018-07-08 05:15:02 +00:00
RowLayout {
2021-12-03 09:24:31 +00:00
spacing: 10
2019-04-25 03:22:39 +00:00
anchors.leftMargin: 10
anchors.rightMargin: 10
2018-07-08 05:15:02 +00:00
anchors.fill: parent
2018-09-23 22:16:27 +00:00
NavigableToolButton {
2018-07-08 05:15:02 +00:00
// Only make the button visible if the user has navigated somewhere.
visible: stackView . depth > 1
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/arrow_left.svg"
2018-08-31 06:28:26 +00:00
2021-03-05 23:44:31 +00:00
onClicked: goBack ( )
2018-09-23 22:16:27 +00:00
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
2018-07-08 05:15:02 +00:00
}
2020-05-10 19:38:56 +00:00
// This label will appear when the window gets too small and
// we need to ensure the toolbar controls don't collide
2018-07-08 05:15:02 +00:00
Label {
2020-05-10 19:38:56 +00:00
id: titleRowLabel
font.pointSize: titleLabel . font . pointSize
elide: Label . ElideRight
horizontalAlignment: Qt . AlignHCenter
verticalAlignment: Qt . AlignVCenter
2018-07-08 05:15:02 +00:00
Layout.fillWidth: true
2020-05-10 19:38:56 +00:00
// We need this label to always be visible so it can occupy
// the remaining space in the RowLayout. To "hide" it, we
// just set the text to empty string.
text: ! titleLabel . visible ? stackView.currentItem.objectName : ""
2018-07-08 05:15:02 +00:00
}
2020-05-10 05:20:39 +00:00
Label {
id: versionLabel
2022-04-10 20:42:50 +00:00
visible: qmltypeof ( stackView . currentItem , "SettingsView" )
2020-11-21 19:15:54 +00:00
text: qsTr ( "Version %1" ) . arg ( SystemProperties . versionString )
2020-05-10 06:02:09 +00:00
font.pointSize: 12
2020-05-10 05:20:39 +00:00
horizontalAlignment: Qt . AlignRight
verticalAlignment: Qt . AlignVCenter
}
2020-09-10 04:49:45 +00:00
NavigableToolButton {
id: discordButton
visible: SystemProperties . hasBrowser &&
2022-04-10 20:42:50 +00:00
qmltypeof ( stackView . currentItem , "SettingsView" )
2020-09-10 04:49:45 +00:00
iconSource: "qrc:/res/Discord-Logo-White.svg"
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
2020-11-21 17:42:16 +00:00
ToolTip.text: qsTr ( "Join our community on Discord" )
2020-09-10 04:49:45 +00:00
// TODO need to make sure browser is brought to foreground.
onClicked: Qt . openUrlExternally ( "https://moonlight-stream.org/discord" ) ;
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
}
2018-07-08 05:15:02 +00:00
2019-03-27 04:31:51 +00:00
NavigableToolButton {
id: addPcButton
2022-04-10 20:42:50 +00:00
visible: qmltypeof ( stackView . currentItem , "PcView" )
2019-03-27 04:31:51 +00:00
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
2019-03-27 04:31:51 +00:00
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
2020-11-21 17:42:16 +00:00
ToolTip.text: qsTr ( "Add PC manually" ) + ( newPcShortcut . nativeText ? ( " (" + newPcShortcut . nativeText + ")" ) : "" )
2019-03-31 20:57:57 +00:00
Shortcut {
id: newPcShortcut
sequence: StandardKey . New
onActivated: addPcButton . clicked ( )
}
2019-03-27 04:31:51 +00:00
onClicked: {
addPcDialog . open ( )
}
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
}
2018-09-23 22:16:27 +00:00
NavigableToolButton {
2018-08-10 05:51:27 +00:00
property string browserUrl: ""
id: updateButton
2018-08-31 06:28:26 +00:00
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/update.svg"
2018-08-10 05:51:27 +00:00
2018-08-10 06:20:20 +00:00
ToolTip.delay: 1000
ToolTip.timeout: 3000
2019-09-30 04:32:24 +00:00
ToolTip.visible: hovered || visible
2018-08-10 06:20:20 +00:00
2018-08-10 05:51:27 +00:00
// Invisible until we get a callback notifying us that
// an update is available
visible: false
2019-12-14 04:15:52 +00:00
onClicked: {
if ( SystemProperties . hasBrowser ) {
Qt . openUrlExternally ( browserUrl ) ;
}
}
2018-08-10 05:51:27 +00:00
2019-01-04 02:00:54 +00:00
function updateAvailable ( version , url )
2018-08-10 05:51:27 +00:00
{
2020-11-21 19:15:54 +00:00
ToolTip . text = qsTr ( "Update available for Moonlight: Version %1" ) . arg ( version )
2018-08-10 05:51:27 +00:00
updateButton . browserUrl = url
updateButton . visible = true
}
Component.onCompleted: {
AutoUpdateChecker . onUpdateAvailable . connect ( updateAvailable )
AutoUpdateChecker . start ( )
}
2018-09-23 22:16:27 +00:00
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
2018-08-10 05:51:27 +00:00
}
2018-09-23 22:16:27 +00:00
NavigableToolButton {
2018-09-24 02:24:47 +00:00
id: helpButton
2019-03-23 20:51:34 +00:00
visible: SystemProperties . hasBrowser
2018-09-24 02:24:47 +00:00
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/question_mark.svg"
2018-07-09 05:37:29 +00:00
2018-08-10 06:20:20 +00:00
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
2020-11-21 17:42:16 +00:00
ToolTip.text: qsTr ( "Help" ) + ( helpShortcut . nativeText ? ( " (" + helpShortcut . nativeText + ")" ) : "" )
2018-09-24 02:24:47 +00:00
Shortcut {
id: helpShortcut
sequence: StandardKey . HelpContents
onActivated: helpButton . clicked ( )
}
2018-08-10 06:20:20 +00:00
2018-07-09 05:37:29 +00:00
// TODO need to make sure browser is brought to foreground.
onClicked: Qt . openUrlExternally ( "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide" ) ;
2018-09-23 22:16:27 +00:00
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
2018-07-08 05:15:02 +00:00
}
2018-09-23 22:16:27 +00:00
NavigableToolButton {
2018-07-28 08:27:42 +00:00
// TODO: Implement gamepad mapping then unhide this button
visible: false
2018-08-10 06:20:20 +00:00
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
2020-11-21 19:15:54 +00:00
ToolTip.text: qsTr ( "Gamepad Mapper" )
2018-08-10 06:20:20 +00:00
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
2018-08-31 06:28:26 +00:00
2022-04-10 20:42:50 +00:00
onClicked: navigateTo ( "qrc:/gui/GamepadMapper.qml" , "GamepadMapper" )
2018-09-23 22:16:27 +00:00
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
2018-07-09 05:37:29 +00:00
}
2018-09-23 22:16:27 +00:00
NavigableToolButton {
2018-09-24 02:24:47 +00:00
id: settingsButton
2019-04-25 03:22:39 +00:00
iconSource: "qrc:/res/settings.svg"
2018-08-31 06:28:26 +00:00
2022-04-10 20:42:50 +00:00
onClicked: navigateTo ( "qrc:/gui/SettingsView.qml" , "SettingsView" )
2018-07-08 05:15:02 +00:00
2018-09-23 22:16:27 +00:00
Keys.onDownPressed: {
stackView . currentItem . forceActiveFocus ( Qt . TabFocus )
}
2018-09-24 02:24:47 +00:00
Shortcut {
id: settingsShortcut
sequence: StandardKey . Preferences
onActivated: settingsButton . clicked ( )
}
2018-08-10 06:20:20 +00:00
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
2020-11-21 17:42:16 +00:00
ToolTip.text: qsTr ( "Settings" ) + ( settingsShortcut . nativeText ? ( " (" + settingsShortcut . nativeText + ")" ) : "" )
2018-07-08 05:15:02 +00:00
}
}
}
2018-10-01 05:39:51 +00:00
2019-03-23 20:51:34 +00:00
ErrorMessageDialog {
2018-10-01 05:39:51 +00:00
id: noHwDecoderDialog
2022-01-07 04:07:31 +00:00
text: qsTr ( "No functioning hardware accelerated video decoder was detected by Moonlight. " +
2020-11-21 19:15:54 +00:00
"Your streaming performance may be severely degraded in this configuration." )
2020-11-21 17:42:16 +00:00
helpText: qsTr ( "Click the Help button for more information on solving this problem." )
2019-03-31 22:16:48 +00:00
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
2018-10-01 05:39:51 +00:00
}
2019-03-23 20:51:34 +00:00
ErrorMessageDialog {
2019-04-22 01:31:11 +00:00
id: xWaylandDialog
2020-11-21 19:15:54 +00:00
text: qsTr ( "Hardware acceleration doesn't work on XWayland. Continuing on XWayland may result in poor streaming performance. " +
"Try running with QT_QPA_PLATFORM=wayland or switch to X11." )
2020-11-21 17:42:16 +00:00
helpText: qsTr ( "Click the Help button for more information." )
2019-03-31 22:16:48 +00:00
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
2018-10-01 05:39:51 +00:00
}
2019-03-31 20:57:57 +00:00
NavigableMessageDialog {
2018-10-01 05:39:51 +00:00
id: wow64Dialog
2019-04-01 00:24:25 +00:00
standardButtons: Dialog . Ok | Dialog . Cancel
2020-12-28 19:32:02 +00:00
text: qsTr ( "This version of Moonlight isn't optimized for your PC. Please download the '%1' version of Moonlight for the best streaming performance." ) . arg ( SystemProperties . friendlyNativeArchName )
2018-10-01 05:39:51 +00:00
onAccepted: {
Qt . openUrlExternally ( "https://github.com/moonlight-stream/moonlight-qt/releases" ) ;
}
}
2019-03-23 20:51:34 +00:00
ErrorMessageDialog {
2018-10-01 05:39:51 +00:00
id: unmappedGamepadDialog
property string unmappedGamepads : ""
2020-11-21 19:15:54 +00:00
text: qsTr ( "Moonlight detected gamepads without a mapping:" ) + "\n" + unmappedGamepads
2019-04-01 03:10:56 +00:00
helpTextSeparator: "\n\n"
2020-11-21 17:42:16 +00:00
helpText: qsTr ( "Click the Help button for information on how to map your gamepads." )
2019-03-31 22:16:48 +00:00
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Gamepad-Mapping"
2018-10-01 05:39:51 +00:00
}
2018-10-13 07:41:22 +00:00
// This dialog appears when quitting via keyboard or gamepad button
2019-03-31 20:57:57 +00:00
NavigableMessageDialog {
2018-10-13 07:41:22 +00:00
id: quitConfirmationDialog
2019-04-01 00:24:25 +00:00
standardButtons: Dialog . Yes | Dialog . No
2020-11-21 17:42:16 +00:00
text: qsTr ( "Are you sure you want to quit?" )
2018-10-13 07:41:22 +00:00
// For keyboard/gamepad navigation
onAccepted: Qt . quit ( )
}
2019-03-27 04:31:51 +00:00
2019-04-24 02:40:21 +00:00
// HACK: This belongs in StreamSegue but keeping a dialog around after the parent
// dies can trigger bugs in Qt 5.12 that cause the app to crash. For now, we will
// host this dialog in a QML component that is never destroyed.
//
// To repro: Start a stream, cut the network connection to trigger the "Connection
// terminated" dialog, wait until the app grid times out back to the PC grid, then
// try to dismiss the dialog.
ErrorMessageDialog {
id: streamSegueErrorDialog
property bool quitAfter: false
onClosed: {
if ( quitAfter ) {
Qt . quit ( )
}
// StreamSegue assumes its dialog will be re-created each time we
// start streaming, so fake it by wiping out the text each time.
text = ""
}
}
2019-04-01 00:24:25 +00:00
NavigableDialog {
2019-03-27 04:31:51 +00:00
id: addPcDialog
2023-03-18 19:20:51 +00:00
property string label: qsTr ( "Enter the IP address of your host PC:" )
2019-03-27 04:31:51 +00:00
2019-04-01 00:24:25 +00:00
standardButtons: Dialog . Ok | Dialog . Cancel
2019-03-27 04:31:51 +00:00
2019-04-01 02:32:17 +00:00
onOpened: {
// Force keyboard focus on the textbox so keyboard navigation works
editText . forceActiveFocus ( )
}
2019-12-30 02:13:45 +00:00
onClosed: {
editText . clear ( )
}
2019-03-27 04:31:51 +00:00
onAccepted: {
if ( editText . text ) {
2021-07-03 04:34:54 +00:00
ComputerManager . addNewHostManually ( editText . text . trim ( ) )
2019-03-27 04:31:51 +00:00
}
}
ColumnLayout {
2019-04-01 00:24:25 +00:00
Label {
2019-03-27 04:31:51 +00:00
text: addPcDialog . label
2019-03-27 07:39:25 +00:00
font.bold: true
2019-03-27 04:31:51 +00:00
}
TextField {
id: editText
Layout.fillWidth: true
2019-04-01 00:24:25 +00:00
focus: true
Keys.onReturnPressed: {
addPcDialog . accept ( )
}
2021-02-27 23:01:22 +00:00
Keys.onEnterPressed: {
addPcDialog . accept ( )
}
2019-03-27 04:31:51 +00:00
}
}
}
2018-07-04 21:16:25 +00:00
}