Merge pull request #8407 from highfidelity/steam

Steam integration
This commit is contained in:
Clément Brisset 2016-08-12 16:55:48 -07:00 committed by GitHub
commit 5a1c7bd4e6
42 changed files with 1752 additions and 343 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -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
}
}

View file

@ -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: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
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")
}
}
}

View file

@ -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: "<a href='https://highfidelity.com/users/password/new'>Forgot Username?</a>"
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: "<a href='https://highfidelity.com/users/password/new'>Forgot Password?</a>"
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
}
}
}

View file

@ -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
}
}
}

View file

@ -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 <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
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
}
}
}

View file

@ -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 <b>") : qsTr("Welcome <b>")) + Account.username + qsTr("</b>!")
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()
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -86,15 +86,16 @@
#include <PhysicsHelpers.h>
#include <plugins/PluginManager.h>
#include <plugins/CodecPlugin.h>
#include <RecordingScriptingInterface.h>
#include <RenderableWebEntityItem.h>
#include <RenderShadowTask.h>
#include <RenderDeferredTask.h>
#include <ResourceCache.h>
#include <SceneScriptingInterface.h>
#include <RecordingScriptingInterface.h>
#include <ScriptEngines.h>
#include <ScriptCache.h>
#include <SoundCache.h>
#include <ScriptEngines.h>
#include <steamworks-wrapper/SteamClient.h>
#include <Tooltip.h>
#include <udt/PacketHeaders.h>
#include <UserActivityLogger.h>
@ -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<bool> 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<UserActivityLoggerScriptingInterface>().data());
scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine));
}
bool Application::canAcceptURL(const QString& urlString) const {

View file

@ -15,6 +15,7 @@
#include <AddressManager.h>
#include <DomainHandler.h>
#include <NodeList.h>
#include <steamworks-wrapper/SteamClient.h>
#include <UserActivityLogger.h>
#include <UUID.h>
@ -36,11 +37,11 @@ const QString SESSION_ID_KEY = "session_id";
void DiscoverabilityManager::updateLocation() {
auto accountManager = DependencyManager::get<AccountManager>();
if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) {
auto addressManager = DependencyManager::get<AddressManager>();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
auto addressManager = DependencyManager::get<AddressManager>();
auto& domainHandler = DependencyManager::get<NodeList>()->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) {

View file

@ -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<AddressManager>().data(), &AddressManager::locationChangeRequired,
[=](const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, bool shouldFaceLocation){
goToLocation(newPosition, hasOrientation, newOrientation, shouldFaceLocation);
});
connect(DependencyManager::get<AddressManager>().data(), &AddressManager::locationChangeRequired,
this, static_cast<SlotType>(&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;

View file

@ -8,6 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <thread>
#include <QCommandLineParser>
#include <QDebug>
#include <QDir>
@ -20,12 +22,13 @@
#include <gl/OpenGLVersionChecker.h>
#include <SharedUtil.h>
#include <steamworks-wrapper/SteamClient.h>
#include "AddressManager.h"
#include "Application.h"
#include "InterfaceLogging.h"
#include "UserActivityLogger.h"
#include "MainWindow.h"
#include <thread>
#ifdef HAS_BUGSPLAT
#include <BuildInfo.h>
@ -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

View file

@ -15,6 +15,9 @@
AccountScriptingInterface* AccountScriptingInterface::getInstance() {
static AccountScriptingInterface sharedInstance;
auto accountManager = DependencyManager::get<AccountManager>();
QObject::connect(accountManager.data(), &AccountManager::profileChanged,
&sharedInstance, &AccountScriptingInterface::usernameChanged);
return &sharedInstance;
}

View file

@ -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();

View file

@ -12,8 +12,11 @@
#include "LoginDialog.h"
#include <QDesktopServices>
#include <QJsonDocument>
#include <QNetworkReply>
#include <NetworkingConstants.h>
#include <steamworks-wrapper/SteamClient.h>
#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<AccountManager>();
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<AccountManager>()->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<AccountManager>()->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>();
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>();
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<OffscreenUi>();
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());
}

View file

@ -16,35 +16,44 @@
#include <OffscreenQmlDialog.h>
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

View file

@ -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<QNetworkReply*>(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() {

View file

@ -96,6 +96,7 @@ public:
public slots:
void requestAccessToken(const QString& login, const QString& password);
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
void requestAccessTokenFinished();
void requestProfileFinished();

View file

@ -17,6 +17,7 @@
#include <QStringList>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <SettingHandle.h>
#include <UUID.h>
@ -46,7 +47,7 @@ bool AddressManager::isConnected() {
return DependencyManager::get<NodeList>()->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;

View file

@ -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; }

View file

@ -11,9 +11,303 @@
#include "SteamClient.h"
#include <atomic>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QMimeData>
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtGui/qevent.h>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
#endif
#include <steam/steam_api.h>
#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<PendingTicket> _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>() << 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);
}

View file

@ -13,8 +13,43 @@
#ifndef hifi_SteamClient_h
#define hifi_SteamClient_h
class SteamClient {
#include <functional>
#include <QtCore/QObject>
#include <QtCore/QByteArray>
using Ticket = QByteArray;
using TicketRequestCallback = std::function<void(Ticket)>;
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(); }
};

View file

@ -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);

