Minimal working version for avatar's client

Also merge of Wayne's proof-of-concept
This commit is contained in:
Simon Walton 2019-04-24 17:42:51 -07:00
parent c03839e49f
commit de97af5c02
10 changed files with 481 additions and 29 deletions

View file

@ -368,9 +368,10 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
return;
}
bool sendIdentity = false;
MixerAvatar& avatar = nodeData->getAvatar();
bool sendIdentity = avatar.needsIdentityUpdate();
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
AvatarData& avatar = nodeData->getAvatar();
MixerAvatar& avatar = nodeData->getAvatar();
const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName();
if (!existingBaseDisplayName.isEmpty()) {
SessionDisplayName existingDisplayName { existingBaseDisplayName };
@ -415,10 +416,11 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
// since this packet includes a change to either the skeleton model URL or the display name
// it needs a new sequence number
nodeData->getAvatar().pushIdentitySequenceNumber();
avatar.pushIdentitySequenceNumber();
// tell node whose name changed about its new session display name or avatar.
sendIdentityPacket(nodeData, node);
avatar.clearIdentityUpdate();
}
}

View file

@ -15,6 +15,7 @@
#include <QJSonDocument>
#include <QNetworkReply>
#include <QCryptographicHash>
#include <QApplication>
#include <ResourceManager.h>
#include <NetworkAccessManager.h>
@ -78,6 +79,7 @@ void MixerAvatar::fstRequestComplete() {
} else {
_avatarFSTContents = fstRequest->getData();
_verifyState = kReceivedFST;
_pendingEvent = true;
}
_avatarRequest->deleteLater();
_avatarRequest = nullptr;
@ -156,12 +158,31 @@ QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
return jsonDocCertifiedItems.toJson(QJsonDocument::Compact);
}
void MixerAvatar::processCertifyEvents() {
void MixerAvatar::ownerRequestComplete() {
QMutexLocker certifyLocker(&_avatarCertifyLock);
if (_verifyState != kReceivedFST && _verifyState != kOwnerResponse && _verifyState != kChallengeResponse && _verifyState != kRequestingOwner) {
QNetworkReply* networkReply = static_cast<QNetworkReply*>(QObject::sender());
if (networkReply->error() == QNetworkReply::NoError) {
_dynamicMarketResponse = networkReply->readAll();
_verifyState = kOwnerResponse;
_pendingEvent = true;
} else {
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
<< jsonData.toObject()["message"].toString();
_verifyState = kError;
}
}
networkReply->deleteLater();
}
void MixerAvatar::processCertifyEvents() {
if (!_pendingEvent) {
return;
}
QMutexLocker certifyLocker(&_avatarCertifyLock);
switch (_verifyState) {
case kReceivedFST:
@ -185,24 +206,10 @@ void MixerAvatar::processCertifyEvents() {
request["certificate_id"] = _certificateIdFromFST;
_verifyState = kRequestingOwner;
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
//networkReply->setParent(this);
connect(networkReply, &QNetworkReply::readyRead, [this, networkReply]() {
QMutexLocker certifyLocker(&_avatarCertifyLock);
if (networkReply->error() == QNetworkReply::NoError) {
_dynamicMarketResponse = networkReply->readAll();
_verifyState = kOwnerResponse;
} else {
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
<< jsonData.toObject()["message"].toString();
_verifyState = kError;
}
}
networkReply->deleteLater();
});
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
} else {
_verifyState = kVerificationFailedPending;
_verifyState = kVerificationFailed;
_pendingEvent = false;
qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification";
}
break;
@ -244,6 +251,7 @@ void MixerAvatar::processCertifyEvents() {
"message:" << responseJson["message"].toString();
_verifyState = kError;
}
_pendingEvent = false;
break;
}
@ -266,19 +274,19 @@ void MixerAvatar::processCertifyEvents() {
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
QByteArray::fromBase64(signedNonce));
_verifyState = challengeResult ? kVerificationSucceeded : kVerificationFailedPending;
if (_verifyState == kVerificationFailedPending) {
_verifyState = challengeResult ? kVerificationSucceeded : kVerificationFailed;
_needsIdentityUpdate = true;
if (_verifyState == kVerificationFailed) {
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
} else {
qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID();
}
_pendingEvent = false;
break;
}
case kRequestingOwner:
{
certifyLocker.unlock();
{ // Qt networking done on this thread:
QCoreApplication::processEvents();
break;
}
@ -307,6 +315,7 @@ void MixerAvatar::sendOwnerChallenge() {
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
_verifyState = kVerificationFailed;
_needsIdentityUpdate = true;
});
}
@ -318,5 +327,6 @@ void MixerAvatar::handleChallengeResponse(ReceivedMessage * response) {
_challengeTimeout.stop();
_challengeResponse = response->readAll();
_verifyState = kChallengeResponse;
_pendingEvent = true;
}
}

