diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 1c205b5f5e..2d5c68c0e8 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -120,6 +120,6 @@ FocusScope { } Component.onCompleted: { - bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 144b91063f..ebc677fb00 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -22,11 +22,16 @@ Item { width: root.width height: root.height readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service") + readonly property int textFieldHeight: 31 readonly property string fontFamily: "Raleway" readonly property int fontSize: 15 readonly property bool fontBold: true + readonly property int textFieldFontSize: 18 + readonly property var passwordImageRatio: 16 / 23 - readonly property bool withSteam: withSteam + property bool withOculus: withOculus + property bool withSteam: withSteam property string errorString: errorString readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -61,15 +66,20 @@ Item { Item { id: contentItem - anchors.fill: parent + width: parent.width + height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height + + termsContainer.height + anchors.top: parent.top + anchors.topMargin: root.bannerHeight + 0.25 * parent.height + anchors.left: parent.left Item { id: errorContainer - width: parent.width + width: root.bannerWidth height: loginErrorMessageTextMetrics.height anchors { - bottom: buttons.top; - bottomMargin: hifi.dimensions.contentSpacing.y; + bottom: completeProfileBody.withOculus ? fields.top : buttons.top; + bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y; left: buttons.left; } TextMetrics { @@ -79,8 +89,8 @@ Item { } Text { id: loginErrorMessage; - width: root.bannerWidth color: "red"; + width: root.bannerWidth; font.family: completeProfileBody.fontFamily font.pixelSize: 18 font.bold: completeProfileBody.fontBold @@ -88,13 +98,196 @@ Item { horizontalAlignment: Text.AlignHCenter text: completeProfileBody.errorString visible: true + onTextChanged: { + mainContainer.recalculateErrorMessage(); + } + Component.onCompleted: { + mainContainer.recalculateErrorMessage(); + } } - Component.onCompleted: { - if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) { - loginErrorMessage.wrapMode = Text.WordWrap; - loginErrorMessage.verticalAlignment = Text.AlignLeft; - loginErrorMessage.horizontalAlignment = Text.AlignLeft; - errorContainer.height = 3 * loginErrorMessageTextMetrics.height; + } + + Item { + id: fields + width: root.bannerWidth + height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y + visible: completeProfileBody.withOculus + anchors { + left: parent.left + leftMargin: (parent.width - root.bannerWidth) / 2 + bottom: buttons.top + bottomMargin: hifi.dimensions.contentSpacing.y + } + + HifiControlsUit.TextField { + id: usernameField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + placeholderText: "Username" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + anchors { + top: parent.top + } + Keys.onPressed: { + if (!usernameField.visible) { + return; + } + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + passwordField.focus = true; + } else { + emailField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + passwordField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } + } + onFocusChanged: { + root.text = ""; + if (focus) { + root.isPassword = false; + } + } + Component.onCompleted: { + var userID = ""; + if (completeProfileBody.withOculus) { + userID = loginDialog.oculusUserID(); + } + usernameField.text = userID; + } + } + HifiControlsUit.TextField { + id: emailField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + anchors { + top: usernameField.bottom + topMargin: hifi.dimensions.contentSpacing.y + } + placeholderText: "Email" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + activeFocusOnPress: true + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + usernameField.focus = true; + } else { + passwordField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + usernameField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } + } + onFocusChanged: { + root.text = ""; + if (focus) { + root.isPassword = false; + } + } + } + HifiControlsUit.TextField { + id: passwordField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + placeholderText: "Password (optional)" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + activeFocusOnPress: true + echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password + anchors { + top: emailField.bottom + topMargin: hifi.dimensions.contentSpacing.y + } + + onFocusChanged: { + root.text = ""; + root.isPassword = focus; + } + + Item { + id: showPasswordContainer + z: 10 + // width + image's rightMargin + width: showPasswordImage.width + 8 + height: parent.height + anchors { + right: parent.right + } + + Image { + id: showPasswordImage + width: passwordField.height * passwordImageRatio + height: passwordField.height * passwordImageRatio + anchors { + right: parent.right + rightMargin: 8 + top: parent.top + topMargin: passwordFieldMouseArea.showPassword ? 6 : 8 + bottom: parent.bottom + bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8 + } + source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg" + MouseArea { + id: passwordFieldMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + property bool showPassword: false + onClicked: { + showPassword = !showPassword; + } + } + } + } + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + emailField.focus = true; + } else if (usernameField.visible) { + usernameField.focus = true; + } else { + emailField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + emailField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } } } } @@ -105,7 +298,7 @@ Item { height: d.minHeightButton anchors { top: parent.top - topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y + topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y left: parent.left leftMargin: (parent.width - root.bannerWidth) / 2 } @@ -144,7 +337,7 @@ Item { width: (parent.width - hifi.dimensions.contentSpacing.x) / 2 height: d.minHeightButton - text: qsTr("Create your profile") + text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile") color: hifi.buttons.blue fontFamily: completeProfileBody.fontFamily @@ -158,55 +351,12 @@ Item { UserActivityLogger.logAction("encourageLoginDialog", data); } loginErrorMessage.visible = false; - loginDialog.createAccountFromSteam(); - } - } - } - - Item { - id: additionalTextContainer - width: parent.width - height: additionalTextMetrics.height - anchors { - top: buttons.bottom - horizontalCenter: parent.horizontalCenter - topMargin: hifi.dimensions.contentSpacing.y - left: parent.left - } - - TextMetrics { - id: additionalTextMetrics - font: additionalText.font - text: "Already have a High Fidelity profile? Link to an existing profile here." - } - - HifiStylesUit.ShortcutText { - id: additionalText - text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>" - - font.family: completeProfileBody.fontFamily - font.pixelSize: completeProfileBody.fontSize - font.bold: completeProfileBody.fontBold - wrapMode: Text.NoWrap - lineHeight: 1 - lineHeightMode: Text.ProportionalHeight - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: { - loginDialog.isLogIn = true; - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true }); - } - Component.onCompleted: { - if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) { - additionalText.width = root.bannerWidth; - additionalText.wrapMode = Text.WordWrap; - additionalText.verticalAlignment = Text.AlignLeft; - additionalText.horizontalAlignment = Text.AlignLeft; - additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height; - additionalTextContainer.anchors.left = buttons.left; - } else { - additionalText.anchors.centerIn = additionalTextContainer; + if (completeProfileBody.withOculus) { + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + } else if (completeProfileBody.withSteam) { + loginDialog.createAccountFromSteam(); } } } @@ -217,29 +367,33 @@ Item { width: parent.width height: termsTextMetrics.height anchors { - top: additionalTextContainer.bottom + top: buttons.bottom horizontalCenter: parent.horizontalCenter - topMargin: 2 * hifi.dimensions.contentSpacing.y + topMargin: hifi.dimensions.contentSpacing.y left: parent.left } TextMetrics { id: termsTextMetrics font: termsText.font - text: completeProfileBody.termsContainerText + text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText Component.onCompleted: { // with the link. - termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>") + if (completeProfileBody.withOculus) { + termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>") + } else { + termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>") + } } } HifiStylesUit.InfoItem { id: termsText - text: completeProfileBody.termsContainerText + text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText font.family: completeProfileBody.fontFamily font.pixelSize: completeProfileBody.fontSize font.bold: completeProfileBody.fontBold wrapMode: Text.WordWrap - color: hifi.colors.lightGray + color: hifi.colors.white linkColor: hifi.colors.blueAccent lineHeight: 1 lineHeightMode: Text.ProportionalHeight @@ -247,7 +401,7 @@ Item { onLinkActivated: loginDialog.openUrl(link); Component.onCompleted: { - if (termsTextMetrics.width > root.bannerWidth && root.isTablet) { + if (termsTextMetrics.width > root.bannerWidth) { termsText.width = root.bannerWidth; termsText.wrapMode = Text.WordWrap; additionalText.verticalAlignment = Text.AlignLeft; @@ -260,14 +414,86 @@ Item { } } } + + Item { + id: additionalTextContainer + width: parent.width + height: additionalTextMetrics.height + anchors { + top: termsContainer.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + } + + TextMetrics { + id: additionalTextMetrics + font: additionalText.font + text: "Already have a High Fidelity profile? Link to an existing profile here." + } + + HifiStylesUit.ShortcutText { + id: additionalText + text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>" + width: root.bannerWidth; + font.family: completeProfileBody.fontFamily + font.pixelSize: completeProfileBody.fontSize + font.bold: completeProfileBody.fontBold + wrapMode: Text.NoWrap + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: { + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", + "withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus, + "linkOculus": completeProfileBody.withOculus }); + } + Component.onCompleted: { + if (additionalTextMetrics.width > root.bannerWidth) { + additionalText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height; + additionalTextContainer.anchors.left = buttons.left; + } else { + additionalText.anchors.centerIn = additionalTextContainer; + } + } + } + } + } + function recalculateErrorMessage() { + if (completeProfileBody.errorString !== "") { + loginErrorMessage.visible = true; + var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length; + var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n"); + loginErrorMessage.text = errorStringEdited; + if (errorLength > 1.0) { + loginErrorMessage.wrapMode = Text.WordWrap; + loginErrorMessage.verticalAlignment = Text.AlignLeft; + loginErrorMessage.horizontalAlignment = Text.AlignLeft; + errorContainer.height = errorLength * loginErrorMessageTextMetrics.height; + } else if (loginErrorMessageTextMetrics.width > root.bannerWidth) { + loginErrorMessage.wrapMode = Text.WordWrap; + loginErrorMessage.verticalAlignment = Text.AlignLeft; + loginErrorMessage.horizontalAlignment = Text.AlignLeft; + errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height; + } else { + loginErrorMessage.wrapMode = Text.NoWrap; + loginErrorMessage.verticalAlignment = Text.AlignVCenter; + loginErrorMessage.horizontalAlignment = Text.AlignHCenter; + errorContainer.height = loginErrorMessageTextMetrics.height; + } + } } } Connections { target: loginDialog onHandleCreateCompleted: { - console.log("Create Succeeded") - + console.log("Create Succeeded"); if (completeProfileBody.withSteam) { if (completeProfileBody.loginDialogPoppedUp) { var data = { @@ -277,20 +503,24 @@ Item { } loginDialog.loginThroughSteam(); } - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false }); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false, + "withOculus": completeProfileBody.withOculus, "linkOculus": false }); } onHandleCreateFailed: { console.log("Create Failed: " + error); - if (completeProfileBody.withSteam) { + if (completeProfileBody.withSteam || completeProfileBody.withOculus) { if (completeProfileBody.loginDialogPoppedUp) { + action = completeProfileBody.withSteam ? "Steam" : "Oculus"; var data = { - "action": "user failed to create a profile with Steam from the complete profile screen" + "action": "user failed to create a profile with " + action + " from the complete profile screen" } UserActivityLogger.logAction("encourageLoginDialog", data); } } - - bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam }); + if (!completeProfileBody.withOculus) { + bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "withOculus": completeProfileBody.withOculus }); + } } } @@ -302,5 +532,6 @@ Item { } d.resize(); root.text = ""; + usernameField.forceActiveFocus(); } } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 5048bf0278..4dd05f594d 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -36,9 +36,10 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property bool withSteam: false + property bool withSteam: withSteam property bool linkSteam: linkSteam - property bool withOculus: false + property bool withOculus: withOculus + property bool linkOculus: linkOculus property string errorString: errorString property bool lostFocus: false @@ -83,23 +84,24 @@ Item { } UserActivityLogger.logAction("encourageLoginDialog", data); } - - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, + "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); } function init() { // going to/from sign in/up dialog. - loginDialog.isLogIn = true; loginErrorMessage.text = linkAccountBody.errorString; loginErrorMessage.visible = (linkAccountBody.errorString !== ""); - loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account"; + if (loginErrorMessageTextMetrics.width > emailField.width) { + loginErrorMessage.wrapMode = Text.WordWrap; + errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height; + } + loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.color = hifi.buttons.blue; emailField.placeholderText = "Username or Email"; var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; - if (linkAccountBody.linkSteam) { - steamInfoText.anchors.top = passwordField.bottom; - keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom; + if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2; loginButton.anchors.right = emailField.right; } else { @@ -125,7 +127,7 @@ Item { id: loginContainer width: emailField.width height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + - keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height + keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height anchors { top: parent.top topMargin: root.bannerHeight + 0.25 * parent.height @@ -135,7 +137,7 @@ Item { Item { id: errorContainer - width: loginErrorMessageTextMetrics.width + width: parent.width height: loginErrorMessageTextMetrics.height anchors { bottom: emailField.top; @@ -304,7 +306,7 @@ Item { fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold color: hifi.buttons.noneBorderlessWhite; - visible: linkAccountBody.linkSteam + visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus anchors { top: keepMeLoggedInCheckbox.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -315,10 +317,9 @@ Item { "action": "user clicked cancel at link account screen" }; UserActivityLogger.logAction("encourageLoginDialog", data); - loginDialog.dismissLoginDialog(); } - - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, + "withOculus": linkAccountBody.withOculus, "errorString": "" }); } } HifiControlsUit.Button { @@ -337,33 +338,6 @@ Item { linkAccountBody.login(); } } - TextMetrics { - id: steamInfoTextMetrics - font: steamInfoText.font - text: steamInfoText.text - } - Text { - id: steamInfoText - width: root.bannerWidth - visible: linkAccountBody.linkSteam - anchors { - top: loginButton.bottom - topMargin: hifi.dimensions.contentSpacing.y - left: emailField.left - } - - font.family: linkAccountBody.fontFamily - font.pixelSize: linkAccountBody.textFieldFontSize - color: "white" - text: qsTr("Your Steam account information will not be exposed to others."); - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - Component.onCompleted: { - if (steamInfoTextMetrics.width > root.bannerWidth) { - steamInfoText.wrapMode = Text.WordWrap; - } - } - } TextMetrics { id: cantAccessTextMetrics font: cantAccessText.font @@ -372,7 +346,7 @@ Item { HifiStylesUit.ShortcutText { id: cantAccessText z: 10 - visible: !linkAccountBody.linkSteam + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus anchors { top: loginButton.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -423,10 +397,10 @@ Item { buttonGlyphSize: 24 buttonGlyphRightMargin: 10 onClicked: { - // if (loginDialog.isOculusStoreRunning()) { - // linkAccountBody.withOculus = true; - // loginDialog.loginThroughSteam(); - // } else + if (loginDialog.isOculusRunning()) { + linkAccountBody.withOculus = true; + loginDialog.loginThroughOculus(); + } else if (loginDialog.isSteamRunning()) { linkAccountBody.withSteam = true; loginDialog.loginThroughSteam(); @@ -446,18 +420,17 @@ Item { } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, - "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); + "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); } Component.onCompleted: { - if (linkAccountBody.linkSteam) { + if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { continueButton.visible = false; return; } - // if (loginDialog.isOculusStoreRunning()) { - // continueButton.text = qsTr("CONTINUE WITH OCULUS"); - // continueButton.buttonGlyph = hifi.glyphs.oculus; - // } else - if (loginDialog.isSteamRunning()) { + if (loginDialog.isOculusRunning()) { + continueButton.text = qsTr("CONTINUE WITH OCULUS"); + continueButton.buttonGlyph = hifi.glyphs.oculus; + } else if (loginDialog.isSteamRunning()) { continueButton.text = qsTr("CONTINUE WITH STEAM"); continueButton.buttonGlyph = hifi.glyphs.steamSquare; } else { @@ -470,7 +443,7 @@ Item { id: signUpContainer width: loginContainer.width height: signUpTextMetrics.height - visible: !linkAccountBody.linkSteam + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus anchors { left: loginContainer.left top: loginContainer.bottom @@ -519,7 +492,7 @@ Item { UserActivityLogger.logAction("encourageLoginDialog", data); } bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, - "errorString": "", "linkSteam": linkAccountBody.linkSteam }); + "errorString": "" }); } } } @@ -543,7 +516,7 @@ Item { fontFamily: linkAccountBody.fontFamily fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold - visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam; + visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus; onClicked: { if (linkAccountBody.loginDialogPoppedUp) { var data = { diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 5e4a6c4cb3..583f00583b 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -29,6 +29,8 @@ Item { property bool withSteam: withSteam property bool withOculus: withOculus property bool linkSteam: linkSteam + property bool linkOculus: linkOculus + property bool createOculus: createOculus readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -75,15 +77,25 @@ Item { } } + Timer { + id: oculusSuccessTimer + interval: 500; + running: false; + repeat: false; + onTriggered: { + loginDialog.loginThroughOculus(); + init(); + } + } + function init() { // For the process of logging in. loggingInText.wrapMode = Text.NoWrap; - - if (loggingInBody.linkSteam) { + if (loggingInBody.createOculus) { + loggingInGlyph.text = hifi.glyphs.oculus; loggingInGlyph.visible = true; - loggingInText.text = "Linking to Steam"; + loggingInText.text = "Creating account with Oculus"; loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; - loginDialog.linkSteam(); } else if (loggingInBody.withSteam) { loggingInGlyph.visible = true; loggingInText.text = "Logging in to Steam"; @@ -100,12 +112,18 @@ Item { loggingInSpinner.visible = true; } function loadingSuccess() { - loggingInSpinner.visible = false; if (loggingInBody.linkSteam) { loggingInText.text = "Linking to Steam"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; loginDialog.linkSteam(); return; + } else if (loggingInBody.linkOculus) { + loggingInText.text = "Linking to Oculus"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; + loginDialog.linkOculus(); + return; } + loggingInSpinner.visible = false; if (loggingInBody.withSteam) { // reset the flag. loggingInGlyph.visible = false; @@ -246,6 +264,26 @@ Item { verticalAlignment: Text.AlignVCenter; visible: false; } + HifiControlsUit.Button { + id: okButton; + width: d.minWidthButton + height: d.minHeightButton + text: qsTr("OK") + color: hifi.buttons.white + anchors { + top: loggedInGlyph.bottom + topMargin: 3 * hifi.dimensions.contentSpacing.y + left: parent.left + leftMargin: (parent.width - width) / 2; + } + onClicked: { + root.tryDestroy(); + if (loginDialog.getLoginDialogPoppedUp()) { + loginDialog.dismissLoginDialog(); + } + } + visible: false + } } } } @@ -257,6 +295,34 @@ Item { Connections { target: loginDialog + onHandleCreateCompleted: { + console.log("Create Succeeded") + if (loggingInBody.withOculus) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user created Oculus account successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loggingInBody.createOculus = false; + loggingInText.text = "Account created!"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; + oculusSuccessTimer.start(); + } + } + onHandleCreateFailed: { + console.log("Create Failed: " + error); + if (loggingInBody.withOculus) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user created Oculus account unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "errorString": error }); + } + } onHandleLinkCompleted: { console.log("Link Succeeded"); if (loggingInBody.linkSteam) { @@ -267,21 +333,40 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - - loggingInBody.loadingSuccess(); + } else if (loggingInBody.linkOculus) { + loggingInBody.linkOculus = false; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Oculus with their hifi account credentials successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } + loggingInBody.loadingSuccess(); } onHandleLinkFailed: { console.log("Link Failed: " + error); - if (loggingInBody.linkSteam) { + loggingInSpinner.visible = false; + if (loggingInBody.linkOculus) { + loggingInText.text = "Oculus failed to link"; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Oculus unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + okButton.visible = true; + } else if (loggingInBody.linkSteam){ if (loggingInBody.loginDialogPoppedUp) { var data = { "action": "user linked Steam unsuccessfully" }; UserActivityLogger.logAction("encourageLoginDialog", data); } + } else { + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam, + "linkOculus": loggingInBody.linkOculus, "errorString": error }); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error }); } onHandleLoginCompleted: { @@ -292,8 +377,19 @@ Item { onHandleLoginFailed: { console.log("Login Failed") loggingInSpinner.visible = false; + loggingInGlyph.visible = false; var errorString = ""; - if (loggingInBody.linkSteam && loggingInBody.withSteam) { + if (loggingInBody.linkOculus && loggingInBody.withOculus) { + errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to link Oculus with their hifi account credentials" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); + } else if (loggingInBody.linkSteam && loggingInBody.withSteam) { errorString = "Username or password is incorrect."; if (loggingInBody.loginDialogPoppedUp) { var data = { @@ -301,9 +397,9 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString }); + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); } else if (loggingInBody.withSteam) { - loggingInGlyph.visible = false; errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again."; if (loggingInBody.loginDialogPoppedUp) { var data = { @@ -311,19 +407,19 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); } else if (loggingInBody.withOculus) { - loggingInGlyph.visible = false; - errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again." + errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one." if (loggingInBody.loginDialogPoppedUp) { var data = { "action": "user failed to authenticate with Oculus to log in" }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); - } - else { + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); + } else { errorString = "Username or password is incorrect."; if (loggingInBody.loginDialogPoppedUp) { var data = { diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 3ba66391e6..64df9089a1 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -23,6 +23,7 @@ Item { clip: true height: root.height width: root.width + readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service") property int textFieldHeight: 31 property string fontFamily: "Raleway" property int fontSize: 15 @@ -37,7 +38,6 @@ Item { onKeyboardRaisedChanged: d.resize(); property string errorString: errorString - property bool linkSteam: linkSteam property bool lostFocus: false readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -73,7 +73,6 @@ Item { function init() { // going to/from sign in/up dialog. - loginDialog.isLogIn = false; emailField.placeholderText = "Email"; emailField.text = ""; emailField.anchors.top = usernameField.bottom; @@ -353,7 +352,7 @@ Item { } UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam }); + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); } } HifiControlsUit.Button { @@ -380,6 +379,54 @@ Item { signUpBody.signup(); } } + Item { + id: termsContainer + width: parent.width + height: termsTextMetrics.height + anchors { + top: signUpButton.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + } + TextMetrics { + id: termsTextMetrics + font: termsText.font + text: signUpBody.termsContainerText + Component.onCompleted: { + // with the link. + termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>") + } + } + + HifiStylesUit.InfoItem { + id: termsText + text: signUpBody.termsContainerText + font.family: signUpBody.fontFamily + font.pixelSize: signUpBody.fontSize + font.bold: signUpBody.fontBold + wrapMode: Text.WordWrap + color: hifi.colors.white + linkColor: hifi.colors.blueAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + + onLinkActivated: loginDialog.openUrl(link); + + Component.onCompleted: { + if (termsTextMetrics.width > root.bannerWidth) { + termsText.width = root.bannerWidth; + termsText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height; + termsContainer.anchors.left = buttons.left; + } else { + termsText.anchors.centerIn = termsContainer; + } + } + } + } } } @@ -433,14 +480,15 @@ Item { if (errorString !== "") { loginErrorMessage.visible = true; + var errorLength = errorString.split(/\r\n|\r|\n/).length; var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n"); loginErrorMessage.text = errorStringEdited; - loginErrorMessageTextMetrics.text = errorString; - if (loginErrorMessageTextMetrics.width > usernameField.width) { + if (errorLength > 1.0) { + loginErrorMessage.width = root.bannerWidth; loginErrorMessage.wrapMode = Text.WordWrap; loginErrorMessage.verticalAlignment = Text.AlignLeft; loginErrorMessage.horizontalAlignment = Text.AlignLeft; - errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height; + errorContainer.height = errorLength * loginErrorMessageTextMetrics.height; } errorContainer.anchors.bottom = usernameField.top; errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y; diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index af46fc0223..2c8e61a29a 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0 Item { id: usernameCollisionBody clip: true + readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") width: root.width height: root.height readonly property string fontFamily: "Raleway" @@ -26,13 +27,18 @@ Item { readonly property int textFieldFontSize: 18 readonly property bool fontBold: true - readonly property bool withSteam: withSteam + property bool withSteam: withSteam + property bool withOculus: withOculus readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() function create() { mainTextContainer.visible = false - loginDialog.createAccountFromSteam(textField.text); + if (usernameCollisionBody.withOculus) { + loginDialog.createAccountFromOculus(textField.text); + } else if (usernameCollisionBody.withSteam) { + loginDialog.createAccountFromSteam(textField.text); + } } property bool keyboardEnabled: false @@ -90,12 +96,19 @@ Item { font.family: usernameCollisionBody.fontFamily font.pixelSize: usernameCollisionBody.fontSize font.bold: usernameCollisionBody.fontBold - text: qsTr("Your Steam username is not available."); + text: qsTr(""); wrapMode: Text.WordWrap color: hifi.colors.redAccent lineHeight: 1 lineHeightMode: Text.ProportionalHeight horizontalAlignment: Text.AlignHCenter + Component.onCompleted: { + if (usernameCollisionBody.withOculus) { + text = qsTr("Your Oculus username is not available."); + } else if (usernameCollisionBody.withSteam) { + text = qsTr("Your Steam username is not available."); + } + } } @@ -164,7 +177,8 @@ Item { fontSize: usernameCollisionBody.fontSize fontBold: usernameCollisionBody.fontBold onClicked: { - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, + "withOculus": usernameCollisionBody.withOculus, "errorString": "" }); } } HifiControlsUit.Button { @@ -187,6 +201,55 @@ Item { } } } + Item { + id: termsContainer + width: parent.width + height: termsTextMetrics.height + anchors { + top: buttons.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + leftMargin: (parent.width - buttons.width) / 2 + } + TextMetrics { + id: termsTextMetrics + font: termsText.font + text: usernameCollisionBody.termsContainerText + Component.onCompleted: { + // with the link. + termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>") + } + } + + HifiStylesUit.InfoItem { + id: termsText + text: usernameCollisionBody.termsContainerText + font.family: usernameCollisionBody.fontFamily + font.pixelSize: usernameCollisionBody.fontSize + font.bold: usernameCollisionBody.fontBold + wrapMode: Text.WordWrap + color: hifi.colors.white + linkColor: hifi.colors.blueAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + + onLinkActivated: loginDialog.openUrl(link); + + Component.onCompleted: { + if (termsTextMetrics.width > root.bannerWidth) { + termsText.width = root.bannerWidth; + termsText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height; + termsContainer.anchors.left = buttons.left; + } else { + termsText.anchors.centerIn = termsContainer; + } + } + } + } } Component.onCompleted: { @@ -201,18 +264,25 @@ Item { target: loginDialog onHandleCreateCompleted: { console.log("Create Succeeded"); - if (usernameCollisionBody.withSteam) { + if (usernameCollisionBody.withOculus) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Oculus successfully in the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.loginThroughOculus(); + } else if (usernameCollisionBody.withSteam) { if (usernameCollisionBody.loginDialogPoppedUp) { var data = { "action": "user created a profile with Steam successfully in the username collision screen" } UserActivityLogger.logAction("encourageLoginDialog", data); } - loginDialog.loginThroughSteam(); } - - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false }) + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, + "withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false }) } onHandleCreateFailed: { console.log("Create Failed: " + error) diff --git a/interface/resources/qml/OverlayLoginDialog.qml b/interface/resources/qml/OverlayLoginDialog.qml index 8f709af2ab..0ad2c57e5f 100644 --- a/interface/resources/qml/OverlayLoginDialog.qml +++ b/interface/resources/qml/OverlayLoginDialog.qml @@ -150,6 +150,6 @@ FocusScope { Component.onCompleted: { keyboardTimer.start(); - bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 3ad17430cd..8d6444bc0e 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -14,6 +14,8 @@ import QtQuick 2.5 import controlsUit 1.0 as HifiControlsUit import stylesUit 1.0 as HifiStylesUit +import TabletScriptingInterface 1.0 + import "../LoginDialog" FocusScope { @@ -25,10 +27,9 @@ FocusScope { width: parent.width height: parent.height - signal sendToScript(var message); - signal canceled(); + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); - property bool isHMD: false + property bool isHMD: HMD.active property bool gotoPreviousApp: false; property bool keyboardEnabled: false @@ -52,6 +53,7 @@ FocusScope { } function tryDestroy() { + tabletProxy.gotoHomeScreen(); } MouseArea { @@ -76,7 +78,7 @@ FocusScope { interval: 200 onTriggered: { - if (MenuInterface.isOptionChecked("Use 3D Keyboard")) { + if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) { KeyboardScriptingInterface.raised = true; } } @@ -169,11 +171,13 @@ FocusScope { Component.onDestruction: { loginKeyboard.raised = false; - KeyboardScriptingInterface.raised = false; + if (root.isHMD) { + KeyboardScriptingInterface.raised = false; + } } Component.onCompleted: { keyboardTimer.start(); - bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 68ac05ef18..30d422949f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -120,6 +120,7 @@ #include <plugins/PluginManager.h> #include <plugins/PluginUtils.h> #include <plugins/SteamClientPlugin.h> +#include <plugins/OculusPlatformPlugin.h> #include <plugins/InputConfiguration.h> #include <RecordingScriptingInterface.h> #include <render/EngineStats.h> @@ -800,7 +801,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { if (auto steamClient = pluginManager->getSteamClientPlugin()) { steamClient->init(); } - PROFILE_SET_THREAD_NAME("Main Thread"); #if defined(Q_OS_WIN) @@ -2737,6 +2737,7 @@ Application::~Application() { if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { steamClient->shutdown(); } + DependencyManager::destroy<PluginManager>(); DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache @@ -4873,6 +4874,10 @@ void Application::idle() { steamClient->runCallbacks(); } + if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlugin->handleOVREvents(); + } + float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND; _lastTimeUpdated.start(); diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 6e9c91785f..4359318833 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -18,6 +18,7 @@ #include <plugins/PluginManager.h> #include <plugins/SteamClientPlugin.h> +#include <plugins/OculusPlatformPlugin.h> #include <shared/GlobalAppProperties.h> #include <ui/TabletScriptingInterface.h> #include <UserActivityLogger.h> @@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const { return steamClient && steamClient->isRunning(); } -bool LoginDialog::isOculusStoreRunning() const { - return qApp->property(hifi::properties::OCULUS_STORE).toBool(); +bool LoginDialog::isOculusRunning() const { + auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin(); + return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning()); +} + +QString LoginDialog::oculusUserID() const { + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + return oculusPlatformPlugin->getOculusUserID(); + } + return ""; } void LoginDialog::dismissLoginDialog() { @@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const DependencyManager::get<AccountManager>()->requestAccessToken(username, password); } +void LoginDialog::loginThroughOculus() { + qDebug() << "Attempting to login through Oculus"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) { + DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID); + }); + } +} + +void LoginDialog::linkOculus() { + qDebug() << "Attempting to link Oculus account"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) { + if (nonce.isEmpty() || oculusID.isEmpty()) { + emit handleLoginFailed(); + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "linkCompleted"; + callbackParams.errorCallbackMethod = "linkFailed"; + const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link"; + + QJsonObject payload; + payload["oculus_nonce"] = nonce; + payload["oculus_id"] = oculusID; + + auto accountManager = DependencyManager::get<AccountManager>(); + accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required, + QNetworkAccessManager::PostOperation, callbackParams, + QJsonDocument(payload).toJson()); + }); + } +} + +void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) { + qDebug() << "Attempting to create account from Oculus info"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) { + if (nonce.isEmpty() || oculusID.isEmpty()) { + emit handleLoginFailed(); + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "createCompleted"; + callbackParams.errorCallbackMethod = "createFailed"; + + const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create"; + + QJsonObject payload; + payload["oculus_nonce"] = nonce; + payload["oculus_id"] = oculusID; + if (!email.isEmpty()) { + payload["email"] = email; + } + if (!username.isEmpty()) { + payload["username"] = username; + } + if (!password.isEmpty()) { + payload["password"] = password; + } + + auto accountManager = DependencyManager::get<AccountManager>(); + accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParams, + QJsonDocument(payload).toJson()); + }); + } +} + void LoginDialog::loginThroughSteam() { qDebug() << "Attempting to login through Steam"; if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { @@ -157,7 +239,7 @@ void LoginDialog::linkSteam() { const QString LINK_STEAM_PATH = "api/v1/user/steam/link"; QJsonObject payload; - payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket))); + payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket)); auto accountManager = DependencyManager::get<AccountManager>(); accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required, @@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) { const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create"; QJsonObject payload; - payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket))); + payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket)); if (!username.isEmpty()) { - payload.insert("username", QJsonValue::fromVariant(QVariant(username))); + payload["username"] = username; } auto accountManager = DependencyManager::get<AccountManager>(); @@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) { } void LoginDialog::createFailed(QNetworkReply* reply) { + if (isOculusRunning()) { + auto replyData = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(replyData, &parseError); + if (parseError.error != QJsonParseError::NoError) { + emit handleCreateFailed(reply->errorString()); + return; + } + auto data = doc["data"]; + auto error = data["error"]; + auto oculusError = data["oculus"]; + auto user = error["username"].toArray(); + auto uid = error["uid"].toArray(); + auto email = error["email"].toArray(); + auto password = error["password"].toArray(); + QString reply; + if (uid[0].isString()) { + emit handleCreateFailed("Oculus ID " + uid[0].toString() + "."); + return; + } + if (user[0].isString()) { + reply = "Username " + user[0].toString() + "."; + } + if (email[0].isString()) { + reply.append((!reply.isEmpty()) ? "\n" : ""); + reply.append("Email " + email[0].toString() + "."); + } + if (password[0].isString()) { + reply.append((!reply.isEmpty()) ? "\n" : ""); + reply.append("Password " + password[0].toString() + "."); + } + if (!oculusError.isNull() && !oculusError.isUndefined()) { + emit handleCreateFailed("Could not verify token with Oculus. Please try again."); + return; + } else { + emit handleCreateFailed(reply); + return; + } + } emit handleCreateFailed(reply->errorString()); } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 2714d654bf..7c932932cf 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -22,7 +22,6 @@ extern const QUrl OVERLAY_LOGIN_DIALOG; class LoginDialog : public OffscreenQmlDialog { Q_OBJECT - Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn) HIFI_QML_DECL public: @@ -67,24 +66,23 @@ protected slots: Q_INVOKABLE void dismissLoginDialog(); Q_INVOKABLE bool isSteamRunning() const; - Q_INVOKABLE bool isOculusStoreRunning() const; + Q_INVOKABLE bool isOculusRunning() const; + + Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE void login(const QString& username, const QString& password) const; Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void linkSteam(); Q_INVOKABLE void createAccountFromSteam(QString username = QString()); + Q_INVOKABLE void loginThroughOculus(); + Q_INVOKABLE void linkOculus(); + Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString()); Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE void openUrl(const QString& url) const; Q_INVOKABLE bool getLoginDialogPoppedUp() const; - -private: - bool getIsLogIn() const { return _isLogIn; } - void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; } - - bool _isLogIn{ false }; }; #endif // hifi_LoginDialog_h diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index a061a4c923..944d5e89d1 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -31,6 +31,8 @@ public: virtual void compositeExtra() override; + virtual void pluginUpdate() override {}; + protected: mutable bool _isThrottled = false; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 11563b3798..e4ff1b8b37 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -20,6 +20,7 @@ public: QImage getScreenshot(float aspectRatio = 0.0f) const override; QImage getSecondaryCameraScreenshot() const override; void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {}; + void pluginUpdate() override {}; private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index a56daaad83..4aeacbe05c 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -46,6 +46,8 @@ public: virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; }; + void pluginUpdate() override {}; + signals: void hmdMountedChanged(); void hmdVisibleChanged(bool visible); diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 5a7ca24059..a55bde0f4e 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -28,6 +28,8 @@ public: // to the HMD plugins. //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; + virtual void pluginUpdate() override {}; + protected: virtual bool internalActivate() override; virtual void internalDeactivate() override; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 989661cb81..f74b337ee7 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -588,6 +588,29 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } +void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + QUrl grantURL = _authURL; + grantURL.setPath("/oauth/token"); + + QByteArray postData; + postData.append("grant_type=password&"); + postData.append("oculus_nonce=" + nonce + "&"); + postData.append("oculus_id=" + oculusID + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + + request.setUrl(grantURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); +} + void AccountManager::refreshAccessToken() { // we can't refresh our access token if we don't have a refresh token, so check for that first diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index ca2b826c98..477488031d 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -106,6 +106,7 @@ public: public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); + void requestAccessTokenWithOculus(const QString& nonce, const QString& oculusID); void requestAccessTokenWithAuthCode(const QString& authCode, const QString& clientId, const QString& clientSecret, diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index ad49ceafe6..fde43e7a5b 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -217,6 +217,9 @@ public: static const QString& MENU_PATH(); + // for updating plugin-related commands. Mimics the input plugin. + virtual void pluginUpdate() = 0; + signals: void recommendedFramebufferSizeChanged(const QSize& size); void resetSensorsRequested(); diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 90746d648e..fc1e12b639 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -21,6 +21,7 @@ class DisplayPlugin; class InputPlugin; class CodecPlugin; class SteamClientPlugin; +class OculusPlatformPlugin; class Plugin; class PluginContainer; class PluginManager; @@ -35,4 +36,5 @@ using CodecPluginPointer = std::shared_ptr<CodecPlugin>; using CodecPluginList = std::vector<CodecPluginPointer>; using CodecPluginProvider = std::function<CodecPluginList()>; using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>; +using OculusPlatformPluginPointer = std::shared_ptr<OculusPlatformPlugin>; using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>; diff --git a/libraries/plugins/src/plugins/OculusPlatformPlugin.h b/libraries/plugins/src/plugins/OculusPlatformPlugin.h new file mode 100644 index 0000000000..93bf534c6e --- /dev/null +++ b/libraries/plugins/src/plugins/OculusPlatformPlugin.h @@ -0,0 +1,28 @@ +// +// Created by Wayne Chen on 2018/12/20 +// Copyright 2018 High Fidelity Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2-0.html +// +#pragma once + +#include <QtCore/QString> + +#include <functional> + +using NonceUserIDCallback = std::function<void(QString, QString)>; + +class OculusPlatformPlugin { +public: + virtual ~OculusPlatformPlugin() = default; + + virtual QString getName() const = 0; + virtual QString getOculusUserID() const = 0; + + virtual bool isRunning() const = 0; + + virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0; + + virtual void handleOVREvents() = 0; +}; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 13fa75f030..0d0209e35f 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -188,6 +188,22 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() { return steamClientPlugin; } +const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() { + static OculusPlatformPluginPointer oculusPlatformPlugin; + static std::once_flag once; + std::call_once(once, [&] { + // Now grab the dynamic plugins + for (auto loader : getLoadedPlugins()) { + OculusPlatformProvider* oculusPlatformProvider = qobject_cast<OculusPlatformProvider*>(loader->instance()); + if (oculusPlatformProvider) { + oculusPlatformPlugin = oculusPlatformProvider->getOculusPlatformPlugin(); + break; + } + } + }); + return oculusPlatformPlugin; +} + const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; static auto deviceAddedCallback = [](QString deviceName) { diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index e340b2fa21..1a578c7406 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -27,6 +27,7 @@ public: const InputPluginList& getInputPlugins(); const CodecPluginList& getCodecPlugins(); const SteamClientPluginPointer getSteamClientPlugin(); + const OculusPlatformPluginPointer getOculusPlatformPlugin(); DisplayPluginList getPreferredDisplayPlugins(); void setPreferredDisplayPlugins(const QStringList& displays); diff --git a/libraries/plugins/src/plugins/RuntimePlugin.h b/libraries/plugins/src/plugins/RuntimePlugin.h index 9a7d6e0638..756b4ff585 100644 --- a/libraries/plugins/src/plugins/RuntimePlugin.h +++ b/libraries/plugins/src/plugins/RuntimePlugin.h @@ -51,5 +51,13 @@ public: virtual SteamClientPluginPointer getSteamClientPlugin() = 0; }; +class OculusPlatformProvider { +public: + virtual OculusPlatformPluginPointer getOculusPlatformPlugin() = 0; +}; + #define SteamClientProvider_iid "com.highfidelity.plugins.steamclient" Q_DECLARE_INTERFACE(SteamClientProvider, SteamClientProvider_iid) + +#define OculusPlatformProvider_iid "com.highfidelity.plugins.oculusplatform" +Q_DECLARE_INTERFACE(OculusPlatformProvider, OculusPlatformProvider_iid) \ No newline at end of file diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index f10aba7920..a67e3127e5 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -12,6 +12,7 @@ #include <display-plugins/CompositorHelper.h> #include <gpu/Frame.h> #include <gl/Config.h> +#include <shared/GlobalAppProperties.h> #include "OculusHelpers.h" @@ -30,7 +31,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return false; } - if (ovr::quitRequested(status) || ovr::displayLost(status) || !ovr::handleOVREvents()) { + if (ovr::quitRequested(status) || ovr::displayLost(status)) { QMetaObject::invokeMethod(qApp, "quit"); return false; } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 547d3ee5fe..1abb7cdad7 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -13,6 +13,9 @@ #include <OVR_CAPI_GL.h> +#define OVRPL_DISABLED +#include <OVR_Platform.h> + class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: @@ -30,7 +33,7 @@ public: QRectF getPlayAreaRect() override; QVector<glm::vec3> getSensorPositions() override; - + protected: void customizeContext() override; void uncustomizeContext() override; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 548afb97ab..2693b9ee7e 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -296,34 +296,3 @@ controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, pose.valid = true; return pose; } - -bool hifi::ovr::handleOVREvents() { -#ifdef OCULUS_APP_ID - if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { - // pop messages to see if we got a return for an entitlement check - ovrMessageHandle message = ovr_PopMessage(); - - while (message) { - switch (ovr_Message_GetType(message)) { - case ovrMessage_Entitlement_GetIsViewerEntitled: { - if (!ovr_Message_IsError(message)) { - // this viewer is entitled, no need to flag anything - qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally"; - } else { - // we failed the entitlement check, quit - qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; - return false; - } - } - } - - // free the message handle to cleanup and not leak - ovr_FreeMessage(message); - - // pop the next message to check, if there is one - message = ovr_PopMessage(); - } - } -#endif - return true; -} diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index bdfc4434bb..3587117825 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -30,7 +30,6 @@ struct ovr { static ovrSessionStatus getStatus(ovrResult& result); static ovrTrackingState getTrackingState(double absTime = 0.0, ovrBool latencyMarker = ovrFalse); static QString getError(); - static bool handleOVREvents(); static inline bool quitRequested() { return quitRequested(getStatus()); } static inline bool reorientRequested() { return reorientRequested(getStatus()); } diff --git a/plugins/oculus/src/OculusPlatformPlugin.cpp b/plugins/oculus/src/OculusPlatformPlugin.cpp new file mode 100644 index 0000000000..27fb98c8b5 --- /dev/null +++ b/plugins/oculus/src/OculusPlatformPlugin.cpp @@ -0,0 +1,109 @@ +// +// Created by Wayne Chen on 2019/01/08 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OculusPlatformPlugin.h" + +#include <shared/GlobalAppProperties.h> + +#include <QMetaObject> + +#include "OculusHelpers.h" + +QString OculusAPIPlugin::NAME { "Oculus Rift" }; + +OculusAPIPlugin::OculusAPIPlugin() { + _session = hifi::ovr::acquireRenderSession(); +} + +OculusAPIPlugin::~OculusAPIPlugin() { + hifi::ovr::releaseRenderSession(_session); +} + +bool OculusAPIPlugin::isRunning() const { + return (qApp->property(hifi::properties::OCULUS_STORE).toBool()); +} + +void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) { +#ifdef OCULUS_APP_ID + _nonceUserIDCallback = callback; + ovr_User_GetUserProof(); + ovr_User_GetLoggedInUser(); +#endif +} + +void OculusAPIPlugin::handleOVREvents() { +#ifdef OCULUS_APP_ID + if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { + // pop messages to see if we got a return for an entitlement check + ovrMessageHandle message { nullptr }; + + // pop the next message to check, if there is one + while ((message = ovr_PopMessage())) { + switch (ovr_Message_GetType(message)) { + case ovrMessage_Entitlement_GetIsViewerEntitled: { + if (!ovr_Message_IsError(message)) { + // this viewer is entitled, no need to flag anything + qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally"; + } else { + // we failed the entitlement check, quit + qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; + QMetaObject::invokeMethod(qApp, "quit"); + } + break; + } + case ovrMessage_User_Get: { + if (!ovr_Message_IsError(message)) { + qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded"; + ovrUserHandle user = ovr_Message_GetUser(message); + _user = ovr_User_GetOculusID(user); + // went all the way through the `requestNonceAndUserID()` pipeline successfully. + } else { + qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + // emit the signal so we don't hang for it anywhere else. + _user = ""; + } + break; + } + case ovrMessage_User_GetLoggedInUser: { + if (!ovr_Message_IsError(message)) { + ovrUserHandle user = ovr_Message_GetUser(message); + _userID = ovr_User_GetID(user); + ovr_User_Get(_userID); + } else { + qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + // emit the signal so we don't hang for it anywhere else. + } + _userIDChanged = true; + break; + } + case ovrMessage_User_GetUserProof: { + if (!ovr_Message_IsError(message)) { + ovrUserProofHandle userProof = ovr_Message_GetUserProof(message); + _nonce = ovr_UserProof_GetNonce(userProof); + qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce; + } else { + qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + _nonce = ""; + // emit the signal so we don't hang for it anywhere else. + } + _nonceChanged = true; + break; + } + } + + if (_nonceChanged && _userIDChanged) { + _nonceUserIDCallback(_nonce, QString::number(_userID)); + _nonceChanged = _userIDChanged = false; + } + + // free the message handle to cleanup and not leak + ovr_FreeMessage(message); + } + } +#endif +} diff --git a/plugins/oculus/src/OculusPlatformPlugin.h b/plugins/oculus/src/OculusPlatformPlugin.h new file mode 100644 index 0000000000..3d80540419 --- /dev/null +++ b/plugins/oculus/src/OculusPlatformPlugin.h @@ -0,0 +1,39 @@ +// +// Created by Wayne Chen on 2019/01/08 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include <plugins/OculusPlatformPlugin.h> + +#include <OVR_CAPI_GL.h> + +#define OVRPL_DISABLED +#include <OVR_Platform.h> + +class OculusAPIPlugin : public OculusPlatformPlugin { +public: + OculusAPIPlugin(); + virtual ~OculusAPIPlugin(); + QString getName() const { return NAME; } + QString getOculusUserID() const { return _user; }; + + bool isRunning() const; + + virtual void requestNonceAndUserID(NonceUserIDCallback callback); + + virtual void handleOVREvents(); + +private: + static QString NAME; + NonceUserIDCallback _nonceUserIDCallback; + QString _nonce; + bool _nonceChanged{ false }; + bool _userIDChanged{ false }; + QString _user; + ovrID _userID; + ovrSession _session; +}; diff --git a/plugins/oculus/src/OculusProvider.cpp b/plugins/oculus/src/OculusProvider.cpp index 47ccc5304e..67871b8610 100644 --- a/plugins/oculus/src/OculusProvider.cpp +++ b/plugins/oculus/src/OculusProvider.cpp @@ -18,15 +18,18 @@ #include "OculusDisplayPlugin.h" #include "OculusDebugDisplayPlugin.h" +#include "OculusPlatformPlugin.h" #include "OculusControllerManager.h" -class OculusProvider : public QObject, public DisplayProvider, InputProvider +class OculusProvider : public QObject, public DisplayProvider, InputProvider, OculusPlatformProvider { Q_OBJECT Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "oculus.json") Q_INTERFACES(DisplayProvider) Q_PLUGIN_METADATA(IID InputProvider_iid FILE "oculus.json") Q_INTERFACES(InputProvider) + Q_PLUGIN_METADATA(IID OculusPlatformProvider_iid FILE "oculus.json") + Q_INTERFACES(OculusPlatformProvider) public: OculusProvider(QObject* parent = nullptr) : QObject(parent) {} @@ -62,6 +65,15 @@ public: return _inputPlugins; } + virtual OculusPlatformPluginPointer getOculusPlatformPlugin() override { + static std::once_flag once; + std::call_once(once, [&] { + _oculusPlatformPlugin = std::make_shared<OculusAPIPlugin>(); + + }); + return _oculusPlatformPlugin; + } + virtual void destroyInputPlugins() override { _inputPlugins.clear(); } @@ -73,6 +85,7 @@ public: private: DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; + OculusPlatformPluginPointer _oculusPlatformPlugin; }; #include "OculusProvider.moc"