mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 18:50:00 +02:00
Merge remote-tracking branch 'upstream/master' into recurse
This commit is contained in:
commit
3d048c77ba
28 changed files with 298 additions and 295 deletions
|
@ -53,6 +53,7 @@
|
||||||
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||||
|
|
||||||
#include "entities/AssignmentParentFinder.h"
|
#include "entities/AssignmentParentFinder.h"
|
||||||
|
#include "AssignmentDynamicFactory.h"
|
||||||
#include "RecordingScriptingInterface.h"
|
#include "RecordingScriptingInterface.h"
|
||||||
#include "AbstractAudioInterface.h"
|
#include "AbstractAudioInterface.h"
|
||||||
#include "AgentScriptingInterface.h"
|
#include "AgentScriptingInterface.h"
|
||||||
|
@ -67,6 +68,9 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
{
|
{
|
||||||
DependencyManager::set<ScriptableAvatar>();
|
DependencyManager::set<ScriptableAvatar>();
|
||||||
|
|
||||||
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::set<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
DependencyManager::set<AnimationCache>();
|
DependencyManager::set<AnimationCache>();
|
||||||
DependencyManager::set<AnimationCacheScriptingInterface>();
|
DependencyManager::set<AnimationCacheScriptingInterface>();
|
||||||
DependencyManager::set<EntityScriptingInterface>(false);
|
DependencyManager::set<EntityScriptingInterface>(false);
|
||||||
|
@ -860,6 +864,8 @@ void Agent::aboutToFinish() {
|
||||||
DependencyManager::destroy<recording::ClipCache>();
|
DependencyManager::destroy<recording::ClipCache>();
|
||||||
DependencyManager::destroy<ScriptEngine>();
|
DependencyManager::destroy<ScriptEngine>();
|
||||||
|
|
||||||
|
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
DependencyManager::destroy<ScriptableAvatar>();
|
DependencyManager::destroy<ScriptableAvatar>();
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||||
|
|
|
@ -541,7 +541,8 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMess
|
||||||
// ...For those nodes, reset the lastBroadcastTime to 0
|
// ...For those nodes, reset the lastBroadcastTime to 0
|
||||||
// so that the AvatarMixer will send Identity data to us
|
// so that the AvatarMixer will send Identity data to us
|
||||||
[&](const SharedNodePointer& node) {
|
[&](const SharedNodePointer& node) {
|
||||||
nodeData->setLastBroadcastTime(node->getUUID(), 0);
|
nodeData->setLastBroadcastTime(node->getUUID(), 0);
|
||||||
|
nodeData->resetSentTraitData(node->getLocalID());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -588,10 +589,10 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessa
|
||||||
QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) );
|
QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) );
|
||||||
if (!avatarID.isNull()) {
|
if (!avatarID.isNull()) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
auto node = nodeList->nodeWithUUID(avatarID);
|
auto requestedNode = nodeList->nodeWithUUID(avatarID);
|
||||||
if (node) {
|
|
||||||
QMutexLocker lock(&node->getMutex());
|
if (requestedNode) {
|
||||||
AvatarMixerClientData* avatarClientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
AvatarMixerClientData* avatarClientData = static_cast<AvatarMixerClientData*>(requestedNode->getLinkedData());
|
||||||
if (avatarClientData) {
|
if (avatarClientData) {
|
||||||
const AvatarData& avatarData = avatarClientData->getAvatar();
|
const AvatarData& avatarData = avatarClientData->getAvatar();
|
||||||
QByteArray serializedAvatar = avatarData.identityByteArray();
|
QByteArray serializedAvatar = avatarData.identityByteArray();
|
||||||
|
@ -600,6 +601,11 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessa
|
||||||
nodeList->sendPacketList(std::move(identityPackets), *senderNode);
|
nodeList->sendPacketList(std::move(identityPackets), *senderNode);
|
||||||
++_sumIdentityPackets;
|
++_sumIdentityPackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvatarMixerClientData* senderData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||||
|
if (senderData) {
|
||||||
|
senderData->resetSentTraitData(requestedNode->getLocalID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -625,23 +631,24 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
|
||||||
while (message->getBytesLeftToRead()) {
|
while (message->getBytesLeftToRead()) {
|
||||||
// parse out the UUID being ignored from the packet
|
// parse out the UUID being ignored from the packet
|
||||||
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
|
||||||
if (nodeList->nodeWithUUID(ignoredUUID)) {
|
if (ignoredNode) {
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
// Reset the lastBroadcastTime for the ignored avatar to 0
|
// Reset the lastBroadcastTime for the ignored avatar to 0
|
||||||
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
|
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
|
||||||
// to the ignorer if the ignorer unignores.
|
// to the ignorer if the ignorer unignores.
|
||||||
nodeData->setLastBroadcastTime(ignoredUUID, 0);
|
nodeData->setLastBroadcastTime(ignoredUUID, 0);
|
||||||
|
nodeData->resetSentTraitData(ignoredNode->getLocalID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
|
// Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
|
||||||
// so the AvatarMixer knows it'll have to send identity data about the ignorer
|
// so the AvatarMixer knows it'll have to send identity data about the ignorer
|
||||||
// to the ignored if the ignorer unignores.
|
// to the ignored if the ignorer unignores.
|
||||||
auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
|
|
||||||
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
|
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
|
||||||
if (ignoredNodeData) {
|
if (ignoredNodeData) {
|
||||||
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
|
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
|
||||||
|
ignoredNodeData->resetSentTraitData(senderNode->getLocalID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,6 +228,9 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
|
||||||
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
|
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
|
||||||
}
|
}
|
||||||
setLastBroadcastTime(other->getUUID(), 0);
|
setLastBroadcastTime(other->getUUID(), 0);
|
||||||
|
|
||||||
|
resetSentTraitData(other->getLocalID());
|
||||||
|
|
||||||
DependencyManager::get<NodeList>()->sendPacket(std::move(killPacket), *self);
|
DependencyManager::get<NodeList>()->sendPacket(std::move(killPacket), *self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +241,11 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) {
|
||||||
|
_lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp();
|
||||||
|
_sentTraitVersions[nodeLocalID].reset();
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
|
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
|
||||||
_currentViewFrustums.clear();
|
_currentViewFrustums.clear();
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,8 @@ public:
|
||||||
|
|
||||||
AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; }
|
AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; }
|
||||||
|
|
||||||
|
void resetSentTraitData(Node::LocalID nodeID);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
||||||
QWeakPointer<Node> node;
|
QWeakPointer<Node> node;
|
||||||
|
|
|
@ -164,7 +164,7 @@ bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
|
||||||
// Send EntityQueryInitialResultsComplete reliable packet ...
|
// Send EntityQueryInitialResultsComplete reliable packet ...
|
||||||
auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete,
|
auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete,
|
||||||
sizeof(OCTREE_PACKET_SEQUENCE), true);
|
sizeof(OCTREE_PACKET_SEQUENCE), true);
|
||||||
initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber() - 1U));
|
initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber()));
|
||||||
DependencyManager::get<NodeList>()->sendPacket(std::move(initialCompletion), *node);
|
DependencyManager::get<NodeList>()->sendPacket(std::move(initialCompletion), *node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <plugins/CodecPlugin.h>
|
#include <plugins/CodecPlugin.h>
|
||||||
#include <plugins/PluginManager.h>
|
#include <plugins/PluginManager.h>
|
||||||
#include <ResourceManager.h>
|
#include <ResourceManager.h>
|
||||||
|
#include <ResourceScriptingInterface.h>
|
||||||
#include <ScriptCache.h>
|
#include <ScriptCache.h>
|
||||||
#include <ScriptEngines.h>
|
#include <ScriptEngines.h>
|
||||||
#include <SoundCacheScriptingInterface.h>
|
#include <SoundCacheScriptingInterface.h>
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
|
|
||||||
#include <EntityScriptClient.h> // for EntityScriptServerServices
|
#include <EntityScriptClient.h> // for EntityScriptServerServices
|
||||||
|
|
||||||
|
#include "../AssignmentDynamicFactory.h"
|
||||||
#include "EntityScriptServerLogging.h"
|
#include "EntityScriptServerLogging.h"
|
||||||
#include "../entities/AssignmentParentFinder.h"
|
#include "../entities/AssignmentParentFinder.h"
|
||||||
|
|
||||||
|
@ -55,7 +57,11 @@ int EntityScriptServer::_entitiesScriptEngineCount = 0;
|
||||||
EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) {
|
EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) {
|
||||||
qInstallMessageHandler(messageHandler);
|
qInstallMessageHandler(messageHandler);
|
||||||
|
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::set<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
|
DependencyManager::set<EntityScriptingInterface>(false)->setPacketSender(&_entityEditSender);
|
||||||
|
DependencyManager::set<ResourceScriptingInterface>();
|
||||||
|
|
||||||
DependencyManager::set<ResourceManager>();
|
DependencyManager::set<ResourceManager>();
|
||||||
DependencyManager::set<PluginManager>();
|
DependencyManager::set<PluginManager>();
|
||||||
|
@ -455,8 +461,11 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
|
||||||
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
|
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
|
||||||
|
|
||||||
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
if (_entitiesScriptEngine) {
|
||||||
this, &EntityScriptServer::updateEntityPPS);
|
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
||||||
|
this, &EntityScriptServer::updateEntityPPS);
|
||||||
|
}
|
||||||
|
|
||||||
_entitiesScriptEngine.swap(newEngine);
|
_entitiesScriptEngine.swap(newEngine);
|
||||||
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
||||||
this, &EntityScriptServer::updateEntityPPS);
|
this, &EntityScriptServer::updateEntityPPS);
|
||||||
|
@ -487,6 +496,21 @@ void EntityScriptServer::shutdownScriptEngine() {
|
||||||
_shuttingDown = true;
|
_shuttingDown = true;
|
||||||
|
|
||||||
clear(); // always clear() on shutdown
|
clear(); // always clear() on shutdown
|
||||||
|
|
||||||
|
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||||
|
scriptEngines->shutdownScripting();
|
||||||
|
|
||||||
|
_entitiesScriptEngine.clear();
|
||||||
|
|
||||||
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||||
|
entityScriptingInterface->setEntityTree(nullptr);
|
||||||
|
|
||||||
|
// Should always be true as they are singletons.
|
||||||
|
if (entityScriptingInterface->getPacketSender() == &_entityEditSender) {
|
||||||
|
// The packet sender is about to go away.
|
||||||
|
entityScriptingInterface->setPacketSender(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptServer::addingEntity(const EntityItemID& entityID) {
|
void EntityScriptServer::addingEntity(const EntityItemID& entityID) {
|
||||||
|
@ -559,24 +583,19 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> mess
|
||||||
void EntityScriptServer::aboutToFinish() {
|
void EntityScriptServer::aboutToFinish() {
|
||||||
shutdownScriptEngine();
|
shutdownScriptEngine();
|
||||||
|
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
|
||||||
entityScriptingInterface->setEntityTree(nullptr);
|
|
||||||
|
|
||||||
// Should always be true as they are singletons.
|
|
||||||
if (entityScriptingInterface->getPacketSender() == &_entityEditSender) {
|
|
||||||
// The packet sender is about to go away.
|
|
||||||
entityScriptingInterface->setPacketSender(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
DependencyManager::destroy<AssignmentParentFinder>();
|
DependencyManager::destroy<AssignmentParentFinder>();
|
||||||
|
|
||||||
DependencyManager::get<ResourceManager>()->cleanup();
|
DependencyManager::get<ResourceManager>()->cleanup();
|
||||||
|
|
||||||
DependencyManager::destroy<PluginManager>();
|
DependencyManager::destroy<PluginManager>();
|
||||||
|
|
||||||
|
DependencyManager::destroy<ResourceScriptingInterface>();
|
||||||
|
DependencyManager::destroy<EntityScriptingInterface>();
|
||||||
|
|
||||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||||
DependencyManager::destroy<AudioInjectorManager>();
|
DependencyManager::destroy<AudioInjectorManager>();
|
||||||
|
|
||||||
DependencyManager::destroy<ScriptEngines>();
|
DependencyManager::destroy<ScriptEngines>();
|
||||||
DependencyManager::destroy<EntityScriptServerServices>();
|
DependencyManager::destroy<EntityScriptServerServices>();
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ Item {
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: usernameField
|
id: usernameField
|
||||||
|
text: Settings.getValue("wallet/savedUsername", "");
|
||||||
width: parent.width
|
width: parent.width
|
||||||
focus: true
|
focus: true
|
||||||
label: "Username or Email"
|
label: "Username or Email"
|
||||||
|
|
|
@ -127,10 +127,10 @@ Item {
|
||||||
StatText {
|
StatText {
|
||||||
visible: root.expanded
|
visible: root.expanded
|
||||||
text: "Intersection calls: Entities/Overlays/Avatars/HUD\n " +
|
text: "Intersection calls: Entities/Overlays/Avatars/HUD\n " +
|
||||||
root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "/" + root.stylusPicksUpdated.w + "\n " +
|
"Styluses:\t" + root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "/" + root.stylusPicksUpdated.w + "\n " +
|
||||||
root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "/" + root.rayPicksUpdated.w + "\n " +
|
"Rays:\t" + root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "/" + root.rayPicksUpdated.w + "\n " +
|
||||||
root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "/" + root.parabolaPicksUpdated.w + "\n " +
|
"Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "/" + root.parabolaPicksUpdated.w + "\n " +
|
||||||
root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z + "/" + root.collisionPicksUpdated.w
|
"Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z + "/" + root.collisionPicksUpdated.w
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,47 +63,6 @@ Item {
|
||||||
question: "How can I get HFC?";
|
question: "How can I get HFC?";
|
||||||
answer: "High Fidelity commerce is in open beta right now. Want more HFC? \
|
answer: "High Fidelity commerce is in open beta right now. Want more HFC? \
|
||||||
Get it by going to <br><br><b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity.</a></font></b> and meeting with the banker!";
|
Get it by going to <br><br><b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity.</a></font></b> and meeting with the banker!";
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
isExpanded: false;
|
|
||||||
question: "What are private keys and where are they stored?";
|
|
||||||
answer:
|
|
||||||
"A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \
|
|
||||||
In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \
|
|
||||||
After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \
|
|
||||||
Your hifikey file contains your private key and is protected by your wallet passphrase. \
|
|
||||||
<br><br>It is very important to back up your hifikey file! \
|
|
||||||
<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
isExpanded: false;
|
|
||||||
question: "How do I back up my private keys?";
|
|
||||||
answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \
|
|
||||||
Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \
|
|
||||||
Others with access to your back up should not be able to spend your HFC without your passphrase. \
|
|
||||||
<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>";
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
isExpanded: false;
|
|
||||||
question: "What happens if I lose my private keys?";
|
|
||||||
answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \
|
|
||||||
If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \
|
|
||||||
Here are some things to try:<ul>\
|
|
||||||
<li>If you have backed up your hifikey file before, search your backup location</li>\
|
|
||||||
<li>Search your AppData directory in the last machine you used to set up the Wallet</li>\
|
|
||||||
<li>If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder</li>\
|
|
||||||
</ul><br><br>As a last resort, you can set up your Wallet again and generate a new hifikey file. \
|
|
||||||
Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
isExpanded: false;
|
|
||||||
question: "What if I forget my wallet passphrase?";
|
|
||||||
answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \
|
|
||||||
<br><br>If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \
|
|
||||||
You will also no longer have access to the contents of your Wallet or My Purchases. \
|
|
||||||
For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \
|
|
||||||
<br><br>If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \
|
|
||||||
Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
|
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
isExpanded: false;
|
isExpanded: false;
|
||||||
|
@ -114,11 +73,9 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose
|
||||||
ListElement {
|
ListElement {
|
||||||
isExpanded: false;
|
isExpanded: false;
|
||||||
question: "What is a Security Pic?"
|
question: "What is a Security Pic?"
|
||||||
answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \
|
answer: "Your Security Pic acts as an extra layer of Wallet security. \
|
||||||
It acts as an extra layer of Wallet security. \
|
When you see your Security Pic, you know that your actions and data are securely making use of your account. \
|
||||||
When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\
|
<br><br><b><font color='#0093C5'><a href='#securitypic'>Tap here to change your Security Pic.</a></font></b>";
|
||||||
<br><br>Don't enter your passphrase anywhere that doesn't display your Security Pic! \
|
|
||||||
If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet.";
|
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
isExpanded: false;
|
isExpanded: false;
|
||||||
|
@ -260,6 +217,8 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta
|
||||||
}
|
}
|
||||||
} else if (link === "#support") {
|
} else if (link === "#support") {
|
||||||
Qt.openUrlExternally("mailto:support@highfidelity.com");
|
Qt.openUrlExternally("mailto:support@highfidelity.com");
|
||||||
|
} else if (link === "#securitypic") {
|
||||||
|
sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,76 +88,9 @@ Item {
|
||||||
color: hifi.colors.faintGray;
|
color: hifi.colors.faintGray;
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
id: changePassphraseContainer;
|
|
||||||
anchors.top: securityTextSeparator.bottom;
|
|
||||||
anchors.topMargin: 8;
|
|
||||||
anchors.left: parent.left;
|
|
||||||
anchors.leftMargin: 40;
|
|
||||||
anchors.right: parent.right;
|
|
||||||
anchors.rightMargin: 55;
|
|
||||||
height: 75;
|
|
||||||
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: changePassphraseImage;
|
|
||||||
text: hifi.glyphs.passphrase;
|
|
||||||
// Size
|
|
||||||
size: 80;
|
|
||||||
// Anchors
|
|
||||||
anchors.top: parent.top;
|
|
||||||
anchors.bottom: parent.bottom;
|
|
||||||
anchors.left: parent.left;
|
|
||||||
// Style
|
|
||||||
color: hifi.colors.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
RalewaySemiBold {
|
|
||||||
text: "Passphrase";
|
|
||||||
// Anchors
|
|
||||||
anchors.top: parent.top;
|
|
||||||
anchors.bottom: parent.bottom;
|
|
||||||
anchors.left: changePassphraseImage.right;
|
|
||||||
anchors.leftMargin: 30;
|
|
||||||
width: 50;
|
|
||||||
// Text size
|
|
||||||
size: 18;
|
|
||||||
// Style
|
|
||||||
color: hifi.colors.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Change Passphrase" button
|
|
||||||
HifiControlsUit.Button {
|
|
||||||
id: changePassphraseButton;
|
|
||||||
color: hifi.buttons.blue;
|
|
||||||
colorScheme: hifi.colorSchemes.dark;
|
|
||||||
anchors.right: parent.right;
|
|
||||||
anchors.verticalCenter: parent.verticalCenter;
|
|
||||||
width: 140;
|
|
||||||
height: 40;
|
|
||||||
text: "Change";
|
|
||||||
onClicked: {
|
|
||||||
sendSignalToWallet({method: 'walletSecurity_changePassphrase'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: changePassphraseSeparator;
|
|
||||||
// Size
|
|
||||||
width: parent.width;
|
|
||||||
height: 1;
|
|
||||||
// Anchors
|
|
||||||
anchors.left: parent.left;
|
|
||||||
anchors.right: parent.right;
|
|
||||||
anchors.top: changePassphraseContainer.bottom;
|
|
||||||
anchors.topMargin: 8;
|
|
||||||
// Style
|
|
||||||
color: hifi.colors.faintGray;
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: changeSecurityImageContainer;
|
id: changeSecurityImageContainer;
|
||||||
anchors.top: changePassphraseSeparator.bottom;
|
anchors.top: securityTextSeparator.bottom;
|
||||||
anchors.topMargin: 8;
|
anchors.topMargin: 8;
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
anchors.leftMargin: 40;
|
anchors.leftMargin: 40;
|
||||||
|
@ -208,139 +141,77 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: privateKeysSeparator;
|
id: autoLogoutContainer;
|
||||||
// Size
|
|
||||||
width: parent.width;
|
|
||||||
height: 1;
|
|
||||||
// Anchors
|
|
||||||
anchors.left: parent.left;
|
|
||||||
anchors.right: parent.right;
|
|
||||||
anchors.top: changeSecurityImageContainer.bottom;
|
anchors.top: changeSecurityImageContainer.bottom;
|
||||||
anchors.topMargin: 8;
|
anchors.topMargin: 8;
|
||||||
// Style
|
|
||||||
color: hifi.colors.faintGray;
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: yourPrivateKeysContainer;
|
|
||||||
anchors.top: privateKeysSeparator.bottom;
|
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
anchors.leftMargin: 40;
|
anchors.leftMargin: 40;
|
||||||
anchors.right: parent.right;
|
anchors.right: parent.right;
|
||||||
anchors.rightMargin: 55;
|
anchors.rightMargin: 55;
|
||||||
anchors.bottom: parent.bottom;
|
height: 75;
|
||||||
|
|
||||||
onVisibleChanged: {
|
HiFiGlyphs {
|
||||||
if (visible) {
|
id: autoLogoutImage;
|
||||||
Commerce.getKeyFilePathIfExists();
|
text: hifi.glyphs.walletKey;
|
||||||
}
|
// Size
|
||||||
|
size: 80;
|
||||||
|
// Anchors
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.topMargin: 20;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.white;
|
||||||
}
|
}
|
||||||
|
|
||||||
HiFiGlyphs {
|
HifiControlsUit.CheckBox {
|
||||||
id: yourPrivateKeysImage;
|
id: autoLogoutCheckbox;
|
||||||
text: hifi.glyphs.walletKey;
|
checked: Settings.getValue("wallet/autoLogout", false);
|
||||||
// Size
|
text: "Automatically Log Out when Exiting Interface"
|
||||||
size: 80;
|
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: parent.top;
|
anchors.verticalCenter: autoLogoutImage.verticalCenter;
|
||||||
anchors.topMargin: 20;
|
anchors.left: autoLogoutImage.right;
|
||||||
anchors.left: parent.left;
|
anchors.leftMargin: 20;
|
||||||
// Style
|
anchors.right: autoLogoutHelp.left;
|
||||||
|
anchors.rightMargin: 12;
|
||||||
|
boxSize: 28;
|
||||||
|
labelFontSize: 18;
|
||||||
color: hifi.colors.white;
|
color: hifi.colors.white;
|
||||||
|
onCheckedChanged: {
|
||||||
|
Settings.setValue("wallet/autoLogout", checked);
|
||||||
|
if (checked) {
|
||||||
|
Settings.setValue("wallet/savedUsername", Account.username);
|
||||||
|
} else {
|
||||||
|
Settings.setValue("wallet/savedUsername", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RalewaySemiBold {
|
RalewaySemiBold {
|
||||||
id: yourPrivateKeysText;
|
id: autoLogoutHelp;
|
||||||
text: "Private Keys";
|
text: '[?]';
|
||||||
size: 18;
|
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: parent.top;
|
anchors.verticalCenter: autoLogoutImage.verticalCenter;
|
||||||
anchors.topMargin: 32;
|
|
||||||
anchors.left: yourPrivateKeysImage.right;
|
|
||||||
anchors.leftMargin: 30;
|
|
||||||
anchors.right: parent.right;
|
anchors.right: parent.right;
|
||||||
|
width: 30;
|
||||||
height: 30;
|
height: 30;
|
||||||
// Style
|
|
||||||
color: hifi.colors.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text below "private keys"
|
|
||||||
RalewayRegular {
|
|
||||||
id: explanitoryText;
|
|
||||||
text: "Your money and purchases are secured with private keys that only you have access to.";
|
|
||||||
// Text size
|
// Text size
|
||||||
size: 18;
|
size: 18;
|
||||||
// Anchors
|
|
||||||
anchors.top: yourPrivateKeysText.bottom;
|
|
||||||
anchors.topMargin: 10;
|
|
||||||
anchors.left: yourPrivateKeysText.left;
|
|
||||||
anchors.right: yourPrivateKeysText.right;
|
|
||||||
height: paintedHeight;
|
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.white;
|
color: hifi.colors.blueHighlight;
|
||||||
wrapMode: Text.WordWrap;
|
|
||||||
// Alignment
|
|
||||||
horizontalAlignment: Text.AlignLeft;
|
|
||||||
verticalAlignment: Text.AlignVCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: removeHmdContainer;
|
|
||||||
z: 998;
|
|
||||||
visible: false;
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
GradientStop {
|
|
||||||
position: 0.2;
|
|
||||||
color: hifi.colors.baseGrayHighlight;
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 1.0;
|
|
||||||
color: hifi.colors.baseGrayShadow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anchors.fill: backupInstructionsButton;
|
|
||||||
radius: 5;
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
propagateComposedEvents: false;
|
|
||||||
hoverEnabled: true;
|
hoverEnabled: true;
|
||||||
}
|
onEntered: {
|
||||||
|
parent.color = hifi.colors.blueAccent;
|
||||||
RalewayBold {
|
}
|
||||||
anchors.fill: parent;
|
onExited: {
|
||||||
text: "INSTRUCTIONS OPEN ON DESKTOP";
|
parent.color = hifi.colors.blueHighlight;
|
||||||
size: 15;
|
}
|
||||||
color: hifi.colors.white;
|
onClicked: {
|
||||||
verticalAlignment: Text.AlignVCenter;
|
sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'});
|
||||||
horizontalAlignment: Text.AlignHCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: removeHmdContainerTimer;
|
|
||||||
interval: 5000;
|
|
||||||
onTriggered: removeHmdContainer.visible = false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
HifiControlsUit.Button {
|
|
||||||
id: backupInstructionsButton;
|
|
||||||
text: "View Backup Instructions";
|
|
||||||
color: hifi.buttons.blue;
|
|
||||||
colorScheme: hifi.colorSchemes.dark;
|
|
||||||
anchors.left: explanitoryText.left;
|
|
||||||
anchors.right: explanitoryText.right;
|
|
||||||
anchors.top: explanitoryText.bottom;
|
|
||||||
anchors.topMargin: 16;
|
|
||||||
height: 40;
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'));
|
|
||||||
Qt.openUrlExternally(keyPath + "/backup_instructions.html");
|
|
||||||
Qt.openUrlExternally(keyPath);
|
|
||||||
removeHmdContainer.visible = true;
|
|
||||||
removeHmdContainerTimer.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,6 +382,17 @@ Rectangle {
|
||||||
} else if (msg.method === 'walletSecurity_changeSecurityImage') {
|
} else if (msg.method === 'walletSecurity_changeSecurityImage') {
|
||||||
securityImageChange.initModel();
|
securityImageChange.initModel();
|
||||||
root.activeView = "securityImageChange";
|
root.activeView = "securityImageChange";
|
||||||
|
} else if (msg.method === 'walletSecurity_autoLogoutHelp') {
|
||||||
|
lightboxPopup.titleText = "Automatically Log Out";
|
||||||
|
lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " +
|
||||||
|
"account even after you close and re-open Interface. This means anyone who opens Interface on your computer " +
|
||||||
|
"could make purchases with your Wallet.\n\n" +
|
||||||
|
"If you do not want to stay logged in across Interface sessions, check this box.";
|
||||||
|
lightboxPopup.button1text = "CLOSE";
|
||||||
|
lightboxPopup.button1method = function() {
|
||||||
|
lightboxPopup.visible = false;
|
||||||
|
}
|
||||||
|
lightboxPopup.visible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,6 +410,9 @@ Rectangle {
|
||||||
onSendSignalToWallet: {
|
onSendSignalToWallet: {
|
||||||
if (msg.method === 'walletReset' || msg.method === 'passphraseReset') {
|
if (msg.method === 'walletReset' || msg.method === 'passphraseReset') {
|
||||||
sendToScript(msg);
|
sendToScript(msg);
|
||||||
|
} else if (msg.method === 'walletSecurity_changeSecurityImage') {
|
||||||
|
securityImageChange.initModel();
|
||||||
|
root.activeView = "securityImageChange";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,12 +817,24 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
function walletResetSetup() {
|
function walletResetSetup() {
|
||||||
|
/* Bypass all this and do it automatically
|
||||||
root.activeView = "walletSetup";
|
root.activeView = "walletSetup";
|
||||||
var timestamp = new Date();
|
var timestamp = new Date();
|
||||||
walletSetup.startingTimestamp = timestamp;
|
walletSetup.startingTimestamp = timestamp;
|
||||||
walletSetup.setupAttemptID = generateUUID();
|
walletSetup.setupAttemptID = generateUUID();
|
||||||
UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app",
|
UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app",
|
||||||
(AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''));
|
(AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''));
|
||||||
|
*/
|
||||||
|
|
||||||
|
var randomNumber = Math.floor(Math.random() * 34) + 1;
|
||||||
|
var securityImagePath = "images/" + addLeadingZero(randomNumber) + ".jpg";
|
||||||
|
Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set.
|
||||||
|
Commerce.chooseSecurityImage(securityImagePath);
|
||||||
|
Commerce.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLeadingZero(n) {
|
||||||
|
return n < 10 ? '0' + n : '' + n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function followReferrer(msg) {
|
function followReferrer(msg) {
|
||||||
|
|
|
@ -375,6 +375,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
|
||||||
static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
||||||
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
|
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
|
||||||
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||||
|
static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout";
|
||||||
|
|
||||||
const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
|
const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
|
||||||
{ SVO_EXTENSION, &Application::importSVOFromURL },
|
{ SVO_EXTENSION, &Application::importSVOFromURL },
|
||||||
|
@ -1730,6 +1731,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
QTimer* settingsTimer = new QTimer();
|
QTimer* settingsTimer = new QTimer();
|
||||||
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
||||||
connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
|
connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
|
||||||
|
bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
|
||||||
|
if (autoLogout) {
|
||||||
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
accountManager->logout();
|
||||||
|
}
|
||||||
// Disconnect the signal from the save settings
|
// Disconnect the signal from the save settings
|
||||||
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||||
// Stop the settings timer
|
// Stop the settings timer
|
||||||
|
@ -3659,7 +3665,7 @@ bool Application::event(QEvent* event) {
|
||||||
|
|
||||||
bool Application::eventFilter(QObject* object, QEvent* event) {
|
bool Application::eventFilter(QObject* object, QEvent* event) {
|
||||||
|
|
||||||
if (_aboutToQuit) {
|
if (_aboutToQuit && event->type() != QEvent::DeferredDelete && event->type() != QEvent::Destroy) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
||||||
_eyeContactTarget(LEFT_EYE),
|
_eyeContactTarget(LEFT_EYE),
|
||||||
_realWorldFieldOfView("realWorldFieldOfView",
|
_realWorldFieldOfView("realWorldFieldOfView",
|
||||||
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
||||||
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false),
|
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", true),
|
||||||
_smoothOrientationTimer(std::numeric_limits<float>::max()),
|
_smoothOrientationTimer(std::numeric_limits<float>::max()),
|
||||||
_smoothOrientationInitial(),
|
_smoothOrientationInitial(),
|
||||||
_smoothOrientationTarget(),
|
_smoothOrientationTarget(),
|
||||||
|
@ -203,6 +203,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
||||||
|
|
||||||
connect(recorder.data(), &Recorder::recordingStateChanged, [=] {
|
connect(recorder.data(), &Recorder::recordingStateChanged, [=] {
|
||||||
if (recorder->isRecording()) {
|
if (recorder->isRecording()) {
|
||||||
|
createRecordingIDs();
|
||||||
setRecordingBasis();
|
setRecordingBasis();
|
||||||
} else {
|
} else {
|
||||||
clearRecordingBasis();
|
clearRecordingBasis();
|
||||||
|
@ -444,7 +445,6 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::update(float deltaTime) {
|
void MyAvatar::update(float deltaTime) {
|
||||||
|
|
||||||
// update moving average of HMD facing in xz plane.
|
// update moving average of HMD facing in xz plane.
|
||||||
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
|
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,9 @@
|
||||||
QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) {
|
QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) {
|
||||||
QByteArray response = reply->readAll();
|
QByteArray response = reply->readAll();
|
||||||
QJsonObject data = QJsonDocument::fromJson(response).object();
|
QJsonObject data = QJsonDocument::fromJson(response).object();
|
||||||
|
#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy.
|
||||||
qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
|
qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
|
||||||
|
#endif
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
// Non-200 responses are not json:
|
// Non-200 responses are not json:
|
||||||
|
@ -69,7 +71,9 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
const QString URL = "/api/v1/commerce/";
|
const QString URL = "/api/v1/commerce/";
|
||||||
JSONCallbackParameters callbackParams(this, success, fail);
|
JSONCallbackParameters callbackParams(this, success, fail);
|
||||||
|
#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy.
|
||||||
qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact);
|
qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact);
|
||||||
|
#endif
|
||||||
accountManager->sendRequest(URL + endpoint,
|
accountManager->sendRequest(URL + endpoint,
|
||||||
authType,
|
authType,
|
||||||
method,
|
method,
|
||||||
|
@ -117,7 +121,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
||||||
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) {
|
bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker) {
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
if (!accountManager->isLoggedIn()) {
|
if (!accountManager->isLoggedIn()) {
|
||||||
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
||||||
|
@ -125,11 +129,25 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) {
|
||||||
emit receiveAtResult(result);
|
emit receiveAtResult(result);
|
||||||
return false; // We know right away that we will fail, so tell the caller.
|
return false; // We know right away that we will fail, so tell the caller.
|
||||||
}
|
}
|
||||||
|
QJsonObject transaction;
|
||||||
signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
transaction["public_key"] = hfc_key;
|
||||||
|
transaction["locker"] = QString::fromUtf8(locker);
|
||||||
|
QJsonDocument transactionDoc{ transaction };
|
||||||
|
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||||
|
signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||||
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Ledger::receiveAt() {
|
||||||
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
|
auto keys = wallet->listPublicKeys();
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto key = keys.first();
|
||||||
|
return receiveAt(key, key, wallet->getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
void Ledger::balance(const QStringList& keys) {
|
void Ledger::balance(const QStringList& keys) {
|
||||||
keysQuery("balance", "balanceSuccess", "balanceFailure");
|
keysQuery("balance", "balanceSuccess", "balanceFailure");
|
||||||
}
|
}
|
||||||
|
@ -283,24 +301,30 @@ void Ledger::accountSuccess(QNetworkReply* reply) {
|
||||||
auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
|
auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
|
||||||
auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
|
auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
|
||||||
QString remotePublicKey = data["public_key"].toString();
|
QString remotePublicKey = data["public_key"].toString();
|
||||||
|
const QByteArray locker = data["locker"].toString().toUtf8();
|
||||||
bool isOverride = wallet->wasSoftReset();
|
bool isOverride = wallet->wasSoftReset();
|
||||||
|
|
||||||
wallet->setSalt(salt);
|
wallet->setSalt(salt);
|
||||||
wallet->setIv(iv);
|
wallet->setIv(iv);
|
||||||
wallet->setCKey(ckey);
|
wallet->setCKey(ckey);
|
||||||
|
if (!locker.isEmpty()) {
|
||||||
|
wallet->setWallet(locker);
|
||||||
|
wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth.
|
||||||
|
}
|
||||||
|
|
||||||
QString keyStatus = "ok";
|
QString keyStatus = "ok";
|
||||||
QStringList localPublicKeys = wallet->listPublicKeys();
|
QStringList localPublicKeys = wallet->listPublicKeys();
|
||||||
if (remotePublicKey.isEmpty() || isOverride) {
|
if (remotePublicKey.isEmpty() || isOverride) {
|
||||||
if (!localPublicKeys.isEmpty()) {
|
if (!localPublicKeys.isEmpty()) { // Let the metaverse know about a local wallet.
|
||||||
QString key = localPublicKeys.first();
|
receiveAt();
|
||||||
receiveAt(key, key);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (localPublicKeys.isEmpty()) {
|
if (localPublicKeys.isEmpty()) {
|
||||||
keyStatus = "preexisting";
|
keyStatus = "preexisting";
|
||||||
} else if (localPublicKeys.first() != remotePublicKey) {
|
} else if (localPublicKeys.first() != remotePublicKey) {
|
||||||
keyStatus = "conflicting";
|
keyStatus = "conflicting";
|
||||||
|
} else if (locker.isEmpty()) { // Matches metaverse data, but we haven't lockered it yet.
|
||||||
|
receiveAt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ class Ledger : public QObject, public Dependency {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||||
bool receiveAt(const QString& hfc_key, const QString& signing_key);
|
bool receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker);
|
||||||
|
bool receiveAt();
|
||||||
void balance(const QStringList& keys);
|
void balance(const QStringList& keys);
|
||||||
void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
|
void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
|
||||||
void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
|
void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
|
||||||
|
|
|
@ -131,7 +131,7 @@ bool Wallet::writeBackupInstructions() {
|
||||||
QFile outputFile(outputFilename);
|
QFile outputFile(outputFilename);
|
||||||
bool retval = false;
|
bool retval = false;
|
||||||
|
|
||||||
if (getKeyFilePath() == "")
|
if (getKeyFilePath().isEmpty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,30 @@ bool writeKeys(const char* filename, EC_KEY* keys) {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::setWallet(const QByteArray& wallet) {
|
||||||
|
QFile file(keyFilePath());
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.write(wallet) != wallet.count()) {
|
||||||
|
qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QByteArray Wallet::getWallet() {
|
||||||
|
QFile file(keyFilePath());
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qCInfo(commerce) << "No existing wallet in" << keyFilePath();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
QByteArray wallet = file.readAll();
|
||||||
|
file.close();
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
||||||
|
|
||||||
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
|
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||||
|
@ -334,7 +358,7 @@ Wallet::Wallet() {
|
||||||
uint status;
|
uint status;
|
||||||
QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : "";
|
QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : "";
|
||||||
|
|
||||||
if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) {
|
if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) {
|
||||||
if (keyStatus == "preexisting") {
|
if (keyStatus == "preexisting") {
|
||||||
status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING;
|
status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING;
|
||||||
} else{
|
} else{
|
||||||
|
@ -524,15 +548,23 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
|
||||||
|
|
||||||
// FIXME: initialize OpenSSL elsewhere soon
|
// FIXME: initialize OpenSSL elsewhere soon
|
||||||
initialize();
|
initialize();
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: checking" << (!_passphrase || !_passphrase->isEmpty());
|
||||||
|
|
||||||
// this should always be false if we don't have a passphrase
|
// this should always be false if we don't have a passphrase
|
||||||
// cached yet
|
// cached yet
|
||||||
if (!_passphrase || _passphrase->isEmpty()) {
|
if (!_passphrase || _passphrase->isEmpty()) {
|
||||||
return false;
|
if (!getKeyFilePath().isEmpty()) { // If file exists, then it is an old school file that has not been lockered. Must get user's passphrase.
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: No passphrase, but there is an existing wallet.";
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: New setup.";
|
||||||
|
setPassphrase("ACCOUNT"); // Going forward, consider this an account-based client.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_publicKeys.count() > 0) {
|
if (_publicKeys.count() > 0) {
|
||||||
// we _must_ be authenticated if the publicKeys are there
|
// we _must_ be authenticated if the publicKeys are there
|
||||||
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
|
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet was ready";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,10 +577,15 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
|
||||||
|
|
||||||
// be sure to add the public key so we don't do this over and over
|
// be sure to add the public key so we don't do this over and over
|
||||||
_publicKeys.push_back(publicKey.toBase64());
|
_publicKeys.push_back(publicKey.toBase64());
|
||||||
|
|
||||||
|
if (*_passphrase != "ACCOUNT") {
|
||||||
|
changePassphrase("ACCOUNT"); // Rewrites with salt and constant, and will be lockered that way.
|
||||||
|
}
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet now ready";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet not ready";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,6 +596,7 @@ bool Wallet::generateKeyPair() {
|
||||||
qCInfo(commerce) << "Generating keypair.";
|
qCInfo(commerce) << "Generating keypair.";
|
||||||
auto keyPair = generateECKeypair();
|
auto keyPair = generateECKeypair();
|
||||||
if (!keyPair.first) {
|
if (!keyPair.first) {
|
||||||
|
qCWarning(commerce) << "Empty keypair";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,7 +614,7 @@ bool Wallet::generateKeyPair() {
|
||||||
// 2. It is maximally private, and we can step back from that later if desired.
|
// 2. It is maximally private, and we can step back from that later if desired.
|
||||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
return ledger->receiveAt(key, key);
|
return ledger->receiveAt(key, key, getWallet());
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Wallet::listPublicKeys() {
|
QStringList Wallet::listPublicKeys() {
|
||||||
|
@ -666,11 +704,13 @@ void Wallet::chooseSecurityImage(const QString& filename) {
|
||||||
// there _is_ a keyfile, we need to update it (similar to changing the
|
// there _is_ a keyfile, we need to update it (similar to changing the
|
||||||
// passphrase, we need to do so into a temp file and move it).
|
// passphrase, we need to do so into a temp file and move it).
|
||||||
if (!QFile(keyFilePath()).exists()) {
|
if (!QFile(keyFilePath()).exists()) {
|
||||||
|
qCDebug(commerce) << "initial security pic set for empty wallet";
|
||||||
emit securityImageResult(true);
|
emit securityImageResult(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = writeWallet();
|
bool success = writeWallet();
|
||||||
|
qCDebug(commerce) << "updated security pic" << success;
|
||||||
emit securityImageResult(success);
|
emit securityImageResult(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,6 +755,11 @@ QString Wallet::getKeyFilePath() {
|
||||||
|
|
||||||
bool Wallet::writeWallet(const QString& newPassphrase) {
|
bool Wallet::writeWallet(const QString& newPassphrase) {
|
||||||
EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
|
EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
|
||||||
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
|
// Remove any existing locker, because it will be out of date.
|
||||||
|
if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) {
|
||||||
|
return false; // FIXME: receiveAt could fail asynchronously.
|
||||||
|
}
|
||||||
if (keys) {
|
if (keys) {
|
||||||
// we read successfully, so now write to a new temp file
|
// we read successfully, so now write to a new temp file
|
||||||
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
|
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
|
||||||
|
@ -722,6 +767,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) {
|
||||||
if (!newPassphrase.isEmpty()) {
|
if (!newPassphrase.isEmpty()) {
|
||||||
setPassphrase(newPassphrase);
|
setPassphrase(newPassphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
|
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
|
||||||
if (writeSecurityImage(_securityImage, tempFileName)) {
|
if (writeSecurityImage(_securityImage, tempFileName)) {
|
||||||
// ok, now move the temp file to the correct spot
|
// ok, now move the temp file to the correct spot
|
||||||
|
@ -729,6 +775,11 @@ bool Wallet::writeWallet(const QString& newPassphrase) {
|
||||||
QFile(tempFileName).rename(QString(keyFilePath()));
|
QFile(tempFileName).rename(QString(keyFilePath()));
|
||||||
qCDebug(commerce) << "wallet written successfully";
|
qCDebug(commerce) << "wallet written successfully";
|
||||||
emit keyFilePathIfExistsResult(getKeyFilePath());
|
emit keyFilePathIfExistsResult(getKeyFilePath());
|
||||||
|
if (!walletIsAuthenticatedWithPassphrase() || !ledger->receiveAt()) {
|
||||||
|
// FIXME: Should we fail the whole operation?
|
||||||
|
// Tricky, because we'll need the the key and file from the TEMP location...
|
||||||
|
qCWarning(commerce) << "Failed to update locker";
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
qCDebug(commerce) << "couldn't write security image to temp wallet";
|
qCDebug(commerce) << "couldn't write security image to temp wallet";
|
||||||
|
|
|
@ -73,6 +73,7 @@ private slots:
|
||||||
void handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
void handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class Ledger;
|
||||||
QStringList _publicKeys{};
|
QStringList _publicKeys{};
|
||||||
QPixmap* _securityImage { nullptr };
|
QPixmap* _securityImage { nullptr };
|
||||||
QByteArray _salt;
|
QByteArray _salt;
|
||||||
|
@ -87,6 +88,9 @@ private:
|
||||||
bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
|
bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
|
||||||
bool writeBackupInstructions();
|
bool writeBackupInstructions();
|
||||||
|
|
||||||
|
bool setWallet(const QByteArray& wallet);
|
||||||
|
QByteArray getWallet();
|
||||||
|
|
||||||
void account();
|
void account();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -122,11 +122,11 @@ bool SafeLanding::isSequenceNumbersComplete() {
|
||||||
int sequenceSize = _initialStart <= _initialEnd ? _initialEnd - _initialStart:
|
int sequenceSize = _initialStart <= _initialEnd ? _initialEnd - _initialStart:
|
||||||
_initialEnd + SEQUENCE_MODULO - _initialStart;
|
_initialEnd + SEQUENCE_MODULO - _initialStart;
|
||||||
auto startIter = _sequenceNumbers.find(_initialStart);
|
auto startIter = _sequenceNumbers.find(_initialStart);
|
||||||
auto endIter = _sequenceNumbers.find(_initialEnd);
|
auto endIter = _sequenceNumbers.find(_initialEnd - 1);
|
||||||
if (sequenceSize == 0 ||
|
if (sequenceSize == 0 ||
|
||||||
(startIter != _sequenceNumbers.end()
|
(startIter != _sequenceNumbers.end()
|
||||||
&& endIter != _sequenceNumbers.end()
|
&& endIter != _sequenceNumbers.end()
|
||||||
&& distance(startIter, endIter) == sequenceSize) ) {
|
&& distance(startIter, endIter) == sequenceSize - 1) ) {
|
||||||
_trackingEntities = false; // Don't track anything else that comes in.
|
_trackingEntities = false; // Don't track anything else that comes in.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class SafeLanding : public QObject {
|
||||||
public:
|
public:
|
||||||
void startEntitySequence(QSharedPointer<EntityTreeRenderer> entityTreeRenderer);
|
void startEntitySequence(QSharedPointer<EntityTreeRenderer> entityTreeRenderer);
|
||||||
void stopEntitySequence();
|
void stopEntitySequence();
|
||||||
void setCompletionSequenceNumbers(int first, int last);
|
void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive.
|
||||||
void noteReceivedsequenceNumber(int sequenceNumber);
|
void noteReceivedsequenceNumber(int sequenceNumber);
|
||||||
bool isLoadSequenceComplete();
|
bool isLoadSequenceComplete();
|
||||||
|
|
||||||
|
|
|
@ -272,7 +272,7 @@ void setupPreferences() {
|
||||||
auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); };
|
auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); };
|
||||||
auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
|
auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
|
||||||
preferences->addPreference(new CheckPreference(VR_MOVEMENT,
|
preferences->addPreference(new CheckPreference(VR_MOVEMENT,
|
||||||
QStringLiteral("Advanced movement for hand controllers"),
|
QStringLiteral("Advanced movement in VR (Teleport movement when unchecked)"),
|
||||||
getter, setter));
|
getter, setter));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
@ -376,6 +376,9 @@ void Avatar::updateAvatarEntities() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (avatarEntities.size() != _avatarEntityForRecording.size()) {
|
||||||
|
createRecordingIDs();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setAvatarEntityDataChanged(false);
|
setAvatarEntityDataChanged(false);
|
||||||
|
|
|
@ -2308,6 +2308,15 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) {
|
||||||
_recordingBasis = recordingBasis;
|
_recordingBasis = recordingBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarData::createRecordingIDs() {
|
||||||
|
_avatarEntitiesLock.withReadLock([&] {
|
||||||
|
_avatarEntityForRecording.clear();
|
||||||
|
for (int i = 0; i < _avatarEntityData.size(); i++) {
|
||||||
|
_avatarEntityForRecording.insert(QUuid::createUuid());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarData::clearRecordingBasis() {
|
void AvatarData::clearRecordingBasis() {
|
||||||
_recordingBasis.reset();
|
_recordingBasis.reset();
|
||||||
}
|
}
|
||||||
|
@ -2368,21 +2377,15 @@ QJsonObject AvatarData::toJson() const {
|
||||||
if (!getDisplayName().isEmpty()) {
|
if (!getDisplayName().isEmpty()) {
|
||||||
root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName();
|
root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName();
|
||||||
}
|
}
|
||||||
if (!getAttachmentData().isEmpty()) {
|
|
||||||
QJsonArray attachmentsJson;
|
|
||||||
for (auto attachment : getAttachmentData()) {
|
|
||||||
attachmentsJson.push_back(attachment.toJson());
|
|
||||||
}
|
|
||||||
root[JSON_AVATAR_ATTACHMENTS] = attachmentsJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
_avatarEntitiesLock.withReadLock([&] {
|
||||||
if (!_avatarEntityData.empty()) {
|
if (!_avatarEntityData.empty()) {
|
||||||
QJsonArray avatarEntityJson;
|
QJsonArray avatarEntityJson;
|
||||||
|
int entityCount = 0;
|
||||||
for (auto entityID : _avatarEntityData.keys()) {
|
for (auto entityID : _avatarEntityData.keys()) {
|
||||||
QVariantMap entityData;
|
QVariantMap entityData;
|
||||||
entityData.insert("id", entityID);
|
QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID;
|
||||||
entityData.insert("properties", _avatarEntityData.value(entityID));
|
entityData.insert("id", newId);
|
||||||
|
entityData.insert("properties", _avatarEntityData.value(entityID).toBase64());
|
||||||
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
|
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
|
||||||
}
|
}
|
||||||
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
|
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
|
||||||
|
@ -2504,12 +2507,17 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
||||||
setAttachmentData(attachments);
|
setAttachmentData(attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
|
if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
|
||||||
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray();
|
||||||
// for (auto attachmentJson : attachmentsJson) {
|
for (auto attachmentJson : attachmentsJson) {
|
||||||
// // TODO -- something
|
if (attachmentJson.isObject()) {
|
||||||
// }
|
QVariantMap entityData = attachmentJson.toObject().toVariantMap();
|
||||||
// }
|
QUuid entityID = entityData.value("id").toUuid();
|
||||||
|
QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray());
|
||||||
|
updateAvatarEntity(entityID, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
|
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
|
||||||
if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) {
|
if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) {
|
||||||
|
|
|
@ -1089,6 +1089,7 @@ public:
|
||||||
void clearRecordingBasis();
|
void clearRecordingBasis();
|
||||||
TransformPointer getRecordingBasis() const;
|
TransformPointer getRecordingBasis() const;
|
||||||
void setRecordingBasis(TransformPointer recordingBasis = TransformPointer());
|
void setRecordingBasis(TransformPointer recordingBasis = TransformPointer());
|
||||||
|
void createRecordingIDs();
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
|
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
|
||||||
|
|
||||||
|
@ -1421,6 +1422,7 @@ protected:
|
||||||
|
|
||||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
mutable ReadWriteLockable _avatarEntitiesLock;
|
||||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||||
|
AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording
|
||||||
AvatarEntityMap _avatarEntityData;
|
AvatarEntityMap _avatarEntityData;
|
||||||
bool _avatarEntityDataChanged { false };
|
bool _avatarEntityDataChanged { false };
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums);
|
return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums);
|
||||||
case PacketType::AvatarIdentityRequest:
|
case PacketType::AvatarIdentityRequest:
|
||||||
return 22;
|
return 22;
|
||||||
|
case PacketType::EntityQueryInitialResultsComplete:
|
||||||
|
return static_cast<PacketVersion>(EntityVersion::ParticleSpin);
|
||||||
default:
|
default:
|
||||||
return 22;
|
return 22;
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,9 +176,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
|
||||||
_timerFunctionMap(),
|
_timerFunctionMap(),
|
||||||
_fileNameString(fileNameString),
|
_fileNameString(fileNameString),
|
||||||
_arrayBufferClass(new ArrayBufferClass(this)),
|
_arrayBufferClass(new ArrayBufferClass(this)),
|
||||||
_assetScriptingInterface(new AssetScriptingInterface(this)),
|
_assetScriptingInterface(new AssetScriptingInterface(this))
|
||||||
// don't delete `ScriptEngines` until all `ScriptEngine`s are gone
|
|
||||||
_scriptEngines(DependencyManager::get<ScriptEngines>())
|
|
||||||
{
|
{
|
||||||
switch (_context) {
|
switch (_context) {
|
||||||
case Context::CLIENT_SCRIPT:
|
case Context::CLIENT_SCRIPT:
|
||||||
|
|
|
@ -806,8 +806,6 @@ protected:
|
||||||
static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS;
|
static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS;
|
||||||
|
|
||||||
Setting::Handle<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true };
|
Setting::Handle<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true };
|
||||||
|
|
||||||
QSharedPointer<ScriptEngines> _scriptEngines;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context,
|
ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context,
|
||||||
|
|
|
@ -406,6 +406,11 @@
|
||||||
sendMoneyRecipient = null;
|
sendMoneyRecipient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUsernameChanged() {
|
||||||
|
Settings.setValue("wallet/autoLogout", false);
|
||||||
|
Settings.setValue("wallet/savedUsername", "");
|
||||||
|
}
|
||||||
|
|
||||||
// Function Name: fromQml()
|
// Function Name: fromQml()
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
|
@ -581,6 +586,7 @@
|
||||||
var tablet = null;
|
var tablet = null;
|
||||||
var walletEnabled = Settings.getValue("commerce", true);
|
var walletEnabled = Settings.getValue("commerce", true);
|
||||||
function startup() {
|
function startup() {
|
||||||
|
GlobalServices.myUsernameChanged.connect(onUsernameChanged);
|
||||||
if (walletEnabled) {
|
if (walletEnabled) {
|
||||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
button = tablet.addButton({
|
button = tablet.addButton({
|
||||||
|
@ -612,6 +618,7 @@
|
||||||
removeOverlays();
|
removeOverlays();
|
||||||
}
|
}
|
||||||
function shutdown() {
|
function shutdown() {
|
||||||
|
GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
|
||||||
button.clicked.disconnect(onButtonClicked);
|
button.clicked.disconnect(onButtonClicked);
|
||||||
tablet.removeButton(button);
|
tablet.removeButton(button);
|
||||||
deleteSendMoneyParticleEffect();
|
deleteSendMoneyParticleEffect();
|
||||||
|
|
|
@ -352,7 +352,7 @@ function fillImageDataFromPrevious() {
|
||||||
containsGif: previousAnimatedSnapPath !== "",
|
containsGif: previousAnimatedSnapPath !== "",
|
||||||
processingGif: false,
|
processingGif: false,
|
||||||
shouldUpload: false,
|
shouldUpload: false,
|
||||||
canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"),
|
canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID"),
|
||||||
isLoggedIn: isLoggedIn
|
isLoggedIn: isLoggedIn
|
||||||
};
|
};
|
||||||
imageData = [];
|
imageData = [];
|
||||||
|
@ -427,7 +427,7 @@ function snapshotUploaded(isError, reply) {
|
||||||
}
|
}
|
||||||
isUploadingPrintableStill = false;
|
isUploadingPrintableStill = false;
|
||||||
}
|
}
|
||||||
var href, domainID;
|
var href, snapshotDomainID;
|
||||||
function takeSnapshot() {
|
function takeSnapshot() {
|
||||||
tablet.emitScriptEvent(JSON.stringify({
|
tablet.emitScriptEvent(JSON.stringify({
|
||||||
type: "snapshot",
|
type: "snapshot",
|
||||||
|
@ -452,8 +452,8 @@ function takeSnapshot() {
|
||||||
// Even the domainID could change (e.g., if the user falls into a teleporter while recording).
|
// Even the domainID could change (e.g., if the user falls into a teleporter while recording).
|
||||||
href = location.href;
|
href = location.href;
|
||||||
Settings.setValue("previousSnapshotHref", href);
|
Settings.setValue("previousSnapshotHref", href);
|
||||||
domainID = location.domainID;
|
snapshotDomainID = location.domainID;
|
||||||
Settings.setValue("previousSnapshotDomainID", domainID);
|
Settings.setValue("previousSnapshotDomainID", snapshotDomainID);
|
||||||
|
|
||||||
maybeDeleteSnapshotStories();
|
maybeDeleteSnapshotStories();
|
||||||
|
|
||||||
|
@ -551,7 +551,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
|
||||||
|
|
||||||
HMD.openTablet();
|
HMD.openTablet();
|
||||||
|
|
||||||
isDomainOpen(domainID, function (canShare) {
|
isDomainOpen(snapshotDomainID, function (canShare) {
|
||||||
snapshotOptions = {
|
snapshotOptions = {
|
||||||
containsGif: false,
|
containsGif: false,
|
||||||
processingGif: false,
|
processingGif: false,
|
||||||
|
@ -594,7 +594,7 @@ function processingGifStarted(pathStillSnapshot) {
|
||||||
|
|
||||||
HMD.openTablet();
|
HMD.openTablet();
|
||||||
|
|
||||||
isDomainOpen(domainID, function (canShare) {
|
isDomainOpen(snapshotDomainID, function (canShare) {
|
||||||
snapshotOptions = {
|
snapshotOptions = {
|
||||||
containsGif: true,
|
containsGif: true,
|
||||||
processingGif: true,
|
processingGif: true,
|
||||||
|
@ -622,7 +622,7 @@ function processingGifCompleted(pathAnimatedSnapshot) {
|
||||||
|
|
||||||
Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
|
Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
|
||||||
|
|
||||||
isDomainOpen(domainID, function (canShare) {
|
isDomainOpen(snapshotDomainID, function (canShare) {
|
||||||
snapshotOptions = {
|
snapshotOptions = {
|
||||||
containsGif: true,
|
containsGif: true,
|
||||||
processingGif: false,
|
processingGif: false,
|
||||||
|
|
Loading…
Reference in a new issue