View file

@ -25,7 +25,15 @@ public:
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
void fetchAvatarFST();
virtual bool isCertifyFailed() const override { return _verifyState == kVerificationFailed || _verifyState == kVerificationFailedPending; }
virtual bool isCertifyFailed() const override { return _verifyState == kVerificationFailed; }
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
void clearIdentityUpdate() { _needsIdentityUpdate = false; }
//bool isPendingCertifyFailed() const { return _verifyState == kVerificationFailedPending; }
//void advanceCertifyFailed() {
// if (isPendingCertifyFailed()) { _verifyState = kVerificationFailed; }
//}
void processCertifyEvents();
void handleChallengeResponse(ReceivedMessage * response);
@ -34,10 +42,11 @@ private:
// Avatar certification/verification:
enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse,
kChallengeClient, kChallengeResponse, kVerified, kVerificationFailedPending, kVerificationFailed,
kChallengeClient, kChallengeResponse, kVerified, kVerificationFailed,
kVerificationSucceeded, kError };
Q_ENUM(VerifyState);
VerifyState _verifyState { kNoncertified };
std::atomic<bool> _pendingEvent { false };
QMutex _avatarCertifyLock;
ResourceRequest* _avatarRequest { nullptr };
QString _marketplaceIdFromURL;
@ -51,6 +60,7 @@ private:
QByteArray _challengeNonceHash;
QByteArray _challengeResponse;
QTimer _challengeTimeout;
bool _needsIdentityUpdate { false };
bool generateFSTHash();
bool validateFSTHash(const QString& publicKey);
@ -59,6 +69,7 @@ private:
private slots:
void fstRequestComplete();
void ownerRequestComplete();
};
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -0,0 +1,70 @@
import QtQuick 2.7
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
Rectangle {
color: "black"
height: 480
width: 720
readonly property string avatarTheftEntityName: "hifi-avatar-theft-banner";
HifiStylesUit.RalewayRegular {
id: displayMessage;
text: "The avatar you're using is registered to another user.";
size: 20;
color: "white";
anchors.top: parent.top;
anchors.topMargin: 0.1 * parent.height;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
HifiStylesUit.ShortcutText {
id: gotoShortcut;
anchors.top: parent.top;
anchors.topMargin: 0.4 * parent.height;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
font.family: "Raleway"
font.pixelSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
text: "<a href='https://fake.link'>Click here to change your avatar and dismiss this banner.</a>"
onLinkActivated: {
AddressManager.handleLookupString("hifi://BodyMart");
}
}
HifiStylesUit.RalewayRegular {
id: contactText;
text: "If you own this avatar, please contact"
size: 20;
anchors.bottom: parent.bottom
anchors.bottomMargin: 0.15 * parent.height
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
}
HifiStylesUit.ShortcutText {
id: gotoShortcut;
anchors.top: contactText.bottom;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
font.family: "Raleway"
font.pixelSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
text: "<a href='mailto:support@highfidelity.com'>Click here to change your avatar and dismiss this banner.</a>"
onLinkActivated: {
HiFiAbout.openUrl("mailto:support@highfidelity.com");
}
}
}

View file

@ -0,0 +1,67 @@
import QtQuick 2.7
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
Rectangle {
color: "black"
HifiStylesUit.HifiConstants { id: hifi; }
HifiStylesUit.RalewayRegular {
id: displayMessage;
text: "The avatar you're using is registered to another user.";
size: 20;
color: "white";
anchors.top: parent.top;
anchors.topMargin: 0.1 * parent.height;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
HifiStylesUit.ShortcutText {
id: gotoShortcut;
anchors.top: parent.top;
anchors.topMargin: 0.4 * parent.height;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
font.family: "Raleway"
font.pixelSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
text: "<a href='https://fake.link'>Click here to change your avatar and dismiss this banner.</a>"
onLinkActivated: {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.loadQMLSource("hifi/AvatarApp.qml");
}
}
HifiStylesUit.RalewayRegular {
id: contactText;
text: "If you own this avatar, please contact"
size: 20;
color: "white"
anchors.bottom: parent.bottom
anchors.bottomMargin: 0.15 * parent.height
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
}
HifiStylesUit.ShortcutText {
id: email;
anchors.top: contactText.bottom;
anchors.left: parent.left;
anchors.leftMargin: (parent.width - width) / 2
font.family: "Raleway"
font.pixelSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
text: "<a href='mailto:support@highfidelity.com'>support@highfidelity.com.</a>"
}
}

View file

@ -0,0 +1,139 @@
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
Rectangle {
id: root;
HifiConstants { id: hifi; }
color: hifi.colors.baseGray;
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
function fromScript(message) {
}
RalewayRegular {
id: title;
color: hifi.colors.white;
text: qsTr("Avatar Theft Entity position")
size: 20
font.bold: true
anchors {
top: parent.top
left: parent.left
leftMargin: (parent.width - width) / 2
}
}
HifiControlsUit.Slider {
id: xSlider
anchors {
top: title.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "X OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: 0.0
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"x": value
});
}
}
HifiControlsUit.Slider {
id: ySlider
anchors {
top: xSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Y OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: 0.0
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"y": value
});
}
}
HifiControlsUit.Slider {
id: zSlider
anchors {
top: ySlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Z OFFSET: " + value.toFixed(2);
maximumValue: 0.0
minimumValue: -2.0
stepSize: 0.05
value: -1.0
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"z": value
});
}
}
HifiControlsUit.Button {
id: setVisibleButton;
text: setVisible ? "SET INVISIBLE" : "SET VISIBLE";
width: 300;
property bool setVisible: true;
anchors {
top: zSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
setVisible = !setVisible;
emitSendToScript({
"method": "setVisible",
"visible": setVisible
});
}
}
HifiControlsUit.Button {
id: printButton;
text: "PRINT POSITIONS";
width: 300;
anchors {
top: setVisibleButton.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
emitSendToScript({
"method": "print",
});
}
}
}

