History

Merge pull request #14727 from wayne-chen/oculusLoginFeature

Jeff Clinton committed 7 years ago

Commit f78fdff2 parents 17b8f65 1c6521430 files · +983 −215Browse files

Unified · Split

30 files
  1. interface/resources/qml/LoginDialog.qml +1 −1
  2. interface/resources/qml/LoginDialog/CompleteProfileBody.qml +300 −69
  3. interface/resources/qml/LoginDialog/LinkAccountBody.qml +30 −57
  4. interface/resources/qml/LoginDialog/LoggingInBody.qml +114 −18
  5. interface/resources/qml/LoginDialog/SignUpBody.qml +54 −6
  6. interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +78 −8
  7. interface/resources/qml/OverlayLoginDialog.qml +1 −1
  8. interface/resources/qml/dialogs/TabletLoginDialog.qml +10 −6
  9. interface/src/Application.cpp +6 −1
  10. interface/src/ui/LoginDialog.cpp +126 −5
  11. interface/src/ui/LoginDialog.h +6 −8
  12. libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +2 −0
  13. libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +1 −0
  14. libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +2 −0
  15. libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +2 −0
  16. libraries/networking/src/AccountManager.cpp +23 −0
  17. libraries/networking/src/AccountManager.h +1 −0
  18. libraries/plugins/src/plugins/DisplayPlugin.h +3 −0
  19. libraries/plugins/src/plugins/Forward.h +2 −0
  20. libraries/plugins/src/plugins/OculusPlatformPlugin.h +28 −0
  21. libraries/plugins/src/plugins/PluginManager.cpp +16 −0
  22. libraries/plugins/src/plugins/PluginManager.h +1 −0
  23. libraries/plugins/src/plugins/RuntimePlugin.h +8 −0
  24. plugins/oculus/src/OculusBaseDisplayPlugin.cpp +2 −1
  25. plugins/oculus/src/OculusBaseDisplayPlugin.h +4 −1
  26. plugins/oculus/src/OculusHelpers.cpp +0 −31
  27. plugins/oculus/src/OculusHelpers.h +0 −1
  28. plugins/oculus/src/OculusPlatformPlugin.cpp +109 −0
  29. plugins/oculus/src/OculusPlatformPlugin.h +39 −0
  30. plugins/oculus/src/OculusProvider.cpp +14 −1