View file

@ -34,6 +34,11 @@ using Lock = std::unique_lock<Mutex>;
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:

View file

@ -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<controller::UserInputMapper>();
handleOpenVrEvents();
if (openVrQuitRequested()) {
deactivate();
return;
}
// because update mutates the internal state we need to lock
userInputMapper->withLock([&, this]() {

View file

@ -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");

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#333333;}
</style>
<g id="Layer_2">
<g>
<g>
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
</g>
</g>
<g>
<g>
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
</g>
</g>
<g>
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
</g>
</g>
<path class="st3" d="M14.32,21.31c0.16,0.05,0.25,0.08,0.33,0.11c1.27,0.53,2.55,1.07,3.82,1.59c0.19,0.08,0.28,0.19,0.34,0.38
c0.49,1.6,1.94,2.57,3.55,2.37c1.59-0.19,2.8-1.51,2.89-3.16c0.01-0.22,0.08-0.35,0.25-0.47c1.17-0.83,2.33-1.67,3.5-2.51
c0.11-0.08,0.27-0.14,0.41-0.14c2.31-0.06,4.14-1.88,4.23-4.29c0.09-2.34-1.64-4.19-3.73-4.49c-2.59-0.36-4.9,1.56-5,4.17
c-0.01,0.14-0.05,0.3-0.13,0.41c-0.82,1.21-1.66,2.42-2.48,3.64c-0.11,0.16-0.22,0.22-0.41,0.23c-0.56,0.02-1.1,0.17-1.59,0.45
c-0.1,0.06-0.27,0.08-0.37,0.03c-1.91-0.79-3.81-1.59-5.72-2.37c-0.2-0.08-0.27-0.18-0.25-0.4c0.22-2.12,0.95-4.04,2.22-5.74
c1.96-2.62,4.57-4.26,7.83-4.6c5.01-0.53,8.91,1.43,11.48,5.76c3.52,5.94,0.91,13.76-5.42,16.52c-6.07,2.65-13.21-0.43-15.45-6.66
C14.51,21.89,14.43,21.64,14.32,21.31z"/>
<path class="st3" d="M29.28,18c-1.6,0-2.92-1.33-2.91-2.93c0.01-1.61,1.33-2.92,2.93-2.92c1.59,0.01,2.88,1.31,2.88,2.92
C32.18,16.69,30.88,18,29.28,18z M29.27,17.28c1.21,0,2.21-0.99,2.21-2.2c0.01-1.22-0.98-2.22-2.18-2.23c-1.21,0-2.2,0.98-2.21,2.2
C27.08,16.27,28.07,17.28,29.27,17.28z"/>
<path class="st3" d="M19.8,23.57c0.47,0.19,0.89,0.37,1.3,0.54c1.01,0.41,2.06,0,2.45-0.97c0.39-0.95-0.05-1.99-1.04-2.41
c-0.44-0.19-0.88-0.37-1.35-0.57c1.08-0.49,2.47,0.1,3.02,1.26c0.57,1.2,0.05,2.68-1.14,3.26C21.84,25.27,20.4,24.8,19.8,23.57z"/>
<g>
<path class="st3" d="M10.49,42.3v-6.38h1.24v6.38H10.49z"/>
<path class="st3" d="M14.53,38.2v4.1h-1.24V35.9h0.96l3.33,4.19v-4.19h1.24v6.38h-1.01L14.53,38.2z"/>
<path class="st3" d="M21.02,35.9l1.73,4.83l1.71-4.83h1.3l-2.49,6.39h-1.04l-2.51-6.39H21.02z"/>
<path class="st3" d="M26.67,42.3v-6.38h1.24v6.38H26.67z"/>
<path class="st3" d="M34.25,36.99h-2.03v5.3h-1.24v-5.3h-2.04V35.9h5.32V36.99z"/>
<path class="st3" d="M39.71,41.21v1.09h-4.44V35.9h4.36v1.09h-3.11v1.54h2.69v1.01h-2.69v1.67H39.71z"/>
</g>
<path class="st1" d="M14.32,71.31c0.16,0.05,0.25,0.08,0.33,0.11c1.27,0.53,2.55,1.07,3.82,1.59c0.19,0.08,0.28,0.19,0.34,0.38
c0.49,1.6,1.94,2.57,3.55,2.37c1.59-0.19,2.8-1.51,2.89-3.16c0.01-0.22,0.08-0.35,0.25-0.47c1.17-0.83,2.33-1.67,3.5-2.51
c0.11-0.08,0.27-0.14,0.41-0.14c2.31-0.06,4.14-1.88,4.23-4.29c0.09-2.34-1.64-4.19-3.73-4.49c-2.59-0.36-4.9,1.56-5,4.17
c-0.01,0.14-0.05,0.3-0.13,0.41c-0.82,1.21-1.66,2.42-2.48,3.64c-0.11,0.16-0.22,0.22-0.41,0.23c-0.56,0.02-1.1,0.17-1.59,0.45
c-0.1,0.06-0.27,0.08-0.37,0.03c-1.91-0.79-3.81-1.59-5.72-2.37c-0.2-0.08-0.27-0.18-0.25-0.4c0.22-2.12,0.95-4.04,2.22-5.74
c1.96-2.62,4.57-4.26,7.83-4.6c5.01-0.53,8.91,1.43,11.48,5.76c3.52,5.94,0.91,13.76-5.42,16.52c-6.07,2.65-13.21-0.43-15.45-6.66
C14.51,71.9,14.43,71.65,14.32,71.31z"/>
<path class="st1" d="M29.28,68c-1.6,0-2.92-1.33-2.91-2.93c0.01-1.61,1.33-2.92,2.93-2.92c1.59,0.01,2.88,1.31,2.88,2.92
C32.18,66.69,30.88,68,29.28,68z M29.27,67.29c1.21,0,2.21-0.99,2.21-2.2c0.01-1.22-0.98-2.22-2.18-2.23c-1.21,0-2.2,0.98-2.21,2.2
C27.08,66.28,28.07,67.28,29.27,67.29z"/>
<path class="st1" d="M19.8,73.58c0.47,0.19,0.89,0.37,1.3,0.54c1.01,0.41,2.06,0,2.45-0.97c0.39-0.95-0.05-1.99-1.04-2.41
c-0.44-0.19-0.88-0.37-1.35-0.57c1.08-0.49,2.47,0.1,3.02,1.26c0.57,1.2,0.05,2.68-1.14,3.26C21.84,75.27,20.4,74.8,19.8,73.58z"/>
<g>
<path class="st1" d="M10.49,92.3v-6.38h1.24v6.38H10.49z"/>
<path class="st1" d="M14.53,88.2v4.1h-1.24v-6.39h0.96l3.33,4.19v-4.19h1.24v6.38h-1.01L14.53,88.2z"/>
<path class="st1" d="M21.02,85.91l1.73,4.83l1.71-4.83h1.3l-2.49,6.39h-1.04l-2.51-6.39H21.02z"/>
<path class="st1" d="M26.67,92.3v-6.38h1.24v6.38H26.67z"/>
<path class="st1" d="M34.25,87h-2.03v5.3h-1.24V87h-2.04v-1.09h5.32V87z"/>
<path class="st1" d="M39.71,91.21v1.09h-4.44v-6.39h4.36V87h-3.11v1.54h2.69v1.01h-2.69v1.67H39.71z"/>
</g>
<path class="st1" d="M14.32,121.41c0.16,0.05,0.25,0.08,0.33,0.11c1.27,0.53,2.55,1.07,3.82,1.59c0.19,0.08,0.28,0.19,0.34,0.38
c0.49,1.6,1.94,2.57,3.55,2.37c1.59-0.19,2.8-1.51,2.89-3.16c0.01-0.22,0.08-0.35,0.25-0.47c1.17-0.83,2.33-1.67,3.5-2.51
c0.11-0.08,0.27-0.14,0.41-0.14c2.31-0.06,4.14-1.88,4.23-4.29c0.09-2.34-1.64-4.19-3.73-4.49c-2.59-0.36-4.9,1.56-5,4.17
c-0.01,0.14-0.05,0.3-0.13,0.41c-0.82,1.21-1.66,2.42-2.48,3.64c-0.11,0.16-0.22,0.22-0.41,0.23c-0.56,0.02-1.1,0.17-1.59,0.45
c-0.1,0.06-0.27,0.08-0.37,0.03c-1.91-0.79-3.81-1.59-5.72-2.37c-0.2-0.08-0.27-0.18-0.25-0.4c0.22-2.12,0.95-4.04,2.22-5.74
c1.96-2.62,4.57-4.26,7.83-4.6c5.01-0.53,8.91,1.43,11.48,5.76c3.52,5.94,0.91,13.76-5.42,16.52c-6.07,2.65-13.21-0.43-15.45-6.66
C14.51,122,14.43,121.75,14.32,121.41z"/>
<path class="st1" d="M29.28,118.1c-1.6,0-2.92-1.33-2.91-2.93c0.01-1.61,1.33-2.92,2.93-2.92c1.59,0.01,2.88,1.31,2.88,2.92
C32.18,116.79,30.88,118.1,29.28,118.1z M29.27,117.39c1.21,0,2.21-0.99,2.21-2.2c0.01-1.22-0.98-2.22-2.18-2.23
c-1.21,0-2.2,0.98-2.21,2.2C27.08,116.38,28.07,117.38,29.27,117.39z"/>
<path class="st1" d="M19.8,123.68c0.47,0.19,0.89,0.37,1.3,0.54c1.01,0.41,2.06,0,2.45-0.97c0.39-0.95-0.05-1.99-1.04-2.41
c-0.44-0.19-0.88-0.37-1.35-0.57c1.08-0.49,2.47,0.1,3.02,1.26c0.57,1.2,0.05,2.68-1.14,3.26C21.84,125.37,20.4,124.9,19.8,123.68z"
/>
<g>
<path class="st1" d="M10.49,142.4v-6.38h1.24v6.38H10.49z"/>
<path class="st1" d="M14.53,138.3v4.1h-1.24v-6.39h0.96l3.33,4.19v-4.19h1.24v6.38h-1.01L14.53,138.3z"/>
<path class="st1" d="M21.02,136.01l1.73,4.83l1.71-4.83h1.3l-2.49,6.39h-1.04l-2.51-6.39H21.02z"/>
<path class="st1" d="M26.67,142.4v-6.38h1.24v6.38H26.67z"/>
<path class="st1" d="M34.25,137.1h-2.03v5.3h-1.24v-5.3h-2.04v-1.09h5.32V137.1z"/>
<path class="st1" d="M39.71,141.31v1.09h-4.44v-6.39h4.36v1.09h-3.11v1.54h2.69v1.01h-2.69v1.67H39.71z"/>
</g>
<path class="st1" d="M14.32,171.51c0.16,0.05,0.25,0.08,0.33,0.11c1.27,0.53,2.55,1.07,3.82,1.59c0.19,0.08,0.28,0.19,0.34,0.38
c0.49,1.6,1.94,2.57,3.55,2.37c1.59-0.19,2.8-1.51,2.89-3.16c0.01-0.22,0.08-0.35,0.25-0.47c1.17-0.83,2.33-1.67,3.5-2.51
c0.11-0.08,0.27-0.14,0.41-0.14c2.31-0.06,4.14-1.88,4.23-4.29c0.09-2.34-1.64-4.19-3.73-4.49c-2.59-0.36-4.9,1.56-5,4.17
c-0.01,0.14-0.05,0.3-0.13,0.41c-0.82,1.21-1.66,2.42-2.48,3.64c-0.11,0.16-0.22,0.22-0.41,0.23c-0.56,0.02-1.1,0.17-1.59,0.45
c-0.1,0.06-0.27,0.08-0.37,0.03c-1.91-0.79-3.81-1.59-5.72-2.37c-0.2-0.08-0.27-0.18-0.25-0.4c0.22-2.12,0.95-4.04,2.22-5.74
c1.96-2.62,4.57-4.26,7.83-4.6c5.01-0.53,8.91,1.43,11.48,5.76c3.52,5.94,0.91,13.76-5.42,16.52c-6.07,2.65-13.21-0.43-15.45-6.66
C14.51,172.1,14.43,171.85,14.32,171.51z"/>
<path class="st1" d="M29.28,168.2c-1.6,0-2.92-1.33-2.91-2.93c0.01-1.61,1.33-2.92,2.93-2.92c1.59,0.01,2.88,1.31,2.88,2.92
C32.18,166.89,30.88,168.2,29.28,168.2z M29.27,167.49c1.21,0,2.21-0.99,2.21-2.2c0.01-1.22-0.98-2.22-2.18-2.23
c-1.21,0-2.2,0.98-2.21,2.2C27.08,166.48,28.07,167.48,29.27,167.49z"/>
<path class="st1" d="M19.8,173.78c0.47,0.19,0.89,0.37,1.3,0.54c1.01,0.41,2.06,0,2.45-0.97c0.39-0.95-0.05-1.99-1.04-2.41
c-0.44-0.19-0.88-0.37-1.35-0.57c1.08-0.49,2.47,0.1,3.02,1.26c0.57,1.2,0.05,2.68-1.14,3.26C21.84,175.47,20.4,175,19.8,173.78z"/>
<g>
<path class="st1" d="M10.49,192.5v-6.38h1.24v6.38H10.49z"/>
<path class="st1" d="M14.53,188.4v4.1h-1.24v-6.39h0.96l3.33,4.19v-4.19h1.24v6.38h-1.01L14.53,188.4z"/>
<path class="st1" d="M21.02,186.11l1.73,4.83l1.71-4.83h1.3l-2.49,6.39h-1.04l-2.51-6.39H21.02z"/>
<path class="st1" d="M26.67,192.5v-6.38h1.24v6.38H26.67z"/>
<path class="st1" d="M34.25,187.2h-2.03v5.3h-1.24v-5.3h-2.04v-1.09h5.32V187.2z"/>
<path class="st1" d="M39.71,191.41v1.09h-4.44v-6.39h4.36v1.09h-3.11v1.54h2.69v1.01h-2.69v1.67H39.71z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

31
scripts/system/steam.js Normal file
View file

@ -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");
});