diff --git a/interface/resources/images/steam-sign-in.png b/interface/resources/images/steam-sign-in.png
new file mode 100644
index 0000000000..148e6ab280
Binary files /dev/null and b/interface/resources/images/steam-sign-in.png differ
diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml
index f75e83e36e..1f84024e15 100644
--- a/interface/resources/qml/LoginDialog.qml
+++ b/interface/resources/qml/LoginDialog.qml
@@ -10,296 +10,70 @@
import Hifi 1.0
import QtQuick 2.4
-import "controls"
-import "styles"
+
+import "controls-uit"
+import "styles-uit"
import "windows"
-ScrollingWindow {
+import "LoginDialog"
+
+ModalWindow {
id: root
HifiConstants { id: hifi }
objectName: "LoginDialog"
- height: loginDialog.implicitHeight
- width: loginDialog.implicitWidth
- // FIXME make movable
- anchors.centerIn: parent
- destroyOnHidden: false
- hideBackground: true
- shown: false
+ implicitWidth: 520
+ implicitHeight: 320
+ destroyOnCloseButton: true
+ destroyOnHidden: true
+ visible: true
+
+ property string iconText: ""
+ property int iconSize: 50
+
+ property string title: ""
+ property int titleWidth: 0
LoginDialog {
id: loginDialog
- implicitWidth: backgroundRectangle.width
- implicitHeight: backgroundRectangle.height
- readonly property int inputWidth: 500
- readonly property int inputHeight: 60
- readonly property int borderWidth: 30
- readonly property int closeMargin: 16
- readonly property real tan30: 0.577 // tan(30°)
- readonly property int inputSpacing: 16
- Rectangle {
- id: backgroundRectangle
- width: loginDialog.inputWidth + loginDialog.borderWidth * 2
- height: loginDialog.inputHeight * 6 + loginDialog.closeMargin * 2
- radius: loginDialog.closeMargin * 2
- color: "#2c86b1"
- opacity: 0.85
- }
-
- Column {
- id: mainContent
- width: loginDialog.inputWidth
- spacing: loginDialog.inputSpacing
- anchors {
- horizontalCenter: parent.horizontalCenter
- verticalCenter: parent.verticalCenter
- }
-
- Item {
- // Offset content down a little
- width: loginDialog.inputWidth
- height: loginDialog.closeMargin
- }
-
- Rectangle {
- width: loginDialog.inputWidth
- height: loginDialog.inputHeight
- radius: height / 2
- color: "#ebebeb"
-
- Image {
- source: "../images/login-username.svg"
- width: loginDialog.inputHeight * 0.65
- height: width
- sourceSize: Qt.size(width, height);
- anchors {
- verticalCenter: parent.verticalCenter
- left: parent.left
- leftMargin: loginDialog.inputHeight / 4
- }
- }
-
- TextInput {
- id: username
- anchors {
- fill: parent
- leftMargin: loginDialog.inputHeight
- rightMargin: loginDialog.inputHeight / 2
- }
-
- helperText: "username or email"
- color: hifi.colors.text
-
- KeyNavigation.tab: password
- KeyNavigation.backtab: password
- }
- }
-
- Rectangle {
- width: loginDialog.inputWidth
- height: loginDialog.inputHeight
- radius: height / 2
- color: "#ebebeb"
-
- Image {
- source: "../images/login-password.svg"
- width: loginDialog.inputHeight * 0.65
- height: width
- sourceSize: Qt.size(width, height);
- anchors {
- verticalCenter: parent.verticalCenter
- left: parent.left
- leftMargin: loginDialog.inputHeight / 4
- }
- }
-
- TextInput {
- id: password
- anchors {
- fill: parent
- leftMargin: loginDialog.inputHeight
- rightMargin: loginDialog.inputHeight / 2
- }
-
- helperText: "password"
- echoMode: TextInput.Password
- color: hifi.colors.text
-
- KeyNavigation.tab: username
- KeyNavigation.backtab: username
- onFocusChanged: {
- if (password.focus) {
- password.selectAll()
- }
- }
- }
- }
-
- Item {
- width: loginDialog.inputWidth
- height: loginDialog.inputHeight / 2
-
- Text {
- id: messageText
-
- visible: loginDialog.statusText != "" && loginDialog.statusText != "Logging in..."
-
- width: loginDialog.inputWidth
- height: loginDialog.inputHeight / 2
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
-
- text: loginDialog.statusText
- color: "white"
- }
-
- Row {
- id: messageSpinner
-
- visible: loginDialog.statusText == "Logging in..."
- onVisibleChanged: visible ? messageSpinnerAnimation.restart() : messageSpinnerAnimation.stop()
-
- spacing: 24
- anchors {
- verticalCenter: parent.verticalCenter
- horizontalCenter: parent.horizontalCenter
- }
-
- Rectangle {
- id: spinner1
- width: 10
- height: 10
- color: "#ebebeb"
- opacity: 0.05
- }
-
- Rectangle {
- id: spinner2
- width: 10
- height: 10
- color: "#ebebeb"
- opacity: 0.05
- }
-
- Rectangle {
- id: spinner3
- width: 10
- height: 10
- color: "#ebebeb"
- opacity: 0.05
- }
-
- SequentialAnimation {
- id: messageSpinnerAnimation
- running: messageSpinner.visible
- loops: Animation.Infinite
- NumberAnimation { target: spinner1; property: "opacity"; to: 1.0; duration: 1000 }
- NumberAnimation { target: spinner2; property: "opacity"; to: 1.0; duration: 1000 }
- NumberAnimation { target: spinner3; property: "opacity"; to: 1.0; duration: 1000 }
- NumberAnimation { target: spinner1; property: "opacity"; to: 0.05; duration: 0 }
- NumberAnimation { target: spinner2; property: "opacity"; to: 0.05; duration: 0 }
- NumberAnimation { target: spinner3; property: "opacity"; to: 0.05; duration: 0 }
- }
- }
- }
-
- Rectangle {
- width: loginDialog.inputWidth
- height: loginDialog.inputHeight
- radius: height / 2
- color: "#353535"
-
- TextInput {
- anchors.fill: parent
- text: "Login"
- color: "white"
- horizontalAlignment: Text.AlignHCenter
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- loginDialog.login(username.text, password.text)
- }
- }
- }
-
- Item {
- anchors { left: parent.left; right: parent.right; }
- height: loginDialog.inputHeight
-
- Image {
- id: hifiIcon
- source: "../images/hifi-logo-blackish.svg"
- width: loginDialog.inputHeight
- height: width
- sourceSize: Qt.size(width, height);
- anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
- }
-
- Text {
- anchors { verticalCenter: parent.verticalCenter; right: hifiIcon.left; margins: loginDialog.inputSpacing }
- text: "Password?"
- scale: 0.8
- font.underline: true
- color: "#e0e0e0"
- MouseArea {
- anchors { fill: parent; margins: -loginDialog.inputSpacing / 2 }
- cursorShape: Qt.PointingHandCursor
- onClicked: loginDialog.openUrl(loginDialog.rootUrl + "/users/password/new")
- }
- }
-
- Text {
- anchors { verticalCenter: parent.verticalCenter; left: hifiIcon.right; margins: loginDialog.inputSpacing }
- text: "Register"
- scale: 0.8
- font.underline: true
- color: "#e0e0e0"
-
- MouseArea {
- anchors { fill: parent; margins: -loginDialog.inputSpacing / 2 }
- cursorShape: Qt.PointingHandCursor
- onClicked: loginDialog.openUrl(loginDialog.rootUrl + "/signup")
- }
- }
-
- }
- }
- }
-
- onShownChanged: {
- if (!shown) {
- username.text = ""
- password.text = ""
- loginDialog.statusText = ""
- } else {
- username.forceActiveFocus()
+ Loader {
+ id: bodyLoader
+ anchors.fill: parent
+ source: loginDialog.isSteamRunning() ? "LoginDialog/SignInBody.qml" : "LoginDialog/LinkAccountBody.qml"
}
}
Keys.onPressed: {
- switch (event.key) {
+ if (!visible) {
+ return
+ }
+
+ if (event.modifiers === Qt.ControlModifier)
+ switch (event.key) {
+ case Qt.Key_A:
+ event.accepted = true
+ detailedText.selectAll()
+ break
+ case Qt.Key_C:
+ event.accepted = true
+ detailedText.copy()
+ break
+ case Qt.Key_Period:
+ if (Qt.platform.os === "osx") {
+ event.accepted = true
+ content.reject()
+ }
+ break
+ } else switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
- root.shown = false;
- event.accepted = true;
- break;
+ event.accepted = true
+ destroy()
+ break
case Qt.Key_Enter:
case Qt.Key_Return:
- if (username.activeFocus) {
- event.accepted = true
- password.forceActiveFocus()
- } else if (password.activeFocus) {
- event.accepted = true
- if (username.text == "") {
- username.forceActiveFocus()
- } else {
- loginDialog.login(username.text, password.text)
- }
- }
+ event.accepted = true
break
}
}
diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
new file mode 100644
index 0000000000..fc7eac3d00
--- /dev/null
+++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
@@ -0,0 +1,125 @@
+//
+// CompleteProfileBody.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2015 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
+//
+
+import Hifi 1.0
+import QtQuick 2.4
+import QtQuick.Controls.Styles 1.4 as OriginalStyles
+
+import "../controls-uit"
+import "../styles-uit"
+
+Item {
+ id: completeProfileBody
+ clip: true
+ width: pane.width
+ height: pane.height
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 480
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, additionalTextContainer.contentWidth)
+ var targetHeight = 4 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height
+
+ root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
+ root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ }
+ }
+
+ Row {
+ id: buttons
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: 3 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+ width: 200
+
+ text: qsTr("Create your profile")
+ color: hifi.buttons.blue
+
+ onClicked: loginDialog.createAccountFromStream()
+ }
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: qsTr("Cancel")
+
+
+ onClicked: root.destroy()
+ }
+ }
+
+ ShortcutText {
+ id: additionalTextContainer
+ anchors {
+ top: buttons.bottom
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ text: "Already have a High Fidelity profile? Link to an existing profile here."
+
+ wrapMode: Text.WordWrap
+ lineHeight: 2
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+
+ onLinkActivated: {
+ bodyLoader.source = "LinkAccountBody.qml"
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ }
+
+ Component.onCompleted: {
+ root.title = qsTr("Complete Your Profile")
+ root.iconText = "<"
+ d.resize();
+ }
+
+ Connections {
+ target: loginDialog
+ onHandleCreateCompleted: {
+ console.log("Create Succeeded")
+
+ loginDialog.loginThroughSteam()
+ }
+ onHandleCreateFailed: {
+ console.log("Create Failed: " + error)
+
+ bodyLoader.source = "UsernameCollisionBody.qml"
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ onHandleLoginCompleted: {
+ console.log("Login Succeeded")
+
+ bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : false })
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ onHandleLoginFailed: {
+ console.log("Login Failed")
+ }
+ }
+}
diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
new file mode 100644
index 0000000000..137556964f
--- /dev/null
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -0,0 +1,215 @@
+//
+// LinkAccountBody.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2015 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
+//
+
+import Hifi 1.0
+import QtQuick 2.4
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4 as OriginalStyles
+
+import "../controls-uit"
+import "../styles-uit"
+
+Item {
+ id: linkAccountBody
+ clip: true
+ width: root.pane.width
+ height: root.pane.height
+
+ function login() {
+ mainTextContainer.visible = false
+ loginDialog.login(usernameField.text, passwordField.text)
+ }
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 480
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, form.contentWidth)
+ var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height +
+ 4 * hifi.dimensions.contentSpacing.y + form.height +
+ 4 * hifi.dimensions.contentSpacing.y + buttons.height
+
+ root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
+ root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ }
+ }
+
+ ShortcutText {
+ id: mainTextContainer
+ anchors {
+ top: parent.top
+ left: parent.left
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ visible: false
+
+ text: qsTr("Username or password incorrect.")
+ wrapMode: Text.WordWrap
+ color: hifi.colors.redAccent
+ lineHeight: 1
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Column {
+ id: form
+ anchors {
+ top: mainTextContainer.bottom
+ left: parent.left
+ margins: 0
+ topMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: 2 * hifi.dimensions.contentSpacing.y
+
+ Row {
+ spacing: hifi.dimensions.contentSpacing.x
+
+ TextField {
+ id: usernameField
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
+ width: 350
+
+ label: "User Name or Email"
+ }
+
+ ShortcutText {
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
+
+ text: "Forgot Username?"
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ onLinkActivated: loginDialog.openUrl(link)
+ }
+ }
+ Row {
+ spacing: hifi.dimensions.contentSpacing.x
+
+ TextField {
+ id: passwordField
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
+ width: 350
+
+ label: "Password"
+ echoMode: TextInput.Password
+ }
+
+ ShortcutText {
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
+
+ text: "Forgot Password?"
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ onLinkActivated: loginDialog.openUrl(link)
+ }
+ }
+
+ }
+
+ Row {
+ id: buttons
+ anchors {
+ top: form.bottom
+ right: parent.right
+ margins: 0
+ topMargin: 3 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+
+ Button {
+ id: linkAccountButton
+ anchors.verticalCenter: parent.verticalCenter
+ width: 200
+
+ text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login")
+ color: hifi.buttons.blue
+
+ onClicked: linkAccountBody.login()
+ }
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: qsTr("Cancel")
+
+ onClicked: root.destroy()
+ }
+ }
+
+ Component.onCompleted: {
+ root.title = qsTr("Sign Into High Fidelity")
+ root.iconText = "<"
+ d.resize();
+
+ usernameField.forceActiveFocus()
+ }
+
+ Connections {
+ target: loginDialog
+ onHandleLoginCompleted: {
+ console.log("Login Succeeded, linking steam account")
+
+ if (loginDialog.isSteamRunning()) {
+ loginDialog.linkSteam()
+ } else {
+ bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ }
+ onHandleLoginFailed: {
+ console.log("Login Failed")
+ mainTextContainer.visible = true
+ }
+ onHandleLinkCompleted: {
+ console.log("Link Succeeded")
+
+ bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ onHandleLinkFailed: {
+ console.log("Link Failed")
+
+ }
+ }
+
+ Keys.onPressed: {
+ if (!visible) {
+ return
+ }
+
+ switch (event.key) {
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ event.accepted = true
+ linkAccountBody.login()
+ break
+ }
+ }
+}
diff --git a/interface/resources/qml/LoginDialog/SignInBody.qml b/interface/resources/qml/LoginDialog/SignInBody.qml
new file mode 100644
index 0000000000..2da0ea856d
--- /dev/null
+++ b/interface/resources/qml/LoginDialog/SignInBody.qml
@@ -0,0 +1,128 @@
+//
+// SignInBody.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2015 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
+//
+
+import Hifi 1.0
+import QtQuick 2.4
+import QtQuick.Controls.Styles 1.4 as OriginalStyles
+
+import "../controls-uit"
+import "../styles-uit"
+
+Item {
+ id: signInBody
+ clip: true
+ width: pane.width
+ height: pane.height
+
+ property bool required: false
+
+ function login() {
+ console.log("Trying to log in")
+ loginDialog.loginThroughSteam()
+ }
+
+ function cancel() {
+ root.destroy()
+ }
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 480
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
+ var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
+
+ root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
+ root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ }
+ }
+
+ MenuItem {
+ id: mainTextContainer
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ text: required ? qsTr("This domain's owner requires that you sign in:")
+ : qsTr("Sign in to access your user account:")
+ wrapMode: Text.WordWrap
+ color: hifi.colors.baseGrayHighlight
+ lineHeight: 2
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Row {
+ id: buttons
+ anchors {
+ top: mainTextContainer.bottom
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ width: undefined // invalidate so that the image's size sets the width
+ height: undefined // invalidate so that the image's size sets the height
+ focus: true
+
+ style: OriginalStyles.ButtonStyle {
+ background: Image {
+ id: buttonImage
+ source: "../../images/steam-sign-in.png"
+ }
+ }
+ onClicked: signInBody.login()
+ }
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: qsTr("Cancel");
+
+ onClicked: signInBody.cancel()
+ }
+ }
+
+ Component.onCompleted: {
+ root.title = required ? qsTr("Sign In Required")
+ : qsTr("Sign In")
+ root.iconText = ""
+ d.resize();
+ }
+
+ Connections {
+ target: loginDialog
+ onHandleLoginCompleted: {
+ console.log("Login Succeeded")
+
+ bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ onHandleLoginFailed: {
+ console.log("Login Failed")
+
+ bodyLoader.source = "CompleteProfileBody.qml"
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ }
+}
diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
new file mode 100644
index 0000000000..7e22b11f8b
--- /dev/null
+++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
@@ -0,0 +1,173 @@
+//
+// UsernameCollisionBody.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2015 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
+//
+
+import Hifi 1.0
+import QtQuick 2.4
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4 as OriginalStyles
+
+import "../controls-uit"
+import "../styles-uit"
+
+Item {
+ id: usernameCollisionBody
+ clip: true
+ width: root.pane.width
+ height: root.pane.height
+
+ function create() {
+ mainTextContainer.visible = false
+ loginDialog.createAccountFromStream(textField.text)
+ }
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 480
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, Math.max(mainTextContainer.contentWidth,
+ termsContainer.contentWidth))
+ var targetHeight = mainTextContainer.height +
+ 2 * hifi.dimensions.contentSpacing.y + textField.height +
+ 5 * hifi.dimensions.contentSpacing.y + termsContainer.height +
+ 1 * hifi.dimensions.contentSpacing.y + buttons.height
+
+ root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
+ root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ }
+ }
+
+ ShortcutText {
+ id: mainTextContainer
+ anchors {
+ top: parent.top
+ left: parent.left
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ text: qsTr("Your Steam username is not available.")
+ wrapMode: Text.WordWrap
+ color: hifi.colors.redAccent
+ lineHeight: 1
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+
+ TextField {
+ id: textField
+ anchors {
+ top: mainTextContainer.bottom
+ left: parent.left
+ margins: 0
+ topMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+ width: 250
+
+ placeholderText: "Choose your own"
+ }
+
+ MenuItem {
+ id: termsContainer
+ anchors {
+ top: textField.bottom
+ left: parent.left
+ margins: 0
+ topMargin: 3 * hifi.dimensions.contentSpacing.y
+ }
+
+ text: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
+ wrapMode: Text.WordWrap
+ color: hifi.colors.baseGrayHighlight
+ lineHeight: 1
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+
+ onLinkActivated: loginDialog.openUrl(link)
+ }
+
+ Row {
+ id: buttons
+ anchors {
+ top: termsContainer.bottom
+ right: parent.right
+ margins: 0
+ topMargin: 1 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+ width: 200
+
+ text: qsTr("Create your profile")
+ color: hifi.buttons.blue
+
+ onClicked: usernameCollisionBody.create()
+ }
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: qsTr("Cancel")
+
+ onClicked: root.destroy()
+ }
+ }
+
+ Component.onCompleted: {
+ root.title = qsTr("Complete Your Profile")
+ root.iconText = "<"
+ d.resize();
+ }
+ Connections {
+ target: loginDialog
+ onHandleCreateCompleted: {
+ console.log("Create Succeeded")
+
+ loginDialog.loginThroughSteam()
+ }
+ onHandleCreateFailed: {
+ console.log("Create Failed: " + error)
+
+ mainTextContainer.visible = true
+ mainTextContainer.text = "\"" + textField.text + qsTr("\" is invalid or already taken.")
+ }
+ onHandleLoginCompleted: {
+ console.log("Login Succeeded")
+
+ bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : false })
+ bodyLoader.item.width = root.pane.width
+ bodyLoader.item.height = root.pane.height
+ }
+ onHandleLoginFailed: {
+ console.log("Login Failed")
+ }
+ }
+
+ Keys.onPressed: {
+ if (!visible) {
+ return
+ }
+
+ switch (event.key) {
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ event.accepted = true
+ usernameCollisionBody.create()
+ break
+ }
+ }
+}
diff --git a/interface/resources/qml/LoginDialog/WelcomeBody.qml b/interface/resources/qml/LoginDialog/WelcomeBody.qml
new file mode 100644
index 0000000000..ecc848cdc0
--- /dev/null
+++ b/interface/resources/qml/LoginDialog/WelcomeBody.qml
@@ -0,0 +1,90 @@
+//
+// WelcomeBody.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2015 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
+//
+
+import Hifi 1.0
+import QtQuick 2.4
+
+import "../controls-uit"
+import "../styles-uit"
+
+Item {
+ id: welcomeBody
+ clip: true
+ width: pane.width
+ height: pane.height
+
+ property bool welcomeBack: false
+
+ function setTitle() {
+ root.title = (welcomeBack ? qsTr("Welcome back ") : qsTr("Welcome ")) + Account.username + qsTr("!")
+ root.iconText = ""
+ d.resize();
+ }
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 480
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
+ var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
+
+ root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
+ root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ }
+ }
+
+ MenuItem {
+ id: mainTextContainer
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ text: qsTr("You are now signed into High Fidelity")
+ wrapMode: Text.WordWrap
+ color: hifi.colors.baseGrayHighlight
+ lineHeight: 2
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Row {
+ id: buttons
+ anchors {
+ top: mainTextContainer.bottom
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+
+ Button {
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: qsTr("Close");
+
+ onClicked: root.destroy()
+ }
+ }
+
+ Component.onCompleted: welcomeBody.setTitle()
+
+ Connections {
+ target: Account
+ onUsernameChanged: welcomeBody.setTitle()
+ }
+}
diff --git a/interface/resources/qml/controls-uit/HorizontalRule.qml b/interface/resources/qml/controls-uit/HorizontalRule.qml
new file mode 100644
index 0000000000..425500f1d4
--- /dev/null
+++ b/interface/resources/qml/controls-uit/HorizontalRule.qml
@@ -0,0 +1,20 @@
+//
+// HorizontalRule.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 1
+ color: hifi.colors.lightGray
+}
diff --git a/interface/resources/qml/controls-uit/HorizontalSpacer.qml b/interface/resources/qml/controls-uit/HorizontalSpacer.qml
new file mode 100644
index 0000000000..545154ab44
--- /dev/null
+++ b/interface/resources/qml/controls-uit/HorizontalSpacer.qml
@@ -0,0 +1,21 @@
+//
+// HorizontalSpacer.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+
+import "../styles-uit"
+
+Item {
+ id: root
+ property alias size: root.width
+
+ width: hifi.dimensions.controlInterlineHeight
+ height: 1 // Must be non-zero
+}
diff --git a/interface/resources/qml/controls-uit/VerticalSpacer.qml b/interface/resources/qml/controls-uit/VerticalSpacer.qml
index 6fc49605c0..2df65f1002 100644
--- a/interface/resources/qml/controls-uit/VerticalSpacer.qml
+++ b/interface/resources/qml/controls-uit/VerticalSpacer.qml
@@ -13,6 +13,9 @@ import QtQuick 2.5
import "../styles-uit"
Item {
+ id: root
+ property alias size: root.height
+
width: 1 // Must be non-zero
height: hifi.dimensions.controlInterlineHeight
}
diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml
index d390ea08bf..40c5a01e15 100644
--- a/interface/resources/qml/dialogs/MessageDialog.qml
+++ b/interface/resources/qml/dialogs/MessageDialog.qml
@@ -21,8 +21,6 @@ import "messageDialog"
ModalWindow {
id: root
HifiConstants { id: hifi }
- implicitWidth: 640
- implicitHeight: 320
destroyOnCloseButton: true
destroyOnHidden: true
visible: true
@@ -70,7 +68,7 @@ ModalWindow {
QtObject {
id: d
readonly property int minWidth: 480
- readonly property int maxWdith: 1280
+ readonly property int maxWidth: 1280
readonly property int minHeight: 120
readonly property int maxHeight: 720
@@ -80,7 +78,7 @@ ModalWindow {
+ (informativeTextContainer.text != "" ? informativeTextContainer.contentHeight + 3 * hifi.dimensions.contentSpacing.y : 0)
+ buttons.height
+ (content.state === "expanded" ? details.implicitHeight + hifi.dimensions.contentSpacing.y : 0)
- root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth)
+ root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWidth) ? d.maxWidth : targetWidth)
root.height = (targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)
}
}
diff --git a/interface/resources/qml/styles-uit/ButtonLabel.qml b/interface/resources/qml/styles-uit/ButtonLabel.qml
new file mode 100644
index 0000000000..aade5fb439
--- /dev/null
+++ b/interface/resources/qml/styles-uit/ButtonLabel.qml
@@ -0,0 +1,18 @@
+//
+// ButtonLabel.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayBold {
+ font.pixelSize: hifi.fontSizes.buttonLabel
+}
diff --git a/interface/resources/qml/styles-uit/IconButton.qml b/interface/resources/qml/styles-uit/IconButton.qml
new file mode 100644
index 0000000000..84c1ef14c1
--- /dev/null
+++ b/interface/resources/qml/styles-uit/IconButton.qml
@@ -0,0 +1,20 @@
+//
+// IconButton.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayRegular {
+ font.pixelSize: hifi.fontSizes.iconButton
+ font.capitalization: Font.AllUppercase
+ font.letterSpacing: 1.5
+}
diff --git a/interface/resources/qml/styles-uit/InputLabel.qml b/interface/resources/qml/styles-uit/InputLabel.qml
new file mode 100644
index 0000000000..59657a554d
--- /dev/null
+++ b/interface/resources/qml/styles-uit/InputLabel.qml
@@ -0,0 +1,18 @@
+//
+// InputLabel.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewaySemiBold {
+ font.pixelSize: hifi.fontSizes.inputLabel
+}
diff --git a/interface/resources/qml/styles-uit/ListItem.qml b/interface/resources/qml/styles-uit/ListItem.qml
new file mode 100644
index 0000000000..f707686edc
--- /dev/null
+++ b/interface/resources/qml/styles-uit/ListItem.qml
@@ -0,0 +1,18 @@
+//
+// ListItem.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayRegular {
+ font.pixelSize: hifi.fontSizes.listItem
+}
diff --git a/interface/resources/qml/styles-uit/Logs.qml b/interface/resources/qml/styles-uit/Logs.qml
new file mode 100644
index 0000000000..577fe2f8d8
--- /dev/null
+++ b/interface/resources/qml/styles-uit/Logs.qml
@@ -0,0 +1,18 @@
+//
+// Logs.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+AnonymousProRegular {
+ font.pixelSize: hifi.fontSizes.logs
+}
diff --git a/interface/resources/qml/styles-uit/MenuItem.qml b/interface/resources/qml/styles-uit/MenuItem.qml
new file mode 100644
index 0000000000..4431c357bf
--- /dev/null
+++ b/interface/resources/qml/styles-uit/MenuItem.qml
@@ -0,0 +1,19 @@
+//
+// MenuItem.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewaySemiBold {
+ lineHeight: 2
+ font.pixelSize: hifi.fontSizes.menuItem
+}
diff --git a/interface/resources/qml/styles-uit/OverlayTitle.qml b/interface/resources/qml/styles-uit/OverlayTitle.qml
new file mode 100644
index 0000000000..e23b9eca14
--- /dev/null
+++ b/interface/resources/qml/styles-uit/OverlayTitle.qml
@@ -0,0 +1,18 @@
+//
+// OverlayTitle.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayRegular {
+ font.pixelSize: hifi.fontSizes.overlayTitle
+}
diff --git a/interface/resources/qml/styles-uit/SectionName.qml b/interface/resources/qml/styles-uit/SectionName.qml
new file mode 100644
index 0000000000..5438fec7bc
--- /dev/null
+++ b/interface/resources/qml/styles-uit/SectionName.qml
@@ -0,0 +1,19 @@
+//
+// SectionName.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayRegular {
+ font.pixelSize: hifi.fontSizes.sectionName
+ font.capitalization: Font.AllUppercase
+}
diff --git a/interface/resources/qml/styles-uit/ShortcutText.qml b/interface/resources/qml/styles-uit/ShortcutText.qml
new file mode 100644
index 0000000000..a3ab351870
--- /dev/null
+++ b/interface/resources/qml/styles-uit/ShortcutText.qml
@@ -0,0 +1,18 @@
+//
+// ShortcutText.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayLight {
+ font.pixelSize: hifi.fontSizes.shortcutText
+}
diff --git a/interface/resources/qml/styles-uit/TabName.qml b/interface/resources/qml/styles-uit/TabName.qml
new file mode 100644
index 0000000000..eb4e790e7e
--- /dev/null
+++ b/interface/resources/qml/styles-uit/TabName.qml
@@ -0,0 +1,19 @@
+//
+// TabName.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+RalewayRegular {
+ font.pixelSize: hifi.fontSizes.tabName
+ font.capitalization: Font.AllUppercase
+}
diff --git a/interface/resources/qml/styles-uit/TextFieldInput.qml b/interface/resources/qml/styles-uit/TextFieldInput.qml
new file mode 100644
index 0000000000..010b4d03ad
--- /dev/null
+++ b/interface/resources/qml/styles-uit/TextFieldInput.qml
@@ -0,0 +1,18 @@
+//
+// TextFieldInput.qml
+//
+// Created by Clement on 7/18/16
+// Copyright 2016 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
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "."
+
+FiraSansSemiBold {
+ font.pixelSize: hifi.fontSizes.textFieldInput
+}
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index bf85c17370..76649739be 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -86,15 +86,16 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
-#include
+#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -1631,6 +1632,8 @@ void Application::initializeUi() {
rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
+
+ rootContext->setContextProperty("Steam", new SteamScriptingInterface(engine));
_glWidget->installEventFilter(offscreenUi.data());
@@ -2943,6 +2946,8 @@ void Application::idle(float nsecsElapsed) {
PROFILE_RANGE(__FUNCTION__);
+ SteamClient::runCallbacks();
+
float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND;
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
@@ -3251,6 +3256,14 @@ void Application::init() {
addressLookupString = arguments().value(urlIndex + 1);
}
+ // when +connect_lobby in command line, join steam lobby
+ const QString STEAM_LOBBY_COMMAND_LINE_KEY = "+connect_lobby";
+ int lobbyIndex = arguments().indexOf(STEAM_LOBBY_COMMAND_LINE_KEY);
+ if (lobbyIndex != -1) {
+ QString lobbyId = arguments().value(lobbyIndex + 1);
+ SteamClient::joinLobby(lobbyId);
+ }
+
Setting::Handle firstRun { Settings::firstRun, true };
if (addressLookupString.isEmpty() && firstRun.get()) {
qDebug() << "First run and no URL passed... attempting to go to Home or Entry...";
@@ -4837,6 +4850,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data());
scriptEngine->registerGlobalObject("Users", DependencyManager::get().data());
+
+ scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine));
}
bool Application::canAcceptURL(const QString& urlString) const {
diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp
index 4051bd8a1e..dd80dadca7 100644
--- a/interface/src/DiscoverabilityManager.cpp
+++ b/interface/src/DiscoverabilityManager.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
@@ -36,11 +37,11 @@ const QString SESSION_ID_KEY = "session_id";
void DiscoverabilityManager::updateLocation() {
auto accountManager = DependencyManager::get();
-
- if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) {
- auto addressManager = DependencyManager::get();
- DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler();
+ auto addressManager = DependencyManager::get();
+ auto& domainHandler = DependencyManager::get()->getDomainHandler();
+
+ if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
@@ -48,8 +49,6 @@ void DiscoverabilityManager::updateLocation() {
QString pathString = addressManager->currentPath();
- const QString LOCATION_KEY_IN_ROOT = "location";
-
const QString PATH_KEY_IN_LOCATION = "path";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
@@ -90,6 +89,7 @@ void DiscoverabilityManager::updateLocation() {
// we have a changed location, send it now
_lastLocationObject = locationObject;
+ const QString LOCATION_KEY_IN_ROOT = "location";
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
apiPath = API_USER_LOCATION_PATH;
@@ -109,6 +109,9 @@ void DiscoverabilityManager::updateLocation() {
accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
QNetworkAccessManager::PutOperation, callbackParameters);
}
+
+ // Update Steam
+ SteamClient::updateLocation(domainHandler.getHostname(), addressManager->currentFacingAddress());
}
void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) {
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index de5455fc14..57e379a9ac 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -122,11 +122,13 @@ MyAvatar::MyAvatar(RigPointer rig) :
_driveKeys[i] = 0.0f;
}
+
+ // Necessary to select the correct slot
+ using SlotType = void(MyAvatar::*)(const glm::vec3&, bool, const glm::quat&, bool);
+
// connect to AddressManager signal for location jumps
- connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired,
- [=](const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, bool shouldFaceLocation){
- goToLocation(newPosition, hasOrientation, newOrientation, shouldFaceLocation);
- });
+ connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired,
+ this, static_cast(&MyAvatar::goToLocation));
_characterController.setEnabled(true);
@@ -1859,7 +1861,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation);
if (shouldFaceLocation) {
- quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
+ quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
diff --git a/interface/src/main.cpp b/interface/src/main.cpp
index 8fc0384aee..527b7f2331 100644
--- a/interface/src/main.cpp
+++ b/interface/src/main.cpp
@@ -8,6 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
+
#include
#include
#include
@@ -20,12 +22,13 @@
#include
#include
+#include
+
#include "AddressManager.h"
#include "Application.h"
#include "InterfaceLogging.h"
#include "UserActivityLogger.h"
#include "MainWindow.h"
-#include
#ifdef HAS_BUGSPLAT
#include
@@ -137,6 +140,8 @@ int main(int argc, const char* argv[]) {
// or in the main window ctor, before GL startup.
Application::initPlugins(arguments);
+ SteamClient::init();
+
int exitCode;
{
QSettings::setDefaultFormat(QSettings::IniFormat);
@@ -202,6 +207,8 @@ int main(int argc, const char* argv[]) {
Application::shutdownPlugins();
+ SteamClient::shutdown();
+
qCDebug(interfaceapp, "Normal exit.");
#if !defined(DEBUG) && !defined(Q_OS_LINUX)
// HACK: exit immediately (don't handle shutdown callbacks) for Release build
diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp
index 1328197195..4090c99ac8 100644
--- a/interface/src/scripting/AccountScriptingInterface.cpp
+++ b/interface/src/scripting/AccountScriptingInterface.cpp
@@ -15,6 +15,9 @@
AccountScriptingInterface* AccountScriptingInterface::getInstance() {
static AccountScriptingInterface sharedInstance;
+ auto accountManager = DependencyManager::get();
+ QObject::connect(accountManager.data(), &AccountManager::profileChanged,
+ &sharedInstance, &AccountScriptingInterface::usernameChanged);
return &sharedInstance;
}
diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h
index 888149b836..49648781ce 100644
--- a/interface/src/scripting/AccountScriptingInterface.h
+++ b/interface/src/scripting/AccountScriptingInterface.h
@@ -17,6 +17,11 @@
class AccountScriptingInterface : public QObject {
Q_OBJECT
+ Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged)
+
+signals:
+ void usernameChanged();
+
public slots:
static AccountScriptingInterface* getInstance();
QString getUsername();
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 80d52b7a07..fef0a12813 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -12,8 +12,11 @@
#include "LoginDialog.h"
#include
+#include
+#include
#include
+#include
#include "AccountManager.h"
#include "DependencyManager.h"
@@ -21,9 +24,7 @@
HIFI_QML_DEF(LoginDialog)
-LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent),
- _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString())
-{
+LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
auto accountManager = DependencyManager::get();
connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
@@ -53,36 +54,102 @@ void LoginDialog::toggleAction() {
}
}
-void LoginDialog::handleLoginCompleted(const QUrl&) {
- hide();
+bool LoginDialog::isSteamRunning() const {
+ return SteamClient::isRunning();
}
-void LoginDialog::handleLoginFailed() {
- setStatusText("Invalid username or password");
-}
-
-void LoginDialog::setStatusText(const QString& statusText) {
- if (statusText != _statusText) {
- _statusText = statusText;
- emit statusTextChanged();
- }
-}
-
-QString LoginDialog::statusText() const {
- return _statusText;
-}
-
-QString LoginDialog::rootUrl() const {
- return _rootUrl;
-}
-
-void LoginDialog::login(const QString& username, const QString& password) {
+void LoginDialog::login(const QString& username, const QString& password) const {
qDebug() << "Attempting to login " << username;
- setStatusText("Logging in...");
DependencyManager::get()->requestAccessToken(username, password);
}
-void LoginDialog::openUrl(const QString& url) {
- qDebug() << url;
- QDesktopServices::openUrl(url);
+void LoginDialog::loginThroughSteam() {
+ qDebug() << "Attempting to login through Steam";
+ SteamClient::requestTicket([this](Ticket ticket) {
+ if (ticket.isNull()) {
+ emit handleLoginFailed();
+ return;
+ }
+
+ DependencyManager::get()->requestAccessTokenWithSteam(ticket);
+ });
}
+
+void LoginDialog::linkSteam() {
+ qDebug() << "Attempting to link Steam account";
+ SteamClient::requestTicket([this](Ticket ticket) {
+ if (ticket.isNull()) {
+ emit handleLoginFailed();
+ return;
+ }
+
+ JSONCallbackParameters callbackParams;
+ callbackParams.jsonCallbackReceiver = this;
+ callbackParams.jsonCallbackMethod = "linkCompleted";
+ callbackParams.errorCallbackReceiver = this;
+ callbackParams.errorCallbackMethod = "linkFailed";
+
+ const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
+
+ QJsonObject payload;
+ payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
+
+ auto accountManager = DependencyManager::get();
+ accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required,
+ QNetworkAccessManager::PostOperation, callbackParams,
+ QJsonDocument(payload).toJson());
+ });
+}
+
+void LoginDialog::createAccountFromStream(QString username) {
+ qDebug() << "Attempting to create account from Steam info";
+ SteamClient::requestTicket([this, username](Ticket ticket) {
+ if (ticket.isNull()) {
+ emit handleLoginFailed();
+ return;
+ }
+
+ JSONCallbackParameters callbackParams;
+ callbackParams.jsonCallbackReceiver = this;
+ callbackParams.jsonCallbackMethod = "createCompleted";
+ callbackParams.errorCallbackReceiver = this;
+ callbackParams.errorCallbackMethod = "createFailed";
+
+ const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
+
+ QJsonObject payload;
+ payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
+ if (!username.isEmpty()) {
+ payload.insert("username", QJsonValue::fromVariant(QVariant(username)));
+ }
+
+ auto accountManager = DependencyManager::get();
+ accountManager->sendRequest(CREATE_ACCOUNT_FROM_STEAM_PATH, AccountManagerAuth::None,
+ QNetworkAccessManager::PostOperation, callbackParams,
+ QJsonDocument(payload).toJson());
+ });
+
+}
+
+void LoginDialog::openUrl(const QString& url) const {
+ auto offscreenUi = DependencyManager::get();
+ auto browser = offscreenUi->load("Browser.qml");
+ browser->setProperty("url", url);
+}
+
+void LoginDialog::linkCompleted(QNetworkReply& reply) {
+ emit handleLinkCompleted();
+}
+
+void LoginDialog::linkFailed(QNetworkReply& reply) {
+ emit handleLinkFailed(reply.errorString());
+}
+
+void LoginDialog::createCompleted(QNetworkReply& reply) {
+ emit handleCreateCompleted();
+}
+
+void LoginDialog::createFailed(QNetworkReply& reply) {
+ emit handleCreateFailed(reply.errorString());
+}
+
diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h
index 25ecf45898..8b6dc40302 100644
--- a/interface/src/ui/LoginDialog.h
+++ b/interface/src/ui/LoginDialog.h
@@ -16,35 +16,44 @@
#include
+class QNetworkReply;
+
class LoginDialog : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL
- Q_PROPERTY(QString statusText READ statusText WRITE setStatusText NOTIFY statusTextChanged)
- Q_PROPERTY(QString rootUrl READ rootUrl)
-
public:
static void toggleAction();
LoginDialog(QQuickItem* parent = nullptr);
- void setStatusText(const QString& statusText);
- QString statusText() const;
-
- QString rootUrl() const;
-
signals:
- void statusTextChanged();
-
-protected:
- void handleLoginCompleted(const QUrl& authURL);
+ void handleLoginCompleted();
void handleLoginFailed();
- Q_INVOKABLE void login(const QString& username, const QString& password);
- Q_INVOKABLE void openUrl(const QString& url);
-private:
- QString _statusText;
- const QString _rootUrl;
+ void handleLinkCompleted();
+ void handleLinkFailed(QString error);
+
+ void handleCreateCompleted();
+ void handleCreateFailed(QString error);
+
+public slots:
+ void linkCompleted(QNetworkReply& reply);
+ void linkFailed(QNetworkReply& reply);
+
+ void createCompleted(QNetworkReply& reply);
+ void createFailed(QNetworkReply& reply);
+
+protected slots:
+ Q_INVOKABLE bool isSteamRunning() const;
+
+ Q_INVOKABLE void login(const QString& username, const QString& password) const;
+ Q_INVOKABLE void loginThroughSteam();
+ Q_INVOKABLE void linkSteam();
+ Q_INVOKABLE void createAccountFromStream(QString username = QString());
+
+ Q_INVOKABLE void openUrl(const QString& url) const;
+
};
#endif // hifi_LoginDialog_h
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index c4bfae7cac..d89514b7cd 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -505,6 +505,29 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
}
+void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
+ QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
+
+ QNetworkRequest request;
+ request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
+
+ QUrl grantURL = _authURL;
+ grantURL.setPath("/oauth/token");
+
+ const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
+
+ QByteArray postData;
+ postData.append("grant_type=password&");
+ postData.append("steam_auth_ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&");
+ 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::requestAccessTokenFinished() {
QNetworkReply* requestReply = reinterpret_cast(sender());
@@ -545,6 +568,7 @@ void AccountManager::requestAccessTokenFinished() {
void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) {
// TODO: error handling
qCDebug(networking) << "AccountManager requestError - " << error;
+ emit loginFailed();
}
void AccountManager::requestProfile() {
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index 846cdb6220..eb4d224501 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -96,6 +96,7 @@ public:
public slots:
void requestAccessToken(const QString& login, const QString& password);
+ void requestAccessTokenWithSteam(QByteArray authSessionTicket);
void requestAccessTokenFinished();
void requestProfileFinished();
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index ae6aad3c4f..6760d44244 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -17,6 +17,7 @@
#include
#include
+#include
#include
#include
@@ -46,7 +47,7 @@ bool AddressManager::isConnected() {
return DependencyManager::get()->getDomainHandler().isConnected();
}
-const QUrl AddressManager::currentAddress() const {
+QUrl AddressManager::currentAddress() const {
QUrl hifiURL;
hifiURL.setScheme(HIFI_URL_SCHEME);
@@ -61,6 +62,21 @@ const QUrl AddressManager::currentAddress() const {
return hifiURL;
}
+QUrl AddressManager::currentFacingAddress() const {
+ QUrl hifiURL;
+
+ hifiURL.setScheme(HIFI_URL_SCHEME);
+ hifiURL.setHost(_host);
+
+ if (_port != 0 && _port != DEFAULT_DOMAIN_SERVER_PORT) {
+ hifiURL.setPort(_port);
+ }
+
+ hifiURL.setPath(currentFacingPath());
+
+ return hifiURL;
+}
+
void AddressManager::loadSettings(const QString& lookupString) {
if (lookupString.isEmpty()) {
handleUrl(currentAddressHandle.get().toString(), LookupTrigger::StartupFromSettings);
@@ -97,7 +113,7 @@ void AddressManager::storeCurrentAddress() {
currentAddressHandle.set(currentAddress());
}
-const QString AddressManager::currentPath(bool withOrientation) const {
+QString AddressManager::currentPath(bool withOrientation) const {
if (_positionGetter) {
QString pathString = "/" + createByteArray(_positionGetter());
@@ -121,6 +137,25 @@ const QString AddressManager::currentPath(bool withOrientation) const {
}
}
+QString AddressManager::currentFacingPath() const {
+ if (_positionGetter && _orientationGetter) {
+ auto position = _positionGetter();
+ auto orientation = _orientationGetter();
+
+ // move the user a couple units away
+ const float DISTANCE_TO_USER = 2.0f;
+ position += orientation * Vectors::FRONT * DISTANCE_TO_USER;
+
+ // rotate the user by 180 degrees
+ orientation = orientation * glm::angleAxis(PI, Vectors::UP);
+
+ return "/" + createByteArray(position) + "/" + createByteArray(orientation);
+ } else {
+ qCDebug(networking) << "Cannot create address path without a getter for position/orientation.";
+ return QString();
+ }
+}
+
const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
static bool hasSetupParameters = false;
static JSONCallbackParameters callbackParams;
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index 2e9f177137..8ccddc5975 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -57,8 +57,10 @@ public:
bool isConnected();
const QString& getProtocol() { return HIFI_URL_SCHEME; };
- const QUrl currentAddress() const;
- const QString currentPath(bool withOrientation = true) const;
+ QUrl currentAddress() const;
+ QUrl currentFacingAddress() const;
+ QString currentPath(bool withOrientation = true) const;
+ QString currentFacingPath() const;
const QUuid& getRootPlaceID() const { return _rootPlaceID; }
const QString& getPlaceName() const { return _placeName; }
diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp
index 0f06e03672..2e8f6bd7b3 100644
--- a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp
+++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp
@@ -11,9 +11,303 @@
#include "SteamClient.h"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverloaded-virtual"
+#endif
+
#include
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+
+static const Ticket INVALID_TICKET = Ticket();
+
+class SteamTicketRequests {
+public:
+ SteamTicketRequests();
+ ~SteamTicketRequests();
+
+ HAuthTicket startRequest(TicketRequestCallback callback);
+ void stopRequest(HAuthTicket authTicket);
+ void stopAll();
+
+ STEAM_CALLBACK(SteamTicketRequests, onGetAuthSessionTicketResponse,
+ GetAuthSessionTicketResponse_t, _getAuthSessionTicketResponse);
+
+private:
+ struct PendingTicket {
+ HAuthTicket authTicket;
+ Ticket ticket;
+ TicketRequestCallback callback;
+ };
+
+ std::vector _pendingTickets;
+};
+
+SteamTicketRequests::SteamTicketRequests() :
+ _getAuthSessionTicketResponse(this, &SteamTicketRequests::onGetAuthSessionTicketResponse)
+{
+}
+
+SteamTicketRequests::~SteamTicketRequests() {
+ stopAll();
+}
+
+HAuthTicket SteamTicketRequests::startRequest(TicketRequestCallback callback) {
+ static const uint32 MAX_TICKET_SIZE { 1024 };
+ uint32 ticketSize { 0 };
+ char ticket[MAX_TICKET_SIZE];
+
+ auto authTicket = SteamUser()->GetAuthSessionTicket(ticket, MAX_TICKET_SIZE, &ticketSize);
+ qDebug() << "Got Steam auth session ticket:" << authTicket;
+
+ if (authTicket == k_HAuthTicketInvalid) {
+ qWarning() << "Auth session ticket is invalid.";
+ callback(INVALID_TICKET);
+ } else {
+ PendingTicket pendingTicket{ authTicket, QByteArray(ticket, ticketSize).toHex(), callback };
+ _pendingTickets.push_back(pendingTicket);
+ }
+
+ return authTicket;
+}
+
+void SteamTicketRequests::stopRequest(HAuthTicket authTicket) {
+ auto it = std::find_if(_pendingTickets.begin(), _pendingTickets.end(), [&authTicket](const PendingTicket& pendingTicket) {
+ return pendingTicket.authTicket == authTicket;
+ });
+
+ if (it != _pendingTickets.end()) {
+ SteamUser()->CancelAuthTicket(it->authTicket);
+ it->callback(INVALID_TICKET);
+ _pendingTickets.erase(it);
+ }
+}
+
+void SteamTicketRequests::stopAll() {
+ auto steamUser = SteamUser();
+ if (steamUser) {
+ for (const auto& pendingTicket : _pendingTickets) {
+ steamUser->CancelAuthTicket(pendingTicket.authTicket);
+ pendingTicket.callback(INVALID_TICKET);
+ }
+ }
+ _pendingTickets.clear();
+}
+
+void SteamTicketRequests::onGetAuthSessionTicketResponse(GetAuthSessionTicketResponse_t* pCallback) {
+ auto authTicket = pCallback->m_hAuthTicket;
+
+ auto it = std::find_if(_pendingTickets.begin(), _pendingTickets.end(), [&authTicket](const PendingTicket& pendingTicket) {
+ return pendingTicket.authTicket == authTicket;
+ });
+
+
+ if (it != _pendingTickets.end()) {
+
+ if (pCallback->m_eResult == k_EResultOK) {
+ qDebug() << "Got steam callback, auth session ticket is valid. Send it." << authTicket;
+ it->callback(it->ticket);
+ } else {
+ qWarning() << "Steam auth session ticket callback encountered an error:" << pCallback->m_eResult;
+ it->callback(INVALID_TICKET);
+ }
+
+ _pendingTickets.erase(it);
+ } else {
+ qWarning() << "Could not find steam auth session ticket in list of pending tickets:" << authTicket;
+ }
+}
+
+
+const QString CONNECT_PREFIX = "--url \"";
+const QString CONNECT_SUFFIX = "\"";
+
+class SteamCallbackManager {
+public:
+ SteamCallbackManager();
+
+ STEAM_CALLBACK(SteamCallbackManager, onGameRichPresenceJoinRequested,
+ GameRichPresenceJoinRequested_t, _gameRichPresenceJoinRequestedResponse);
+
+ STEAM_CALLBACK(SteamCallbackManager, onLobbyCreated,
+ LobbyCreated_t, _lobbyCreatedResponse);
+
+ STEAM_CALLBACK(SteamCallbackManager, onGameLobbyJoinRequested,
+ GameLobbyJoinRequested_t, _gameLobbyJoinRequestedResponse);
+
+ STEAM_CALLBACK(SteamCallbackManager, onLobbyEnter,
+ LobbyEnter_t, _lobbyEnterResponse);
+
+ SteamTicketRequests& getTicketRequests() { return _steamTicketRequests; }
+
+private:
+ SteamTicketRequests _steamTicketRequests;
+};
+
+SteamCallbackManager::SteamCallbackManager() :
+ _gameRichPresenceJoinRequestedResponse(this, &SteamCallbackManager::onGameRichPresenceJoinRequested),
+ _lobbyCreatedResponse(this, &SteamCallbackManager::onLobbyCreated),
+ _gameLobbyJoinRequestedResponse(this, &SteamCallbackManager::onGameLobbyJoinRequested),
+ _lobbyEnterResponse(this, &SteamCallbackManager::onLobbyEnter)
+{
+}
+
+void parseUrlAndGo(QString url) {
+ if (url.startsWith(CONNECT_PREFIX) && url.endsWith(CONNECT_SUFFIX)) {
+ url.remove(0, CONNECT_PREFIX.size());
+ url.remove(-CONNECT_SUFFIX.size(), CONNECT_SUFFIX.size());
+ }
+
+ qDebug() << "Joining Steam Friend at:" << url;
+ auto mimeData = new QMimeData();
+ mimeData->setUrls(QList() << QUrl(url));
+ auto event = new QDropEvent(QPointF(0, 0), Qt::MoveAction, mimeData, Qt::LeftButton, Qt::NoModifier);
+
+ QCoreApplication::postEvent(qApp, event);
+}
+
+void SteamCallbackManager::onGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t* pCallback) {
+ auto url = QString::fromLocal8Bit(pCallback->m_rgchConnect);
+
+ parseUrlAndGo(url);
+}
+void SteamCallbackManager::onLobbyCreated(LobbyCreated_t* pCallback) {
+ if (pCallback->m_eResult == k_EResultOK) {
+ qDebug() << "Inviting steam friends" << pCallback->m_ulSteamIDLobby;
+ auto url = SteamFriends()->GetFriendRichPresence(SteamUser()->GetSteamID(), "connect");
+ SteamMatchmaking()->SetLobbyData(pCallback->m_ulSteamIDLobby, "connect", url);
+ SteamFriends()->ActivateGameOverlayInviteDialog(pCallback->m_ulSteamIDLobby);
+ }
+}
+
+void SteamCallbackManager::onGameLobbyJoinRequested(GameLobbyJoinRequested_t* pCallback) {
+ qDebug() << "Joining Steam lobby" << pCallback->m_steamIDLobby.ConvertToUint64();
+ SteamMatchmaking()->JoinLobby(pCallback->m_steamIDLobby);
+}
+
+void SteamCallbackManager::onLobbyEnter(LobbyEnter_t* pCallback) {
+ if (pCallback->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess) {
+ qWarning() << "An error occured while joing the Steam lobby:" << pCallback->m_EChatRoomEnterResponse;
+ return;
+ }
+
+ qDebug() << "Entered Steam lobby" << pCallback->m_ulSteamIDLobby;
+
+ if (SteamMatchmaking()->GetLobbyOwner(pCallback->m_ulSteamIDLobby) != SteamUser()->GetSteamID()) {
+ auto url = SteamMatchmaking()->GetLobbyData(pCallback->m_ulSteamIDLobby, "connect");
+ qDebug() << "Jumping to" << url;
+ parseUrlAndGo(url);
+ SteamMatchmaking()->LeaveLobby(pCallback->m_ulSteamIDLobby);
+ }
+}
+
+
+static std::atomic_bool initialized { false };
+static SteamCallbackManager steamCallbackManager;
+
+
+bool SteamClient::isRunning() {
+ return initialized;
+}
+
+bool SteamClient::init() {
+ if (SteamAPI_IsSteamRunning() && !initialized) {
+ initialized = SteamAPI_Init();
+ }
+ return initialized;
+}
+
+void SteamClient::shutdown() {
+ if (initialized) {
+ SteamAPI_Shutdown();
+ }
+
+ steamCallbackManager.getTicketRequests().stopAll();
+}
+
+void SteamClient::runCallbacks() {
+ if (!initialized) {
+ return;
+ }
+
+ auto steamPipe = SteamAPI_GetHSteamPipe();
+ if (!steamPipe) {
+ qDebug() << "Could not get SteamPipe";
+ return;
+ }
+
+ Steam_RunCallbacks(steamPipe, false);
+}
+
+void SteamClient::requestTicket(TicketRequestCallback callback) {
+ if (!initialized) {
+ if (SteamAPI_IsSteamRunning()) {
+ init();
+ } else {
+ qWarning() << "Steam is not running";
+ callback(INVALID_TICKET);
+ return;
+ }
+ }
+
+ if (!initialized) {
+ qDebug() << "Steam not initialized";
+ return;
+ }
+
+ steamCallbackManager.getTicketRequests().startRequest(callback);
+}
+
+void SteamClient::updateLocation(QString status, QUrl locationUrl) {
+ if (!initialized) {
+ return;
+ }
+
+ auto connectStr = locationUrl.isEmpty() ? "" : CONNECT_PREFIX + locationUrl.toString() + CONNECT_SUFFIX;
+
+ SteamFriends()->SetRichPresence("status", status.toLocal8Bit().data());
+ SteamFriends()->SetRichPresence("connect", connectStr.toLocal8Bit().data());
+}
+
+void SteamClient::openInviteOverlay() {
+ if (!initialized) {
+ return;
+ }
+
+ qDebug() << "Creating Steam lobby";
+ static const int MAX_LOBBY_SIZE = 20;
+ SteamMatchmaking()->CreateLobby(k_ELobbyTypePrivate, MAX_LOBBY_SIZE);
+}
+
+
+void SteamClient::joinLobby(QString lobbyIdStr) {
+ if (!initialized) {
+ if (SteamAPI_IsSteamRunning()) {
+ init();
+ } else {
+ qWarning() << "Steam is not running";
+ return;
+ }
+ }
+
+ qDebug() << "Trying to join Steam lobby:" << lobbyIdStr;
+ CSteamID lobbyId(lobbyIdStr.toULongLong());
+ SteamMatchmaking()->JoinLobby(lobbyId);
+}
\ No newline at end of file
diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h
index 369641b0c7..5bf0d4db56 100644
--- a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h
+++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h
@@ -13,8 +13,43 @@
#ifndef hifi_SteamClient_h
#define hifi_SteamClient_h
-class SteamClient {
+#include
+#include
+#include
+
+using Ticket = QByteArray;
+using TicketRequestCallback = std::function;
+
+class QUrl;
+
+class SteamClient {
+public:
+ static bool isRunning();
+
+ static bool init();
+ static void shutdown();
+
+ static void runCallbacks();
+
+ static void requestTicket(TicketRequestCallback callback);
+ static void updateLocation(QString status, QUrl locationUrl);
+ static void openInviteOverlay();
+ static void joinLobby(QString lobbyId);
+
+};
+
+class SteamScriptingInterface : public QObject {
+ Q_OBJECT
+
+ Q_PROPERTY(bool isRunning READ isRunning)
+
+public:
+ SteamScriptingInterface(QObject* parent) : QObject(parent) {}
+
+public slots:
+ bool isRunning() const { return SteamClient::isRunning(); }
+ void openInviteOverlay() const { SteamClient::openInviteOverlay(); }
};
diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
index 4e84c6d0fa..a532261014 100644
--- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp
+++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
@@ -161,7 +161,10 @@ static bool isBadPose(vr::HmdMatrix34_t* mat) {
bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
handleOpenVrEvents();
-
+ if (openVrQuitRequested()) {
+ QMetaObject::invokeMethod(qApp, "quit");
+ return false;
+ }
double displayFrequency = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float);
double frameDuration = 1.f / displayFrequency;
double vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float);
diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp
index c93a2178b5..ee61a07da6 100644
--- a/plugins/openvr/src/OpenVrHelpers.cpp
+++ b/plugins/openvr/src/OpenVrHelpers.cpp
@@ -34,6 +34,11 @@ using Lock = std::unique_lock;
static int refCount { 0 };
static Mutex mutex;
static vr::IVRSystem* activeHmd { nullptr };
+static bool _openVrQuitRequested { false };
+
+bool openVrQuitRequested() {
+ return _openVrQuitRequested;
+}
static const uint32_t RELEASE_OPENVR_HMD_DELAY_MS = 5000;
@@ -99,6 +104,7 @@ void releaseOpenVrSystem() {
qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system";
#endif
vr::VR_Shutdown();
+ _openVrQuitRequested = false;
activeHmd = nullptr;
}
}
@@ -257,8 +263,8 @@ void handleOpenVrEvents() {
while (activeHmd->PollNextEvent(&event, sizeof(event))) {
switch (event.eventType) {
case vr::VREvent_Quit:
+ _openVrQuitRequested = true;
activeHmd->AcknowledgeQuit_Exiting();
- QMetaObject::invokeMethod(qApp, "quit");
break;
case vr::VREvent_KeyboardDone:
diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp
index 85feebda11..930b3dd450 100644
--- a/plugins/openvr/src/ViveControllerManager.cpp
+++ b/plugins/openvr/src/ViveControllerManager.cpp
@@ -214,6 +214,10 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch&
void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
auto userInputMapper = DependencyManager::get();
handleOpenVrEvents();
+ if (openVrQuitRequested()) {
+ deactivate();
+ return;
+ }
// because update mutates the internal state we need to lock
userInputMapper->withLock([&, this]() {
diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
index 0efcd0c140..cf707c4d19 100644
--- a/scripts/defaultScripts.js
+++ b/scripts/defaultScripts.js
@@ -15,6 +15,7 @@ Script.load("system/users.js");
Script.load("system/mute.js");
Script.load("system/goto.js");
Script.load("system/hmd.js");
+Script.load("system/steam.js");
Script.load("system/marketplace.js");
Script.load("system/edit.js");
Script.load("system/mod.js");
diff --git a/scripts/system/assets/images/tools/steam-invite.svg b/scripts/system/assets/images/tools/steam-invite.svg
new file mode 100644
index 0000000000..ce225cca68
--- /dev/null
+++ b/scripts/system/assets/images/tools/steam-invite.svg
@@ -0,0 +1,112 @@
+
+
+
diff --git a/scripts/system/steam.js b/scripts/system/steam.js
new file mode 100644
index 0000000000..a80f0072ac
--- /dev/null
+++ b/scripts/system/steam.js
@@ -0,0 +1,31 @@
+//
+// steam.js
+// scripts/system/
+//
+// Created by Clement on 7/28/16
+// Copyright 2016 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
+//
+
+var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
+
+
+var steamInviteButton = toolBar.addButton({
+ objectName: "steamInvite",
+ imageURL: Script.resolvePath("assets/images/tools/steam-invite.svg"),
+ visible: Steam.isRunning,
+ buttonState: 1,
+ defaultState: 1,
+ hoverState: 3,
+ alpha: 0.9
+});
+
+steamInviteButton.clicked.connect(function(){
+ Steam.openInviteOverlay();
+});
+
+Script.scriptEnding.connect(function () {
+ toolBar.removeButton("steamInvite");
+});
\ No newline at end of file