interface/resources/qml/LoginDialog.qml+1−1
@@ -120,6 +120,6 @@ FocusScope { · 119 unmodified lines
120120 }
121121
122122 Component.onCompleted: {
123− bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
123+ bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
124124 }
125125 }
interface/resources/qml/LoginDialog/CompleteProfileBody.qml+300−69
@@ -22,11 +22,16 @@ Item { · 21 unmodified lines
2222 width: root.width
2323 height: root.height
2424 readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
25+ readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
26+ readonly property int textFieldHeight: 31
2527 readonly property string fontFamily: "Raleway"
2628 readonly property int fontSize: 15
2729 readonly property bool fontBold: true
30+ readonly property int textFieldFontSize: 18
31+ readonly property var passwordImageRatio: 16 / 23
2832
29− readonly property bool withSteam: withSteam
33+ property bool withOculus: withOculus
34+ property bool withSteam: withSteam
3035 property string errorString: errorString
3136
3237 readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@@ -61,15 +66,20 @@ Item { · 28 unmodified lines
6166
6267 Item {
6368 id: contentItem
64− anchors.fill: parent
69+ width: parent.width
70+ height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height +
71+ termsContainer.height
72+ anchors.top: parent.top
73+ anchors.topMargin: root.bannerHeight + 0.25 * parent.height
74+ anchors.left: parent.left
6575
6676 Item {
6777 id: errorContainer
68− width: parent.width
78+ width: root.bannerWidth
6979 height: loginErrorMessageTextMetrics.height
7080 anchors {
71− bottom: buttons.top;
72− bottomMargin: hifi.dimensions.contentSpacing.y;
81+ bottom: completeProfileBody.withOculus ? fields.top : buttons.top;
82+ bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y;
7383 left: buttons.left;
7484 }
7585 TextMetrics {
@@ -79,8 +89,8 @@ Item { · 3 unmodified lines
7989 }
8090 Text {
8191 id: loginErrorMessage;
82− width: root.bannerWidth
8392 color: "red";
93+ width: root.bannerWidth;
8494 font.family: completeProfileBody.fontFamily
8595 font.pixelSize: 18
8696 font.bold: completeProfileBody.fontBold
@@ -88,13 +98,196 @@ Item { · 1 unmodified line
8898 horizontalAlignment: Text.AlignHCenter
8999 text: completeProfileBody.errorString
90100 visible: true
101+ onTextChanged: {
102+ mainContainer.recalculateErrorMessage();
103+ }
104+ Component.onCompleted: {
105+ mainContainer.recalculateErrorMessage();
106+ }
107+ }
108+ }
109+
110+ Item {
111+ id: fields
112+ width: root.bannerWidth
113+ height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y
114+ visible: completeProfileBody.withOculus
115+ anchors {
116+ left: parent.left
117+ leftMargin: (parent.width - root.bannerWidth) / 2
118+ bottom: buttons.top
119+ bottomMargin: hifi.dimensions.contentSpacing.y
120+ }
121+
122+ HifiControlsUit.TextField {
123+ id: usernameField
124+ width: root.bannerWidth
125+ height: completeProfileBody.textFieldHeight
126+ placeholderText: "Username"
127+ font.pixelSize: completeProfileBody.textFieldFontSize
128+ styleRenderType: Text.QtRendering
129+ anchors {
130+ top: parent.top
131+ }
132+ Keys.onPressed: {
133+ if (!usernameField.visible) {
134+ return;
135+ }
136+ switch (event.key) {
137+ case Qt.Key_Tab:
138+ event.accepted = true;
139+ if (event.modifiers === Qt.ShiftModifier) {
140+ passwordField.focus = true;
141+ } else {
142+ emailField.focus = true;
143+ }
144+ break;
145+ case Qt.Key_Backtab:
146+ event.accepted = true;
147+ passwordField.focus = true;
148+ break;
149+ case Qt.Key_Enter:
150+ case Qt.Key_Return:
151+ event.accepted = true;
152+ loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
153+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
154+ "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
155+ break;
156+ }
157+ }
158+ onFocusChanged: {
159+ root.text = "";
160+ if (focus) {
161+ root.isPassword = false;
162+ }
163+ }
164+ Component.onCompleted: {
165+ var userID = "";
166+ if (completeProfileBody.withOculus) {
167+ userID = loginDialog.oculusUserID();
168+ }
169+ usernameField.text = userID;
170+ }
171+ }
172+ HifiControlsUit.TextField {
173+ id: emailField
174+ width: root.bannerWidth
175+ height: completeProfileBody.textFieldHeight
176+ anchors {
177+ top: usernameField.bottom
178+ topMargin: hifi.dimensions.contentSpacing.y
179+ }
180+ placeholderText: "Email"
181+ font.pixelSize: completeProfileBody.textFieldFontSize
182+ styleRenderType: Text.QtRendering
183+ activeFocusOnPress: true
184+ Keys.onPressed: {
185+ switch (event.key) {
186+ case Qt.Key_Tab:
187+ event.accepted = true;
188+ if (event.modifiers === Qt.ShiftModifier) {
189+ usernameField.focus = true;
190+ } else {
191+ passwordField.focus = true;
192+ }
193+ break;
194+ case Qt.Key_Backtab:
195+ event.accepted = true;
196+ usernameField.focus = true;
197+ break;
198+ case Qt.Key_Enter:
199+ case Qt.Key_Return:
200+ event.accepted = true;
201+ loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
202+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
203+ "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
204+ break;
205+ }
206+ }
207+ onFocusChanged: {
208+ root.text = "";
209+ if (focus) {
210+ root.isPassword = false;
211+ }
212+ }
91213 }
92− Component.onCompleted: {
93− if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) {
94− loginErrorMessage.wrapMode = Text.WordWrap;
95− loginErrorMessage.verticalAlignment = Text.AlignLeft;
96− loginErrorMessage.horizontalAlignment = Text.AlignLeft;
97− errorContainer.height = 3 * loginErrorMessageTextMetrics.height;
214+ HifiControlsUit.TextField {
215+ id: passwordField
216+ width: root.bannerWidth
217+ height: completeProfileBody.textFieldHeight
218+ placeholderText: "Password (optional)"
219+ font.pixelSize: completeProfileBody.textFieldFontSize
220+ styleRenderType: Text.QtRendering
221+ activeFocusOnPress: true
222+ echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password
223+ anchors {
224+ top: emailField.bottom
225+ topMargin: hifi.dimensions.contentSpacing.y
226+ }
227+
228+ onFocusChanged: {
229+ root.text = "";
230+ root.isPassword = focus;
231+ }
232+
233+ Item {
234+ id: showPasswordContainer
235+ z: 10
236+ // width + image's rightMargin
237+ width: showPasswordImage.width + 8
238+ height: parent.height
239+ anchors {
240+ right: parent.right
241+ }
242+
243+ Image {
244+ id: showPasswordImage
245+ width: passwordField.height * passwordImageRatio
246+ height: passwordField.height * passwordImageRatio
247+ anchors {
248+ right: parent.right
249+ rightMargin: 8
250+ top: parent.top
251+ topMargin: passwordFieldMouseArea.showPassword ? 6 : 8
252+ bottom: parent.bottom
253+ bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8
254+ }
255+ source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"
256+ MouseArea {
257+ id: passwordFieldMouseArea
258+ anchors.fill: parent
259+ acceptedButtons: Qt.LeftButton
260+ property bool showPassword: false
261+ onClicked: {
262+ showPassword = !showPassword;
263+ }
264+ }
265+ }
266+ }
267+ Keys.onPressed: {
268+ switch (event.key) {
269+ case Qt.Key_Tab:
270+ event.accepted = true;
271+ if (event.modifiers === Qt.ShiftModifier) {
272+ emailField.focus = true;
273+ } else if (usernameField.visible) {
274+ usernameField.focus = true;
275+ } else {
276+ emailField.focus = true;
277+ }
278+ break;
279+ case Qt.Key_Backtab:
280+ event.accepted = true;
281+ emailField.focus = true;
282+ break;
283+ case Qt.Key_Enter:
284+ case Qt.Key_Return:
285+ event.accepted = true;
286+ loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
287+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
288+ "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
289+ break;
290+ }
98291 }
99292 }
100293 }
@@ -105,7 +298,7 @@ Item { · 4 unmodified lines
105298 height: d.minHeightButton
106299 anchors {
107300 top: parent.top
108− topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y
301+ topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y
109302 left: parent.left
110303 leftMargin: (parent.width - root.bannerWidth) / 2
111304 }
@@ -144,7 +337,7 @@ Item { · 32 unmodified lines
144337 width: (parent.width - hifi.dimensions.contentSpacing.x) / 2
145338 height: d.minHeightButton
146339
147− text: qsTr("Create your profile")
340+ text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile")
148341 color: hifi.buttons.blue
149342
150343 fontFamily: completeProfileBody.fontFamily
@@ -158,116 +351,149 @@ Item { · 7 unmodified lines
158351 UserActivityLogger.logAction("encourageLoginDialog", data);
159352 }
160353 loginErrorMessage.visible = false;
161− loginDialog.createAccountFromSteam();
354+ if (completeProfileBody.withOculus) {
355+ loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
356+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
357+ "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
358+ } else if (completeProfileBody.withSteam) {
359+ loginDialog.createAccountFromSteam();
360+ }
162361 }
163362 }
164363 }
165364
166365 Item {
167− id: additionalTextContainer
366+ id: termsContainer
168367 width: parent.width
169− height: additionalTextMetrics.height
368+ height: termsTextMetrics.height
170369 anchors {
171370 top: buttons.bottom
172371 horizontalCenter: parent.horizontalCenter
173372 topMargin: hifi.dimensions.contentSpacing.y
174373 left: parent.left
175374 }
176−
177375 TextMetrics {
178− id: additionalTextMetrics
179− font: additionalText.font
180− text: "Already have a High Fidelity profile? Link to an existing profile here."
376+ id: termsTextMetrics
377+ font: termsText.font
378+ text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
379+ Component.onCompleted: {
380+ // with the link.
381+ if (completeProfileBody.withOculus) {
382+ termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
383+ } else {
384+ termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
385+ }
386+ }
181387 }
182388
183− HifiStylesUit.ShortcutText {
184− id: additionalText
185− text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
186−
389+ HifiStylesUit.InfoItem {
390+ id: termsText
391+ text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
187392 font.family: completeProfileBody.fontFamily
188393 font.pixelSize: completeProfileBody.fontSize
189394 font.bold: completeProfileBody.fontBold
190− wrapMode: Text.NoWrap
395+ wrapMode: Text.WordWrap
396+ color: hifi.colors.white
397+ linkColor: hifi.colors.blueAccent
191398 lineHeight: 1
192399 lineHeightMode: Text.ProportionalHeight
193− horizontalAlignment: Text.AlignHCenter
194− linkColor: hifi.colors.blueAccent
195400
196− onLinkActivated: {
197− loginDialog.isLogIn = true;
198− bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true });
199− }
401+ onLinkActivated: loginDialog.openUrl(link);
402+
200403 Component.onCompleted: {
201− if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) {
202− additionalText.width = root.bannerWidth;
203− additionalText.wrapMode = Text.WordWrap;
404+ if (termsTextMetrics.width > root.bannerWidth) {
405+ termsText.width = root.bannerWidth;
406+ termsText.wrapMode = Text.WordWrap;
204407 additionalText.verticalAlignment = Text.AlignLeft;
205408 additionalText.horizontalAlignment = Text.AlignLeft;
206− additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
207− additionalTextContainer.anchors.left = buttons.left;
409+ termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
410+ termsContainer.anchors.left = buttons.left;
208411 } else {
209− additionalText.anchors.centerIn = additionalTextContainer;
412+ termsText.anchors.centerIn = termsContainer;
210413 }
211414 }
212415 }
213416 }
214417
215418 Item {
216− id: termsContainer
419+ id: additionalTextContainer
217420 width: parent.width
218− height: termsTextMetrics.height
421+ height: additionalTextMetrics.height
219422 anchors {
220− top: additionalTextContainer.bottom
423+ top: termsContainer.bottom
221424 horizontalCenter: parent.horizontalCenter
222425 topMargin: 2 * hifi.dimensions.contentSpacing.y
223426 left: parent.left
224427 }
428+
225429 TextMetrics {
226− id: termsTextMetrics
227− font: termsText.font
228− text: completeProfileBody.termsContainerText
229− Component.onCompleted: {
230− // with the link.
231− termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
232− }
430+ id: additionalTextMetrics
431+ font: additionalText.font
432+ text: "Already have a High Fidelity profile? Link to an existing profile here."
233433 }
234434
235− HifiStylesUit.InfoItem {
236− id: termsText
237− text: completeProfileBody.termsContainerText
435+ HifiStylesUit.ShortcutText {
436+ id: additionalText
437+ text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
438+ width: root.bannerWidth;
238439 font.family: completeProfileBody.fontFamily
239440 font.pixelSize: completeProfileBody.fontSize
240441 font.bold: completeProfileBody.fontBold
241− wrapMode: Text.WordWrap
242− color: hifi.colors.lightGray
243− linkColor: hifi.colors.blueAccent
442+ wrapMode: Text.NoWrap
244443 lineHeight: 1
245444 lineHeightMode: Text.ProportionalHeight
445+ horizontalAlignment: Text.AlignHCenter
446+ linkColor: hifi.colors.blueAccent
246447
247− onLinkActivated: loginDialog.openUrl(link);
248−
448+ onLinkActivated: {
449+ bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "",
450+ "withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus,
451+ "linkOculus": completeProfileBody.withOculus });
452+ }
249453 Component.onCompleted: {
250− if (termsTextMetrics.width > root.bannerWidth && root.isTablet) {
251− termsText.width = root.bannerWidth;
252− termsText.wrapMode = Text.WordWrap;
454+ if (additionalTextMetrics.width > root.bannerWidth) {
455+ additionalText.wrapMode = Text.WordWrap;
253456 additionalText.verticalAlignment = Text.AlignLeft;
254457 additionalText.horizontalAlignment = Text.AlignLeft;
255− termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
256− termsContainer.anchors.left = buttons.left;
458+ additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
459+ additionalTextContainer.anchors.left = buttons.left;
257460 } else {
258− termsText.anchors.centerIn = termsContainer;
461+ additionalText.anchors.centerIn = additionalTextContainer;
259462 }
260463 }
261464 }
262465 }
263466 }
467+ function recalculateErrorMessage() {
468+ if (completeProfileBody.errorString !== "") {
469+ loginErrorMessage.visible = true;
470+ var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length;
471+ var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n");
472+ loginErrorMessage.text = errorStringEdited;
473+ if (errorLength > 1.0) {
474+ loginErrorMessage.wrapMode = Text.WordWrap;
475+ loginErrorMessage.verticalAlignment = Text.AlignLeft;
476+ loginErrorMessage.horizontalAlignment = Text.AlignLeft;
477+ errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
478+ } else if (loginErrorMessageTextMetrics.width > root.bannerWidth) {
479+ loginErrorMessage.wrapMode = Text.WordWrap;
480+ loginErrorMessage.verticalAlignment = Text.AlignLeft;
481+ loginErrorMessage.horizontalAlignment = Text.AlignLeft;
482+ errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height;
483+ } else {
484+ loginErrorMessage.wrapMode = Text.NoWrap;
485+ loginErrorMessage.verticalAlignment = Text.AlignVCenter;
486+ loginErrorMessage.horizontalAlignment = Text.AlignHCenter;
487+ errorContainer.height = loginErrorMessageTextMetrics.height;
488+ }
489+ }
490+ }
264491 }
265492
266493 Connections {
267494 target: loginDialog
268495 onHandleCreateCompleted: {
269− console.log("Create Succeeded")
270−
496+ console.log("Create Succeeded");
271497 if (completeProfileBody.withSteam) {
272498 if (completeProfileBody.loginDialogPoppedUp) {
273499 var data = {
@@ -277,20 +503,24 @@ Item { · 3 unmodified lines
277503 }
278504 loginDialog.loginThroughSteam();
279505 }
280− bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false });
506+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false,
507+ "withOculus": completeProfileBody.withOculus, "linkOculus": false });
281508 }
282509 onHandleCreateFailed: {
283510 console.log("Create Failed: " + error);
284− if (completeProfileBody.withSteam) {
511+ if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
285512 if (completeProfileBody.loginDialogPoppedUp) {
513+ action = completeProfileBody.withSteam ? "Steam" : "Oculus";
286514 var data = {
287− "action": "user failed to create a profile with Steam from the complete profile screen"
515+ "action": "user failed to create a profile with " + action + " from the complete profile screen"
288516 }
289517 UserActivityLogger.logAction("encourageLoginDialog", data);
290518 }
291519 }
292−
293− bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam });
520+ if (!completeProfileBody.withOculus) {
521+ bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
522+ "withOculus": completeProfileBody.withOculus });
523+ }
294524 }
295525 }
296526
@@ -302,5 +532,6 @@ Item { · 5 unmodified lines
302532 }
303533 d.resize();
304534 root.text = "";
535+ usernameField.forceActiveFocus();
305536 }
306537 }
interface/resources/qml/LoginDialog/LinkAccountBody.qml+30−57
@@ -36,9 +36,10 @@ Item { · 35 unmodified lines
3636 property bool keyboardRaised: false
3737 property bool punctuationMode: false
3838
39− property bool withSteam: false
39+ property bool withSteam: withSteam
4040 property bool linkSteam: linkSteam
41− property bool withOculus: false
41+ property bool withOculus: withOculus
42+ property bool linkOculus: linkOculus
4243 property string errorString: errorString
4344 property bool lostFocus: false
4445
@@ -83,23 +84,24 @@ Item { · 38 unmodified lines
8384 }
8485 UserActivityLogger.logAction("encourageLoginDialog", data);
8586 }
86−
87− bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
87+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
88+ "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
8889 }
8990
9091 function init() {
9192 // going to/from sign in/up dialog.
92− loginDialog.isLogIn = true;
9393 loginErrorMessage.text = linkAccountBody.errorString;
9494 loginErrorMessage.visible = (linkAccountBody.errorString !== "");
95− loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account";
95+ if (loginErrorMessageTextMetrics.width > emailField.width) {
96+ loginErrorMessage.wrapMode = Text.WordWrap;
97+ errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height;
98+ }
99+ loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account";
96100 loginButton.color = hifi.buttons.blue;
97101 emailField.placeholderText = "Username or Email";
98102 var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
99103 emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
100− if (linkAccountBody.linkSteam) {
101− steamInfoText.anchors.top = passwordField.bottom;
102− keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom;
104+ if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
103105 loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2;
104106 loginButton.anchors.right = emailField.right;
105107 } else {
@@ -125,7 +127,7 @@ Item { · 19 unmodified lines
125127 id: loginContainer
126128 width: emailField.width
127129 height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y +
128− keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height
130+ keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height
129131 anchors {
130132 top: parent.top
131133 topMargin: root.bannerHeight + 0.25 * parent.height
@@ -135,7 +137,7 @@ Item { · 3 unmodified lines
135137
136138 Item {
137139 id: errorContainer
138− width: loginErrorMessageTextMetrics.width
140+ width: parent.width
139141 height: loginErrorMessageTextMetrics.height
140142 anchors {
141143 bottom: emailField.top;
@@ -304,7 +306,7 @@ Item { · 162 unmodified lines
304306 fontSize: linkAccountBody.fontSize
305307 fontBold: linkAccountBody.fontBold
306308 color: hifi.buttons.noneBorderlessWhite;
307− visible: linkAccountBody.linkSteam
309+ visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus
308310 anchors {
309311 top: keepMeLoggedInCheckbox.bottom
310312 topMargin: hifi.dimensions.contentSpacing.y
@@ -315,10 +317,9 @@ Item { · 4 unmodified lines
315317 "action": "user clicked cancel at link account screen"
316318 };
317319 UserActivityLogger.logAction("encourageLoginDialog", data);
318− loginDialog.dismissLoginDialog();
319320 }
320−
321− bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" });
321+ bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
322+ "withOculus": linkAccountBody.withOculus, "errorString": "" });
322323 }
323324 }
324325 HifiControlsUit.Button {
@@ -337,33 +338,6 @@ Item { · 12 unmodified lines
337338 linkAccountBody.login();
338339 }
339340 }
340− TextMetrics {
341− id: steamInfoTextMetrics
342− font: steamInfoText.font
343− text: steamInfoText.text
344− }
345− Text {
346− id: steamInfoText
347− width: root.bannerWidth
348− visible: linkAccountBody.linkSteam
349− anchors {
350− top: loginButton.bottom
351− topMargin: hifi.dimensions.contentSpacing.y
352− left: emailField.left
353− }
354−
355− font.family: linkAccountBody.fontFamily
356− font.pixelSize: linkAccountBody.textFieldFontSize
357− color: "white"
358− text: qsTr("Your Steam account information will not be exposed to others.");
359− verticalAlignment: Text.AlignVCenter
360− horizontalAlignment: Text.AlignHCenter
361− Component.onCompleted: {
362− if (steamInfoTextMetrics.width > root.bannerWidth) {
363− steamInfoText.wrapMode = Text.WordWrap;
364− }
365− }
366− }
367341 TextMetrics {
368342 id: cantAccessTextMetrics
369343 font: cantAccessText.font
@@ -372,7 +346,7 @@ Item { · 2 unmodified lines
372346 HifiStylesUit.ShortcutText {
373347 id: cantAccessText
374348 z: 10
375− visible: !linkAccountBody.linkSteam
349+ visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
376350 anchors {
377351 top: loginButton.bottom
378352 topMargin: hifi.dimensions.contentSpacing.y
@@ -423,10 +397,10 @@ Item { · 44 unmodified lines
423397 buttonGlyphSize: 24
424398 buttonGlyphRightMargin: 10
425399 onClicked: {
426− // if (loginDialog.isOculusStoreRunning()) {
427− // linkAccountBody.withOculus = true;
428− // loginDialog.loginThroughSteam();
429− // } else
400+ if (loginDialog.isOculusRunning()) {
401+ linkAccountBody.withOculus = true;
402+ loginDialog.loginThroughOculus();
403+ } else
430404 if (loginDialog.isSteamRunning()) {
431405 linkAccountBody.withSteam = true;
432406 loginDialog.loginThroughSteam();
@@ -446,18 +420,17 @@ Item { · 13 unmodified lines
446420 }
447421
448422 bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
449− "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
423+ "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
450424 }
451425 Component.onCompleted: {
452− if (linkAccountBody.linkSteam) {
426+ if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
453427 continueButton.visible = false;
454428 return;
455429 }
456− // if (loginDialog.isOculusStoreRunning()) {
457− // continueButton.text = qsTr("CONTINUE WITH OCULUS");
458− // continueButton.buttonGlyph = hifi.glyphs.oculus;
459− // } else
460− if (loginDialog.isSteamRunning()) {
430+ if (loginDialog.isOculusRunning()) {
431+ continueButton.text = qsTr("CONTINUE WITH OCULUS");
432+ continueButton.buttonGlyph = hifi.glyphs.oculus;
433+ } else if (loginDialog.isSteamRunning()) {
461434 continueButton.text = qsTr("CONTINUE WITH STEAM");
462435 continueButton.buttonGlyph = hifi.glyphs.steamSquare;
463436 } else {
@@ -470,7 +443,7 @@ Item { · 6 unmodified lines
470443 id: signUpContainer
471444 width: loginContainer.width
472445 height: signUpTextMetrics.height
473− visible: !linkAccountBody.linkSteam
446+ visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
474447 anchors {
475448 left: loginContainer.left
476449 top: loginContainer.bottom
@@ -519,7 +492,7 @@ Item { · 42 unmodified lines
519492 UserActivityLogger.logAction("encourageLoginDialog", data);
520493 }
521494 bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
522− "errorString": "", "linkSteam": linkAccountBody.linkSteam });
495+ "errorString": "" });
523496 }
524497 }
525498 }
@@ -543,7 +516,7 @@ Item { · 17 unmodified lines
543516 fontFamily: linkAccountBody.fontFamily
544517 fontSize: linkAccountBody.fontSize
545518 fontBold: linkAccountBody.fontBold
546− visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam;
519+ visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
547520 onClicked: {
548521 if (linkAccountBody.loginDialogPoppedUp) {
549522 var data = {
interface/resources/qml/LoginDialog/LoggingInBody.qml+114−18
@@ -29,6 +29,8 @@ Item { · 28 unmodified lines
2929 property bool withSteam: withSteam
3030 property bool withOculus: withOculus
3131 property bool linkSteam: linkSteam
32+ property bool linkOculus: linkOculus
33+ property bool createOculus: createOculus
3234
3335 readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
3436
@@ -75,15 +77,25 @@ Item { · 40 unmodified lines
7577 }
7678 }
7779
80+ Timer {
81+ id: oculusSuccessTimer
82+ interval: 500;
83+ running: false;
84+ repeat: false;
85+ onTriggered: {
86+ loginDialog.loginThroughOculus();
87+ init();
88+ }
89+ }
90+
7891 function init() {
7992 // For the process of logging in.
8093 loggingInText.wrapMode = Text.NoWrap;
81−
82− if (loggingInBody.linkSteam) {
94+ if (loggingInBody.createOculus) {
95+ loggingInGlyph.text = hifi.glyphs.oculus;
8396 loggingInGlyph.visible = true;
84− loggingInText.text = "Linking to Steam";
97+ loggingInText.text = "Creating account with Oculus";
8598 loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
86− loginDialog.linkSteam();
8799 } else if (loggingInBody.withSteam) {
88100 loggingInGlyph.visible = true;
89101 loggingInText.text = "Logging in to Steam";
@@ -100,12 +112,18 @@ Item { · 10 unmodified lines
100112 loggingInSpinner.visible = true;
101113 }
102114 function loadingSuccess() {
103− loggingInSpinner.visible = false;
104115 if (loggingInBody.linkSteam) {
105116 loggingInText.text = "Linking to Steam";
117+ loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
106118 loginDialog.linkSteam();
107119 return;
120+ } else if (loggingInBody.linkOculus) {
121+ loggingInText.text = "Linking to Oculus";
122+ loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
123+ loginDialog.linkOculus();
124+ return;
108125 }
126+ loggingInSpinner.visible = false;
109127 if (loggingInBody.withSteam) {
110128 // reset the flag.
111129 loggingInGlyph.visible = false;
@@ -246,6 +264,26 @@ Item { · 134 unmodified lines
246264 verticalAlignment: Text.AlignVCenter;
247265 visible: false;
248266 }
267+ HifiControlsUit.Button {
268+ id: okButton;
269+ width: d.minWidthButton
270+ height: d.minHeightButton
271+ text: qsTr("OK")
272+ color: hifi.buttons.white
273+ anchors {
274+ top: loggedInGlyph.bottom
275+ topMargin: 3 * hifi.dimensions.contentSpacing.y
276+ left: parent.left
277+ leftMargin: (parent.width - width) / 2;
278+ }
279+ onClicked: {
280+ root.tryDestroy();
281+ if (loginDialog.getLoginDialogPoppedUp()) {
282+ loginDialog.dismissLoginDialog();
283+ }
284+ }
285+ visible: false
286+ }
249287 }
250288 }
251289 }
@@ -257,6 +295,34 @@ Item { · 5 unmodified lines
257295
258296 Connections {
259297 target: loginDialog
298+ onHandleCreateCompleted: {
299+ console.log("Create Succeeded")
300+ if (loggingInBody.withOculus) {
301+ if (loggingInBody.loginDialogPoppedUp) {
302+ var data = {
303+ "action": "user created Oculus account successfully"
304+ };
305+ UserActivityLogger.logAction("encourageLoginDialog", data);
306+ }
307+ loggingInBody.createOculus = false;
308+ loggingInText.text = "Account created!";
309+ loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
310+ oculusSuccessTimer.start();
311+ }
312+ }
313+ onHandleCreateFailed: {
314+ console.log("Create Failed: " + error);
315+ if (loggingInBody.withOculus) {
316+ if (loggingInBody.loginDialogPoppedUp) {
317+ var data = {
318+ "action": "user created Oculus account unsuccessfully"
319+ };
320+ UserActivityLogger.logAction("encourageLoginDialog", data);
321+ }
322+ bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
323+ "withOculus": loggingInBody.withOculus, "errorString": error });
324+ }
325+ }
260326 onHandleLinkCompleted: {
261327 console.log("Link Succeeded");
262328 if (loggingInBody.linkSteam) {
@@ -267,21 +333,40 @@ Item { · 4 unmodified lines
267333 };
268334 UserActivityLogger.logAction("encourageLoginDialog", data);
269335 }
270−
271− loggingInBody.loadingSuccess();
336+ } else if (loggingInBody.linkOculus) {
337+ loggingInBody.linkOculus = false;
338+ if (loggingInBody.loginDialogPoppedUp) {
339+ var data = {
340+ "action": "user linked Oculus with their hifi account credentials successfully"
341+ };
342+ UserActivityLogger.logAction("encourageLoginDialog", data);
343+ }
272344 }
345+ loggingInBody.loadingSuccess();
273346 }
274347 onHandleLinkFailed: {
275348 console.log("Link Failed: " + error);
276− if (loggingInBody.linkSteam) {
349+ loggingInSpinner.visible = false;
350+ if (loggingInBody.linkOculus) {
351+ loggingInText.text = "Oculus failed to link";
352+ if (loggingInBody.loginDialogPoppedUp) {
353+ var data = {
354+ "action": "user linked Oculus unsuccessfully"
355+ };
356+ UserActivityLogger.logAction("encourageLoginDialog", data);
357+ }
358+ okButton.visible = true;
359+ } else if (loggingInBody.linkSteam){
277360 if (loggingInBody.loginDialogPoppedUp) {
278361 var data = {
279362 "action": "user linked Steam unsuccessfully"
280363 };
281364 UserActivityLogger.logAction("encourageLoginDialog", data);
282365 }
366+ } else {
367+ bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam,
368+ "linkOculus": loggingInBody.linkOculus, "errorString": error });
283369 }
284− bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error });
285370 }
286371
287372 onHandleLoginCompleted: {
@@ -292,8 +377,19 @@ Item { · 4 unmodified lines
292377 onHandleLoginFailed: {
293378 console.log("Login Failed")
294379 loggingInSpinner.visible = false;
380+ loggingInGlyph.visible = false;
295381 var errorString = "";
296− if (loggingInBody.linkSteam && loggingInBody.withSteam) {
382+ if (loggingInBody.linkOculus && loggingInBody.withOculus) {
383+ errorString = "Username or password is incorrect.";
384+ if (loggingInBody.loginDialogPoppedUp) {
385+ var data = {
386+ "action": "user failed to link Oculus with their hifi account credentials"
387+ };
388+ UserActivityLogger.logAction("encourageLoginDialog", data);
389+ }
390+ bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
391+ "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
392+ } else if (loggingInBody.linkSteam && loggingInBody.withSteam) {
297393 errorString = "Username or password is incorrect.";
298394 if (loggingInBody.loginDialogPoppedUp) {
299395 var data = {
@@ -301,9 +397,9 @@ Item { · 1 unmodified line
301397 };
302398 UserActivityLogger.logAction("encourageLoginDialog", data);
303399 }
304− bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString });
400+ bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
401+ "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
305402 } else if (loggingInBody.withSteam) {
306− loggingInGlyph.visible = false;
307403 errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again.";
308404 if (loggingInBody.loginDialogPoppedUp) {
309405 var data = {
@@ -311,19 +407,19 @@ Item { · 1 unmodified line
311407 };
312408 UserActivityLogger.logAction("encourageLoginDialog", data);
313409 }
314− bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString });
410+ bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
411+ "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
315412 } else if (loggingInBody.withOculus) {
316− loggingInGlyph.visible = false;
317− errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again."
413+ errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one."
318414 if (loggingInBody.loginDialogPoppedUp) {
319415 var data = {
320416 "action": "user failed to authenticate with Oculus to log in"
321417 };
322418 UserActivityLogger.logAction("encourageLoginDialog", data);
323419 }
324− bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString });
325− }
326− else {
420+ bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
421+ "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
422+ } else {
327423 errorString = "Username or password is incorrect.";
328424 if (loggingInBody.loginDialogPoppedUp) {
329425 var data = {
interface/resources/qml/LoginDialog/SignUpBody.qml+54−6
@@ -23,6 +23,7 @@ Item { · 22 unmodified lines
2323 clip: true
2424 height: root.height
2525 width: root.width
26+ readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
2627 property int textFieldHeight: 31
2728 property string fontFamily: "Raleway"
2829 property int fontSize: 15
@@ -37,7 +38,6 @@ Item { · 8 unmodified lines
3738 onKeyboardRaisedChanged: d.resize();
3839
3940 property string errorString: errorString
40− property bool linkSteam: linkSteam
4141 property bool lostFocus: false
4242
4343 readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@@ -73,7 +73,6 @@ Item { · 29 unmodified lines
7373
7474 function init() {
7575 // going to/from sign in/up dialog.
76− loginDialog.isLogIn = false;
7776 emailField.placeholderText = "Email";
7877 emailField.text = "";
7978 emailField.anchors.top = usernameField.bottom;
@@ -353,7 +352,7 @@ Item { · 273 unmodified lines
353352 }
354353 UserActivityLogger.logAction("encourageLoginDialog", data);
355354 }
356− bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam });
355+ bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
357356 }
358357 }
359358 HifiControlsUit.Button {
@@ -380,6 +379,54 @@ Item { · 20 unmodified lines
380379 signUpBody.signup();
381380 }
382381 }
382+ Item {
383+ id: termsContainer
384+ width: parent.width
385+ height: termsTextMetrics.height
386+ anchors {
387+ top: signUpButton.bottom
388+ horizontalCenter: parent.horizontalCenter
389+ topMargin: 2 * hifi.dimensions.contentSpacing.y
390+ left: parent.left
391+ }
392+ TextMetrics {
393+ id: termsTextMetrics
394+ font: termsText.font
395+ text: signUpBody.termsContainerText
396+ Component.onCompleted: {
397+ // with the link.
398+ termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
399+ }
400+ }
401+
402+ HifiStylesUit.InfoItem {
403+ id: termsText
404+ text: signUpBody.termsContainerText
405+ font.family: signUpBody.fontFamily
406+ font.pixelSize: signUpBody.fontSize
407+ font.bold: signUpBody.fontBold
408+ wrapMode: Text.WordWrap
409+ color: hifi.colors.white
410+ linkColor: hifi.colors.blueAccent
411+ lineHeight: 1
412+ lineHeightMode: Text.ProportionalHeight
413+
414+ onLinkActivated: loginDialog.openUrl(link);
415+
416+ Component.onCompleted: {
417+ if (termsTextMetrics.width > root.bannerWidth) {
418+ termsText.width = root.bannerWidth;
419+ termsText.wrapMode = Text.WordWrap;
420+ additionalText.verticalAlignment = Text.AlignLeft;
421+ additionalText.horizontalAlignment = Text.AlignLeft;
422+ termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
423+ termsContainer.anchors.left = buttons.left;
424+ } else {
425+ termsText.anchors.centerIn = termsContainer;
426+ }
427+ }
428+ }
429+ }
383430 }
384431 }
385432
@@ -433,14 +480,15 @@ Item { · 47 unmodified lines
433480
434481 if (errorString !== "") {
435482 loginErrorMessage.visible = true;
483+ var errorLength = errorString.split(/\r\n|\r|\n/).length;
436484 var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n");
437485 loginErrorMessage.text = errorStringEdited;
438− loginErrorMessageTextMetrics.text = errorString;
439− if (loginErrorMessageTextMetrics.width > usernameField.width) {
486+ if (errorLength > 1.0) {
487+ loginErrorMessage.width = root.bannerWidth;
440488 loginErrorMessage.wrapMode = Text.WordWrap;
441489 loginErrorMessage.verticalAlignment = Text.AlignLeft;
442490 loginErrorMessage.horizontalAlignment = Text.AlignLeft;
443− errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height;
491+ errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
444492 }
445493 errorContainer.anchors.bottom = usernameField.top;
446494 errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y;
interface/resources/qml/LoginDialog/UsernameCollisionBody.qml+78−8
@@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0 · 18 unmodified lines
1919 Item {
2020 id: usernameCollisionBody
2121 clip: true
22+ readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
2223 width: root.width
2324 height: root.height
2425 readonly property string fontFamily: "Raleway"
@@ -26,13 +27,18 @@ Item { · 1 unmodified line
2627 readonly property int textFieldFontSize: 18
2728 readonly property bool fontBold: true
2829
29− readonly property bool withSteam: withSteam
30+ property bool withSteam: withSteam
31+ property bool withOculus: withOculus
3032
3133 readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
3234
3335 function create() {
3436 mainTextContainer.visible = false
35− loginDialog.createAccountFromSteam(textField.text);
37+ if (usernameCollisionBody.withOculus) {
38+ loginDialog.createAccountFromOculus(textField.text);
39+ } else if (usernameCollisionBody.withSteam) {
40+ loginDialog.createAccountFromSteam(textField.text);
41+ }
3642 }
3743
3844 property bool keyboardEnabled: false
@@ -90,12 +96,19 @@ Item { · 51 unmodified lines
9096 font.family: usernameCollisionBody.fontFamily
9197 font.pixelSize: usernameCollisionBody.fontSize
9298 font.bold: usernameCollisionBody.fontBold
93− text: qsTr("Your Steam username is not available.");
99+ text: qsTr("");
94100 wrapMode: Text.WordWrap
95101 color: hifi.colors.redAccent
96102 lineHeight: 1
97103 lineHeightMode: Text.ProportionalHeight
98104 horizontalAlignment: Text.AlignHCenter
105+ Component.onCompleted: {
106+ if (usernameCollisionBody.withOculus) {
107+ text = qsTr("Your Oculus username is not available.");
108+ } else if (usernameCollisionBody.withSteam) {
109+ text = qsTr("Your Steam username is not available.");
110+ }
111+ }
99112 }
100113
101114
@@ -164,7 +177,8 @@ Item { · 62 unmodified lines
164177 fontSize: usernameCollisionBody.fontSize
165178 fontBold: usernameCollisionBody.fontBold
166179 onClicked: {
167− bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" });
180+ bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
181+ "withOculus": usernameCollisionBody.withOculus, "errorString": "" });
168182 }
169183 }
170184 HifiControlsUit.Button {
@@ -187,6 +201,55 @@ Item { · 16 unmodified lines
187201 }
188202 }
189203 }
204+ Item {
205+ id: termsContainer
206+ width: parent.width
207+ height: termsTextMetrics.height
208+ anchors {
209+ top: buttons.bottom
210+ horizontalCenter: parent.horizontalCenter
211+ topMargin: 2 * hifi.dimensions.contentSpacing.y
212+ left: parent.left
213+ leftMargin: (parent.width - buttons.width) / 2
214+ }
215+ TextMetrics {
216+ id: termsTextMetrics
217+ font: termsText.font
218+ text: usernameCollisionBody.termsContainerText
219+ Component.onCompleted: {
220+ // with the link.
221+ termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
222+ }
223+ }
224+
225+ HifiStylesUit.InfoItem {
226+ id: termsText
227+ text: usernameCollisionBody.termsContainerText
228+ font.family: usernameCollisionBody.fontFamily
229+ font.pixelSize: usernameCollisionBody.fontSize
230+ font.bold: usernameCollisionBody.fontBold
231+ wrapMode: Text.WordWrap
232+ color: hifi.colors.white
233+ linkColor: hifi.colors.blueAccent
234+ lineHeight: 1
235+ lineHeightMode: Text.ProportionalHeight
236+
237+ onLinkActivated: loginDialog.openUrl(link);
238+
239+ Component.onCompleted: {
240+ if (termsTextMetrics.width > root.bannerWidth) {
241+ termsText.width = root.bannerWidth;
242+ termsText.wrapMode = Text.WordWrap;
243+ additionalText.verticalAlignment = Text.AlignLeft;
244+ additionalText.horizontalAlignment = Text.AlignLeft;
245+ termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
246+ termsContainer.anchors.left = buttons.left;
247+ } else {
248+ termsText.anchors.centerIn = termsContainer;
249+ }
250+ }
251+ }
252+ }
190253 }
191254
192255 Component.onCompleted: {
@@ -201,18 +264,25 @@ Item { · 8 unmodified lines
201264 target: loginDialog
202265 onHandleCreateCompleted: {
203266 console.log("Create Succeeded");
204− if (usernameCollisionBody.withSteam) {
267+ if (usernameCollisionBody.withOculus) {
268+ if (usernameCollisionBody.loginDialogPoppedUp) {
269+ var data = {
270+ "action": "user created a profile with Oculus successfully in the username collision screen"
271+ }
272+ UserActivityLogger.logAction("encourageLoginDialog", data);
273+ }
274+ loginDialog.loginThroughOculus();
275+ } else if (usernameCollisionBody.withSteam) {
205276 if (usernameCollisionBody.loginDialogPoppedUp) {
206277 var data = {
207278 "action": "user created a profile with Steam successfully in the username collision screen"
208279 }
209280 UserActivityLogger.logAction("encourageLoginDialog", data);
210281 }
211−
212282 loginDialog.loginThroughSteam();
213283 }
214−
215− bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false })
284+ bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
285+ "withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false })
216286 }
217287 onHandleCreateFailed: {
218288 console.log("Create Failed: " + error)
interface/resources/qml/OverlayLoginDialog.qml+1−1
@@ -150,6 +150,6 @@ FocusScope { · 149 unmodified lines
150150
151151 Component.onCompleted: {
152152 keyboardTimer.start();
153− bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
153+ bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
154154 }
155155 }
interface/resources/qml/dialogs/TabletLoginDialog.qml+10−6
@@ -14,6 +14,8 @@ import QtQuick 2.5 · 13 unmodified lines
1414 import controlsUit 1.0 as HifiControlsUit
1515 import stylesUit 1.0 as HifiStylesUit
1616
17+import TabletScriptingInterface 1.0
18+
1719 import "../LoginDialog"
1820
1921 FocusScope {
@@ -25,10 +27,9 @@ FocusScope { · 5 unmodified lines
2527 width: parent.width
2628 height: parent.height
2729
28− signal sendToScript(var message);
29− signal canceled();
30+ property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system");
3031
31− property bool isHMD: false
32+ property bool isHMD: HMD.active
3233 property bool gotoPreviousApp: false;
3334
3435 property bool keyboardEnabled: false
@@ -52,6 +53,7 @@ FocusScope { · 17 unmodified lines
5253 }
5354
5455 function tryDestroy() {
56+ tabletProxy.gotoHomeScreen();
5557 }
5658
5759 MouseArea {
@@ -76,7 +78,7 @@ FocusScope { · 18 unmodified lines
7678 interval: 200
7779
7880 onTriggered: {
79− if (MenuInterface.isOptionChecked("Use 3D Keyboard")) {
81+ if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) {
8082 KeyboardScriptingInterface.raised = true;
8183 }
8284 }
@@ -169,11 +171,13 @@ FocusScope { · 86 unmodified lines
169171
170172 Component.onDestruction: {
171173 loginKeyboard.raised = false;
172− KeyboardScriptingInterface.raised = false;
174+ if (root.isHMD) {
175+ KeyboardScriptingInterface.raised = false;
176+ }
173177 }
174178
175179 Component.onCompleted: {
176180 keyboardTimer.start();
177− bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
181+ bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
178182 }
179183 }
interface/src/Application.cpp+6−1
@@ -120,6 +120,7 @@ · 119 unmodified lines
120120 #include <plugins/PluginManager.h>
121121 #include <plugins/PluginUtils.h>
122122 #include <plugins/SteamClientPlugin.h>
123+#include <plugins/OculusPlatformPlugin.h>
123124 #include <plugins/InputConfiguration.h>
124125 #include <RecordingScriptingInterface.h>
125126 #include <render/EngineStats.h>
@@ -800,7 +801,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 674 unmodified lines
800801 if (auto steamClient = pluginManager->getSteamClientPlugin()) {
801802 steamClient->init();
802803 }
803−
804804 PROFILE_SET_THREAD_NAME("Main Thread");
805805
806806 #if defined(Q_OS_WIN)
@@ -2737,6 +2737,7 @@ Application::~Application() { · 1930 unmodified lines
27372737 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
27382738 steamClient->shutdown();
27392739 }
2740+
27402741 DependencyManager::destroy<PluginManager>();
27412742
27422743 DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
@@ -4873,6 +4874,10 @@ void Application::idle() { · 2130 unmodified lines
48734874 steamClient->runCallbacks();
48744875 }
48754876
4877+ if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
4878+ oculusPlugin->handleOVREvents();
4879+ }
4880+
48764881 float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
48774882 _lastTimeUpdated.start();
48784883
interface/src/ui/LoginDialog.cpp+126−5
@@ -18,6 +18,7 @@ · 17 unmodified lines
1818
1919 #include <plugins/PluginManager.h>
2020 #include <plugins/SteamClientPlugin.h>
21+#include <plugins/OculusPlatformPlugin.h>
2122 #include <shared/GlobalAppProperties.h>
2223 #include <ui/TabletScriptingInterface.h>
2324 #include <UserActivityLogger.h>
@@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const { · 85 unmodified lines
109110 return steamClient && steamClient->isRunning();
110111 }
111112
112−bool LoginDialog::isOculusStoreRunning() const {
113− return qApp->property(hifi::properties::OCULUS_STORE).toBool();
113+bool LoginDialog::isOculusRunning() const {
114+ auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin();
115+ return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning());
116+}
117+
118+QString LoginDialog::oculusUserID() const {
119+ if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
120+ return oculusPlatformPlugin->getOculusUserID();
121+ }
122+ return "";
114123 }
115124
116125 void LoginDialog::dismissLoginDialog() {
@@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const · 9 unmodified lines
126135 DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
127136 }
128137
138+void LoginDialog::loginThroughOculus() {
139+ qDebug() << "Attempting to login through Oculus";
140+ if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
141+ oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
142+ DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
143+ });
144+ }
145+}
146+
147+void LoginDialog::linkOculus() {
148+ qDebug() << "Attempting to link Oculus account";
149+ if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
150+ oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
151+ if (nonce.isEmpty() || oculusID.isEmpty()) {
152+ emit handleLoginFailed();
153+ return;
154+ }
155+
156+ JSONCallbackParameters callbackParams;
157+ callbackParams.callbackReceiver = this;
158+ callbackParams.jsonCallbackMethod = "linkCompleted";
159+ callbackParams.errorCallbackMethod = "linkFailed";
160+ const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link";
161+
162+ QJsonObject payload;
163+ payload["oculus_nonce"] = nonce;
164+ payload["oculus_id"] = oculusID;
165+
166+ auto accountManager = DependencyManager::get<AccountManager>();
167+ accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required,
168+ QNetworkAccessManager::PostOperation, callbackParams,
169+ QJsonDocument(payload).toJson());
170+ });
171+ }
172+}
173+
174+void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) {
175+ qDebug() << "Attempting to create account from Oculus info";
176+ if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
177+ oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) {
178+ if (nonce.isEmpty() || oculusID.isEmpty()) {
179+ emit handleLoginFailed();
180+ return;
181+ }
182+
183+ JSONCallbackParameters callbackParams;
184+ callbackParams.callbackReceiver = this;
185+ callbackParams.jsonCallbackMethod = "createCompleted";
186+ callbackParams.errorCallbackMethod = "createFailed";
187+
188+ const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create";
189+
190+ QJsonObject payload;
191+ payload["oculus_nonce"] = nonce;
192+ payload["oculus_id"] = oculusID;
193+ if (!email.isEmpty()) {
194+ payload["email"] = email;
195+ }
196+ if (!username.isEmpty()) {
197+ payload["username"] = username;
198+ }
199+ if (!password.isEmpty()) {
200+ payload["password"] = password;
201+ }
202+
203+ auto accountManager = DependencyManager::get<AccountManager>();
204+ accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None,
205+ QNetworkAccessManager::PostOperation, callbackParams,
206+ QJsonDocument(payload).toJson());
207+ });
208+ }
209+}
210+
129211 void LoginDialog::loginThroughSteam() {
130212 qDebug() << "Attempting to login through Steam";
131213 if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@@ -157,7 +239,7 @@ void LoginDialog::linkSteam() { · 25 unmodified lines
157239 const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
158240
159241 QJsonObject payload;
160− payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
242+ payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
161243
162244 auto accountManager = DependencyManager::get<AccountManager>();
163245 accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required,
@@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) { · 20 unmodified lines
184266 const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
185267
186268 QJsonObject payload;
187− payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
269+ payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
188270 if (!username.isEmpty()) {
189− payload.insert("username", QJsonValue::fromVariant(QVariant(username)));
271+ payload["username"] = username;
190272 }
191273
192274 auto accountManager = DependencyManager::get<AccountManager>();
@@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) { · 21 unmodified lines
214296 }
215297
216298 void LoginDialog::createFailed(QNetworkReply* reply) {
299+ if (isOculusRunning()) {
300+ auto replyData = reply->readAll();
301+ QJsonParseError parseError;
302+ auto doc = QJsonDocument::fromJson(replyData, &parseError);
303+ if (parseError.error != QJsonParseError::NoError) {
304+ emit handleCreateFailed(reply->errorString());
305+ return;
306+ }
307+ auto data = doc["data"];
308+ auto error = data["error"];
309+ auto oculusError = data["oculus"];
310+ auto user = error["username"].toArray();
311+ auto uid = error["uid"].toArray();
312+ auto email = error["email"].toArray();
313+ auto password = error["password"].toArray();
314+ QString reply;
315+ if (uid[0].isString()) {
316+ emit handleCreateFailed("Oculus ID " + uid[0].toString() + ".");
317+ return;
318+ }
319+ if (user[0].isString()) {
320+ reply = "Username " + user[0].toString() + ".";
321+ }
322+ if (email[0].isString()) {
323+ reply.append((!reply.isEmpty()) ? "\n" : "");
324+ reply.append("Email " + email[0].toString() + ".");
325+ }
326+ if (password[0].isString()) {
327+ reply.append((!reply.isEmpty()) ? "\n" : "");
328+ reply.append("Password " + password[0].toString() + ".");
329+ }
330+ if (!oculusError.isNull() && !oculusError.isUndefined()) {
331+ emit handleCreateFailed("Could not verify token with Oculus. Please try again.");
332+ return;
333+ } else {
334+ emit handleCreateFailed(reply);
335+ return;
336+ }
337+ }
217338 emit handleCreateFailed(reply->errorString());
218339 }
219340
interface/src/ui/LoginDialog.h+6−8
@@ -22,7 +22,6 @@ extern const QUrl OVERLAY_LOGIN_DIALOG; · 21 unmodified lines
2222
2323 class LoginDialog : public OffscreenQmlDialog {
2424 Q_OBJECT
25− Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn)
2625 HIFI_QML_DECL
2726
2827 public:
@@ -67,24 +66,23 @@ protected slots: · 38 unmodified lines
6766 Q_INVOKABLE void dismissLoginDialog();
6867
6968 Q_INVOKABLE bool isSteamRunning() const;
70− Q_INVOKABLE bool isOculusStoreRunning() const;
69+ Q_INVOKABLE bool isOculusRunning() const;
70+
71+ Q_INVOKABLE QString oculusUserID() const;
7172
7273 Q_INVOKABLE void login(const QString& username, const QString& password) const;
7374 Q_INVOKABLE void loginThroughSteam();
7475 Q_INVOKABLE void linkSteam();
7576 Q_INVOKABLE void createAccountFromSteam(QString username = QString());
77+ Q_INVOKABLE void loginThroughOculus();
78+ Q_INVOKABLE void linkOculus();
79+ Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString());
7680
7781 Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
7882
7983 Q_INVOKABLE void openUrl(const QString& url) const;
8084
8185 Q_INVOKABLE bool getLoginDialogPoppedUp() const;
82−
83−private:
84− bool getIsLogIn() const { return _isLogIn; }
85− void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; }
86−
87− bool _isLogIn{ false };
8886 };
8987
9088 #endif // hifi_LoginDialog_h
libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h+2−0
@@ -31,6 +31,8 @@ public: · 30 unmodified lines
3131
3232 virtual void compositeExtra() override;
3333
34+ virtual void pluginUpdate() override {};
35+
3436 protected:
3537 mutable bool _isThrottled = false;
3638
libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h+1−0
@@ -20,6 +20,7 @@ public: · 19 unmodified lines
2020 QImage getScreenshot(float aspectRatio = 0.0f) const override;
2121 QImage getSecondaryCameraScreenshot() const override;
2222 void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
23+ void pluginUpdate() override {};
2324 private:
2425 static const QString NAME;
2526 };
libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h+2−0
@@ -46,6 +46,8 @@ public: · 45 unmodified lines
4646
4747 virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
4848
49+ void pluginUpdate() override {};
50+
4951 signals:
5052 void hmdMountedChanged();
5153 void hmdVisibleChanged(bool visible);
libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h+2−0
@@ -28,6 +28,8 @@ public: · 27 unmodified lines
2828 // to the HMD plugins.
2929 //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
3030
31+ virtual void pluginUpdate() override {};
32+
3133 protected:
3234 virtual bool internalActivate() override;
3335 virtual void internalDeactivate() override;
libraries/networking/src/AccountManager.cpp+23−0
@@ -588,6 +588,29 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { · 587 unmodified lines
588588 connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
589589 }
590590
591+void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) {
592+ QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
593+
594+ QNetworkRequest request;
595+ request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
596+
597+ QUrl grantURL = _authURL;
598+ grantURL.setPath("/oauth/token");
599+
600+ QByteArray postData;
601+ postData.append("grant_type=password&");
602+ postData.append("oculus_nonce=" + nonce + "&");
603+ postData.append("oculus_id=" + oculusID + "&");
604+ postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
605+
606+ request.setUrl(grantURL);
607+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
608+
609+ QNetworkReply* requestReply = networkAccessManager.post(request, postData);
610+ connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
611+ connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
612+}
613+
591614 void AccountManager::refreshAccessToken() {
592615
593616 // we can't refresh our access token if we don't have a refresh token, so check for that first
libraries/networking/src/AccountManager.h+1−0
@@ -106,6 +106,7 @@ public: · 105 unmodified lines
106106 public slots:
107107 void requestAccessToken(const QString& login, const QString& password);
108108 void requestAccessTokenWithSteam(QByteArray authSessionTicket);
109+ void requestAccessTokenWithOculus(const QString& nonce, const QString& oculusID);
109110 void requestAccessTokenWithAuthCode(const QString& authCode,
110111 const QString& clientId,
111112 const QString& clientSecret,
libraries/plugins/src/plugins/DisplayPlugin.h+3−0
@@ -217,6 +217,9 @@ public: · 216 unmodified lines
217217
218218 static const QString& MENU_PATH();
219219
220+ // for updating plugin-related commands. Mimics the input plugin.
221+ virtual void pluginUpdate() = 0;
222+
220223 signals:
221224 void recommendedFramebufferSizeChanged(const QSize& size);
222225 void resetSensorsRequested();
libraries/plugins/src/plugins/Forward.h+2−0
@@ -21,6 +21,7 @@ class DisplayPlugin; · 20 unmodified lines
2121 class InputPlugin;
2222 class CodecPlugin;
2323 class SteamClientPlugin;
24+class OculusPlatformPlugin;
2425 class Plugin;
2526 class PluginContainer;
2627 class PluginManager;
@@ -35,4 +36,5 @@ using CodecPluginPointer = std::shared_ptr<CodecPlugin>; · 8 unmodified lines
3536 using CodecPluginList = std::vector<CodecPluginPointer>;
3637 using CodecPluginProvider = std::function<CodecPluginList()>;
3738 using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>;
39+using OculusPlatformPluginPointer = std::shared_ptr<OculusPlatformPlugin>;
3840 using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>;
libraries/plugins/src/plugins/OculusPlatformPlugin.h+28−0
@@ -0,0 +1,28 @@
1+//
2+// Created by Wayne Chen on 2018/12/20
3+// Copyright 2018 High Fidelity Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2-0.html
7+//
8+#pragma once
9+
10+#include <QtCore/QString>
11+
12+#include <functional>
13+
14+using NonceUserIDCallback = std::function<void(QString, QString)>;
15+
16+class OculusPlatformPlugin {
17+public:
18+ virtual ~OculusPlatformPlugin() = default;
19+
20+ virtual QString getName() const = 0;
21+ virtual QString getOculusUserID() const = 0;
22+
23+ virtual bool isRunning() const = 0;
24+
25+ virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0;
26+
27+ virtual void handleOVREvents() = 0;
28+};
libraries/plugins/src/plugins/PluginManager.cpp+16−0
@@ -188,6 +188,22 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() { · 187 unmodified lines
188188 return steamClientPlugin;
189189 }
190190
191+const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() {
192+ static OculusPlatformPluginPointer oculusPlatformPlugin;
193+ static std::once_flag once;
194+ std::call_once(once, [&] {
195+ // Now grab the dynamic plugins
196+ for (auto loader : getLoadedPlugins()) {
197+ OculusPlatformProvider* oculusPlatformProvider = qobject_cast<OculusPlatformProvider*>(loader->instance());
198+ if (oculusPlatformProvider) {
199+ oculusPlatformPlugin = oculusPlatformProvider->getOculusPlatformPlugin();
200+ break;
201+ }
202+ }
203+ });
204+ return oculusPlatformPlugin;
205+}
206+
191207 const DisplayPluginList& PluginManager::getDisplayPlugins() {
192208 static std::once_flag once;
193209 static auto deviceAddedCallback = [](QString deviceName) {
libraries/plugins/src/plugins/PluginManager.h+1−0
@@ -27,6 +27,7 @@ public: · 26 unmodified lines
2727 const InputPluginList& getInputPlugins();
2828 const CodecPluginList& getCodecPlugins();
2929 const SteamClientPluginPointer getSteamClientPlugin();
30+ const OculusPlatformPluginPointer getOculusPlatformPlugin();
3031
3132 DisplayPluginList getPreferredDisplayPlugins();
3233 void setPreferredDisplayPlugins(const QStringList& displays);
libraries/plugins/src/plugins/RuntimePlugin.h+8−0
@@ -51,5 +51,13 @@ public: · 50 unmodified lines
5151 virtual SteamClientPluginPointer getSteamClientPlugin() = 0;
5252 };
5353
54+class OculusPlatformProvider {
55+public:
56+ virtual OculusPlatformPluginPointer getOculusPlatformPlugin() = 0;
57+};
58+
5459 #define SteamClientProvider_iid "com.highfidelity.plugins.steamclient"
5560 Q_DECLARE_INTERFACE(SteamClientProvider, SteamClientProvider_iid)
61+
62+#define OculusPlatformProvider_iid "com.highfidelity.plugins.oculusplatform"
63+Q_DECLARE_INTERFACE(OculusPlatformProvider, OculusPlatformProvider_iid)
plugins/oculus/src/OculusBaseDisplayPlugin.cpp+2−1
@@ -12,6 +12,7 @@ · 11 unmodified lines
1212 #include <display-plugins/CompositorHelper.h>
1313 #include <gpu/Frame.h>
1414 #include <gl/Config.h>
15+#include <shared/GlobalAppProperties.h>
1516
1617 #include "OculusHelpers.h"
1718
@@ -30,7 +31,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { · 12 unmodified lines
3031 return false;
3132 }
3233
33− if (ovr::quitRequested(status) || ovr::displayLost(status) || !ovr::handleOVREvents()) {
34+ if (ovr::quitRequested(status) || ovr::displayLost(status)) {
3435 QMetaObject::invokeMethod(qApp, "quit");
3536 return false;
3637 }
plugins/oculus/src/OculusBaseDisplayPlugin.h+4−1
@@ -13,6 +13,9 @@ · 12 unmodified lines
1313
1414 #include <OVR_CAPI_GL.h>
1515
16+#define OVRPL_DISABLED
17+#include <OVR_Platform.h>
18+
1619 class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
1720 using Parent = HmdDisplayPlugin;
1821 public:
@@ -30,7 +33,7 @@ public: · 11 unmodified lines
3033
3134 QRectF getPlayAreaRect() override;
3235 QVector<glm::vec3> getSensorPositions() override;
33−
36+
3437 protected:
3538 void customizeContext() override;
3639 void uncustomizeContext() override;
plugins/oculus/src/OculusHelpers.cpp+0−31
@@ -296,34 +296,3 @@ controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, · 295 unmodified lines
296296 pose.valid = true;
297297 return pose;
298298 }
299−
300−bool hifi::ovr::handleOVREvents() {
301−#ifdef OCULUS_APP_ID
302− if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
303− // pop messages to see if we got a return for an entitlement check
304− ovrMessageHandle message = ovr_PopMessage();
305−
306− while (message) {
307− switch (ovr_Message_GetType(message)) {
308− case ovrMessage_Entitlement_GetIsViewerEntitled: {
309− if (!ovr_Message_IsError(message)) {
310− // this viewer is entitled, no need to flag anything
311− qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
312− } else {
313− // we failed the entitlement check, quit
314− qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
315− return false;
316− }
317− }
318− }
319−
320− // free the message handle to cleanup and not leak
321− ovr_FreeMessage(message);
322−
323− // pop the next message to check, if there is one
324− message = ovr_PopMessage();
325− }
326− }
327−#endif
328− return true;
329−}
plugins/oculus/src/OculusHelpers.h+0−1
@@ -30,7 +30,6 @@ struct ovr { · 29 unmodified lines
3030 static ovrSessionStatus getStatus(ovrResult& result);
3131 static ovrTrackingState getTrackingState(double absTime = 0.0, ovrBool latencyMarker = ovrFalse);
3232 static QString getError();
33− static bool handleOVREvents();
3433
3534 static inline bool quitRequested() { return quitRequested(getStatus()); }
3635 static inline bool reorientRequested() { return reorientRequested(getStatus()); }
plugins/oculus/src/OculusPlatformPlugin.cpp+109−0
@@ -0,0 +1,109 @@
1+//
2+// Created by Wayne Chen on 2019/01/08
3+// Copyright 2019 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include "OculusPlatformPlugin.h"
10+
11+#include <shared/GlobalAppProperties.h>
12+
13+#include <QMetaObject>
14+
15+#include "OculusHelpers.h"
16+
17+QString OculusAPIPlugin::NAME { "Oculus Rift" };
18+
19+OculusAPIPlugin::OculusAPIPlugin() {
20+ _session = hifi::ovr::acquireRenderSession();
21+}
22+
23+OculusAPIPlugin::~OculusAPIPlugin() {
24+ hifi::ovr::releaseRenderSession(_session);
25+}
26+
27+bool OculusAPIPlugin::isRunning() const {
28+ return (qApp->property(hifi::properties::OCULUS_STORE).toBool());
29+}
30+
31+void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) {
32+#ifdef OCULUS_APP_ID
33+ _nonceUserIDCallback = callback;
34+ ovr_User_GetUserProof();
35+ ovr_User_GetLoggedInUser();
36+#endif
37+}
38+
39+void OculusAPIPlugin::handleOVREvents() {
40+#ifdef OCULUS_APP_ID
41+ if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
42+ // pop messages to see if we got a return for an entitlement check
43+ ovrMessageHandle message { nullptr };
44+
45+ // pop the next message to check, if there is one
46+ while ((message = ovr_PopMessage())) {
47+ switch (ovr_Message_GetType(message)) {
48+ case ovrMessage_Entitlement_GetIsViewerEntitled: {
49+ if (!ovr_Message_IsError(message)) {
50+ // this viewer is entitled, no need to flag anything
51+ qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
52+ } else {
53+ // we failed the entitlement check, quit
54+ qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
55+ QMetaObject::invokeMethod(qApp, "quit");
56+ }
57+ break;
58+ }
59+ case ovrMessage_User_Get: {
60+ if (!ovr_Message_IsError(message)) {
61+ qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded";
62+ ovrUserHandle user = ovr_Message_GetUser(message);
63+ _user = ovr_User_GetOculusID(user);
64+ // went all the way through the `requestNonceAndUserID()` pipeline successfully.
65+ } else {
66+ qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
67+ // emit the signal so we don't hang for it anywhere else.
68+ _user = "";
69+ }
70+ break;
71+ }
72+ case ovrMessage_User_GetLoggedInUser: {
73+ if (!ovr_Message_IsError(message)) {
74+ ovrUserHandle user = ovr_Message_GetUser(message);
75+ _userID = ovr_User_GetID(user);
76+ ovr_User_Get(_userID);
77+ } else {
78+ qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
79+ // emit the signal so we don't hang for it anywhere else.
80+ }
81+ _userIDChanged = true;
82+ break;
83+ }
84+ case ovrMessage_User_GetUserProof: {
85+ if (!ovr_Message_IsError(message)) {
86+ ovrUserProofHandle userProof = ovr_Message_GetUserProof(message);
87+ _nonce = ovr_UserProof_GetNonce(userProof);
88+ qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce;
89+ } else {
90+ qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
91+ _nonce = "";
92+ // emit the signal so we don't hang for it anywhere else.
93+ }
94+ _nonceChanged = true;
95+ break;
96+ }
97+ }
98+
99+ if (_nonceChanged && _userIDChanged) {
100+ _nonceUserIDCallback(_nonce, QString::number(_userID));
101+ _nonceChanged = _userIDChanged = false;
102+ }
103+
104+ // free the message handle to cleanup and not leak
105+ ovr_FreeMessage(message);
106+ }
107+ }
108+#endif
109+}
plugins/oculus/src/OculusPlatformPlugin.h+39−0
@@ -0,0 +1,39 @@
1+//
2+// Created by Wayne Chen on 2019/01/08
3+// Copyright 2019 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+#pragma once
9+
10+#include <plugins/OculusPlatformPlugin.h>
11+
12+#include <OVR_CAPI_GL.h>
13+
14+#define OVRPL_DISABLED
15+#include <OVR_Platform.h>
16+
17+class OculusAPIPlugin : public OculusPlatformPlugin {
18+public:
19+ OculusAPIPlugin();
20+ virtual ~OculusAPIPlugin();
21+ QString getName() const { return NAME; }
22+ QString getOculusUserID() const { return _user; };
23+
24+ bool isRunning() const;
25+
26+ virtual void requestNonceAndUserID(NonceUserIDCallback callback);
27+
28+ virtual void handleOVREvents();
29+
30+private:
31+ static QString NAME;
32+ NonceUserIDCallback _nonceUserIDCallback;
33+ QString _nonce;
34+ bool _nonceChanged{ false };
35+ bool _userIDChanged{ false };
36+ QString _user;
37+ ovrID _userID;
38+ ovrSession _session;
39+};
plugins/oculus/src/OculusProvider.cpp+14−1
@@ -18,15 +18,18 @@ · 17 unmodified lines
1818
1919 #include "OculusDisplayPlugin.h"
2020 #include "OculusDebugDisplayPlugin.h"
21+#include "OculusPlatformPlugin.h"
2122 #include "OculusControllerManager.h"
2223
23−class OculusProvider : public QObject, public DisplayProvider, InputProvider
24+class OculusProvider : public QObject, public DisplayProvider, InputProvider, OculusPlatformProvider
2425 {
2526 Q_OBJECT
2627 Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "oculus.json")
2728 Q_INTERFACES(DisplayProvider)
2829 Q_PLUGIN_METADATA(IID InputProvider_iid FILE "oculus.json")
2930 Q_INTERFACES(InputProvider)
31+ Q_PLUGIN_METADATA(IID OculusPlatformProvider_iid FILE "oculus.json")
32+ Q_INTERFACES(OculusPlatformProvider)
3033
3134 public:
3235 OculusProvider(QObject* parent = nullptr) : QObject(parent) {}
@@ -62,6 +65,15 @@ public: · 29 unmodified lines
6265 return _inputPlugins;
6366 }
6467
68+ virtual OculusPlatformPluginPointer getOculusPlatformPlugin() override {
69+ static std::once_flag once;
70+ std::call_once(once, [&] {
71+ _oculusPlatformPlugin = std::make_shared<OculusAPIPlugin>();
72+
73+ });
74+ return _oculusPlatformPlugin;
75+ }
76+
6577 virtual void destroyInputPlugins() override {
6678 _inputPlugins.clear();
6779 }
@@ -73,6 +85,7 @@ public: · 5 unmodified lines
7385 private:
7486 DisplayPluginList _displayPlugins;
7587 InputPluginList _inputPlugins;
88+ OculusPlatformPluginPointer _oculusPlatformPlugin;
7689 };
7790
7891 #include "OculusProvider.moc"