View file

@ -3368,6 +3368,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
if (setAdditionalContextProperties) {
qDebug() << "setting additional context properties!";
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();

View file

@ -46,6 +46,7 @@
#include "MyAvatar.h"
#include "DebugDraw.h"
#include "SceneScriptingInterface.h"
#include "ui/AvatarCertifyBanner.h"
// 50 times per second - target is 45hz, but this helps account for any small deviations
// in the update loop - this also results in ~30hz when in desktop mode which is essentially
@ -176,6 +177,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
_lastSendAvatarDataTime = now;
_myAvatarSendRate.increment();
}
static AvatarCertifyBanner theftBanner;
if (_myAvatar->isCertifyFailed()) {
theftBanner.show(_myAvatar->getSessionUUID(), _myAvatar->getJointIndex("_CAMERA_MATRIX"));
} else {
theftBanner.clear();
}
}

View file

@ -0,0 +1,144 @@
"use strict";
(function() {
var appUi = Script.require("appUi");
var ui;
var AVATAR_THEFT_BANNER_IMAGE = Script.resourcesPath() + "images/AvatarTheftBanner.png";
var AVATAR_THEFT_SETTINGS_QML = Script.resourcesPath() + "qml/AvatarTheftSettings.qml";
var button;
var theftBanner = null;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var onAvatarBanner = false;
var THEFT_BANNER_DIMENSIONS = {x: 1.0, y: 1.0, z: 0.3};
function createEntities() {
//if (HMD.active) {
var render = tablet.tabletShown ? "world" : "front";
var pos = tablet.tabletShown ? { x: 0.0, y: 0.0, z: -1.8 } : { x: 0.0, y: 0.0, z: -0.7 };
var dimensionMultiplier = tablet.tabletShown ? 2.6 : 1;
var props = {
type: "Image",
imageURL: AVATAR_THEFT_BANNER_IMAGE,
name: "hifi-avatar-theft-banner",
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
localPosition: pos,
dimensions: {x: THEFT_BANNER_DIMENSIONS.x * dimensionMultiplier, y: THEFT_BANNER_DIMENSIONS.y * dimensionMultiplier},
renderLayer: render,
userData: {
grabbable: false
},
visible: true
};
/* var props = {*/
//type: "Web",
//name: "hifi-avatar-theft-banner",
//sourceUrl: AVATAR_THEFT_BANNER_QML,
//parentID: MyAvatar.SELF_ID,
//parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
//localPosition: { x: 0.0, y: 0.0, z: -1.0 },
//dimensions: THEFT_BANNER_DIMENSIONS,
//drawInFront: true,
//userData: {
//grabbable: false
//},
//visible: true
/*};*/
if (theftBanner) {
Entities.deleteEntity(theftBanner);
}
theftBanner = Entities.addEntity(props, "local");
Window.copyToClipboard(theftBanner);
console.log("created entity");
//} else {
//}
}
function fromQml(message) {
if (message.method === "reposition") {
var theftBannerLocalPosition = Entities.getEntityProperties(theftBanner).localPosition;
var newTheftBannerLocalPosition;
if (message.x !== undefined) {
newTheftBannerLocalPosition = { x: -((THEFT_BANNER_DIMENSIONS.x) / 2) + message.x, y: theftBannerLocalPosition.y, z: theftBannerLocalPosition.z };
} else if (message.y !== undefined) {
newtheftBannerLocalPosition = { x: theftBannerLocalPosition.x, y: message.y, z: theftBannerLocalPosition.z };
} else if (message.z !== undefined) {
newtheftBannerLocalPosition = { x: theftBannerLocalPosition.x, y: theftBannerLocalPosition.y, z: message.z };
}
var theftBannerProps = {
localPosition: newtheftBannerLocalPosition
};
Entities.editEntity(theftBanner, theftBannerProps);
} else if (message.method === "setVisible") {
if (message.visible !== undefined) {
var props = {
visible: message.visible
};
Entities.editEntity(theftBannerEntity, props);
}
} else if (message.method === "print") {
// prints the local position into the hifi log.
var theftBannerLocalPosition = Entities.getEntityProperties(theftBannerEntity).localPosition;
console.log("theft banner local position is at " + JSON.stringify(theftBannerLocalPosition));
}
};
var cleanup = function () {
if (theftBanner) {
Entities.deleteEntity(theftBanner);
}
};
function setup() {
ui = new appUi({
buttonName: "THEFT",
home: AVATAR_THEFT_SETTINGS_QML,
onMessage: fromQml,
onOpened: createEntities,
onClosed: cleanup,
normalButton: "icons/tablet-icons/edit-i.svg",
activeButton: "icons/tablet-icons/edit-a.svg",
});
};
setup();
Entities.mousePressOnEntity.connect(function (entityID, event) {
if (entityID === theftBanner && theftBanner){
tablet.loadQMLSource(Script.resourcesPath() + "qml/hifi/AvatarApp.qml");
}
})
tablet.isTabletShownChanged.connect(function () {
if (theftBanner) {
if (tablet.tabletShown) {
Entities.editEntity(theftBanner, {
dimensions: THEFT_BANNER_DIMENSIONS,
localPosition: { x: 0.0, y: 0.0, z: -0.7 },
renderLayer: "world"
});
} else {
Entities.editEntity(theftBanner, {
localPosition: { x: 0.0, y: 0.0, z: -1.8 },
dimensions: {x: THEFT_BANNER_DIMENSIONS.x * 2.6, y: THEFT_BANNER_DIMENSIONS.y * 2.6},
renderLayer: "front"
});
}
console.log(Entities.getEntityProperties(theftBanner).renderLayer);
}
})
Script.scriptEnding.connect(cleanup);
location.hostChanged.connect(function (){
if (theftBanner) {
Entities.editEntity(theftBanner, {
visible: false
});
}
})
}());