Merge branch 'master' into destroy

This commit is contained in:
Sam Gondelman 2018-08-06 09:51:14 -07:00 committed by GitHub
commit 2edefe3209
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 688 additions and 389 deletions

View file

@ -827,10 +827,6 @@ void Agent::processAgentAvatarAudio() {
void Agent::aboutToFinish() { void Agent::aboutToFinish() {
setIsAvatar(false);// will stop timers for sending identity packets setIsAvatar(false);// will stop timers for sending identity packets
if (_scriptEngine) {
_scriptEngine->stop();
}
// our entity tree is going to go away so tell that to the EntityScriptingInterface // our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr); DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
@ -843,7 +839,6 @@ void Agent::aboutToFinish() {
// destroy all other created dependencies // destroy all other created dependencies
DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ResourceCacheSharedItems>(); DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCacheScriptingInterface>(); DependencyManager::destroy<SoundCacheScriptingInterface>();
@ -862,3 +857,11 @@ void Agent::aboutToFinish() {
_encoder = nullptr; _encoder = nullptr;
} }
} }
void Agent::stop() {
if (_scriptEngine) {
_scriptEngine->stop();
} else {
setFinished(true);
}
}

View file

@ -67,6 +67,8 @@ public slots:
void setIsAvatar(bool isAvatar); void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; } bool isAvatar() const { return _isAvatar; }
Q_INVOKABLE virtual void stop() override;
private slots: private slots:
void requestScript(); void requestScript();
void scriptRequestFinished(); void scriptRequestFinished();

View file

@ -46,6 +46,14 @@
"default": "40102", "default": "40102",
"type": "int", "type": "int",
"advanced": true "advanced": true
},
{
"name": "enable_packet_verification",
"label": "Enable Packet Verification",
"help": "Enable secure checksums on communication that uses the High Fidelity protocol. Increases security with possibly a small performance penalty.",
"default": true,
"type": "checkbox",
"advanced": true
} }
] ]
}, },

View file

@ -630,6 +630,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) {
void DomainServer::setupNodeListAndAssignments() { void DomainServer::setupNodeListAndAssignments() {
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
static const QString ENABLE_PACKET_AUTHENTICATION = "metaverse.enable_packet_verification";
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
int domainServerPort = localPortValue.toInt(); int domainServerPort = localPortValue.toInt();
@ -696,6 +697,9 @@ void DomainServer::setupNodeListAndAssignments() {
} }
} }
bool isAuthEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_PACKET_AUTHENTICATION).toBool();
nodeList->setAuthenticatePackets(isAuthEnabled);
connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
@ -1133,7 +1137,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
extendedHeaderStream << node->getUUID(); extendedHeaderStream << node->getUUID();
extendedHeaderStream << node->getLocalID(); extendedHeaderStream << node->getLocalID();
extendedHeaderStream << node->getPermissions(); extendedHeaderStream << node->getPermissions();
extendedHeaderStream << limitedNodeList->getAuthenticatePackets();
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
// always send the node their own UUID back // always send the node their own UUID back

View file

@ -62,6 +62,7 @@ Windows.ScrollingWindow {
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
profile: HFWebEngineProfile;
property string userScriptUrl: "" property string userScriptUrl: ""

View file

@ -299,7 +299,7 @@ Item {
anchors.fill: stackView anchors.fill: stackView
id: controllerPrefereneces id: controllerPrefereneces
objectName: "TabletControllerPreferences" objectName: "TabletControllerPreferences"
showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
categoryProperties: { categoryProperties: {
"VR Movement" : { "VR Movement" : {
"User real-world height (meters)" : { "anchors.right" : "undefined" }, "User real-world height (meters)" : { "anchors.right" : "undefined" },

View file

@ -6627,11 +6627,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter); LocationScriptingInterface::locationSetter);
bool clientScript = scriptEngine->isClientScript();
scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor);
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor);
#endif #endif
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerFunction("QmlFragment", clientScript ? QmlFragmentClass::constructor : QmlFragmentClass::restricted_constructor);
scriptEngine->registerFunction("QmlFragment", QmlFragmentClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get<DesktopPreviewProvider>().data()); scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get<DesktopPreviewProvider>().data());

View file

@ -412,9 +412,6 @@ void AvatarManager::clearOtherAvatars() {
while (avatarIterator != _avatarHash.end()) { while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
if (avatar != _myAvatar) { if (avatar != _myAvatar) {
if (avatar->isInScene()) {
avatar->removeFromScene(avatar, scene, transaction);
}
handleRemovedAvatar(avatar); handleRemovedAvatar(avatar);
avatarIterator = _avatarHash.erase(avatarIterator); avatarIterator = _avatarHash.erase(avatarIterator);
} else { } else {

View file

@ -19,6 +19,7 @@
AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar); assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR; _type = MOTIONSTATE_TYPE_AVATAR;
cacheShapeDiameter();
} }
void AvatarMotionState::handleEasyChanges(uint32_t& flags) { void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
@ -57,9 +58,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
const btCollisionShape* AvatarMotionState::computeNewShape() { const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo; ShapeInfo shapeInfo;
std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo); std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
halfExtents.y = 0.0f;
_diameter = 2.0f * glm::length(halfExtents);
return getShapeManager()->getShape(shapeInfo); return getShapeManager()->getShape(shapeInfo);
} }
@ -98,6 +96,10 @@ void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) {
btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget;
_body->setLinearVelocity(velocity); _body->setLinearVelocity(velocity);
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
// slam its rotation
btTransform newTransform = worldTrans;
newTransform.setRotation(glmToBullet(getObjectRotation()));
_body->setWorldTransform(newTransform);
} }
} }
@ -141,7 +143,10 @@ glm::vec3 AvatarMotionState::getObjectLinearVelocity() const {
// virtual // virtual
glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
return _avatar->getWorldAngularVelocity(); // HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant.
// Therefore, as optimization toward support for larger crowds we ignore it and return zero.
//return _avatar->getWorldAngularVelocity();
return glm::vec3(0.0f);
} }
// virtual // virtual
@ -174,3 +179,28 @@ float AvatarMotionState::getMass() const {
return std::static_pointer_cast<Avatar>(_avatar)->computeMass(); return std::static_pointer_cast<Avatar>(_avatar)->computeMass();
} }
void AvatarMotionState::cacheShapeDiameter() {
if (_shape) {
// shape is capsuleY
btVector3 aabbMin, aabbMax;
btTransform transform;
transform.setIdentity();
_shape->getAabb(transform, aabbMin, aabbMax);
_diameter = (aabbMax - aabbMin).getX();
} else {
_diameter = 0.0f;
}
}
void AvatarMotionState::setRigidBody(btRigidBody* body) {
ObjectMotionState::setRigidBody(body);
if (_body) {
// remove angular dynamics from this body
_body->setAngularFactor(0.0f);
}
}
void AvatarMotionState::setShape(const btCollisionShape* shape) {
ObjectMotionState::setShape(shape);
cacheShapeDiameter();
}

View file

@ -74,6 +74,10 @@ public:
friend class Avatar; friend class Avatar;
protected: protected:
void setRigidBody(btRigidBody* body) override;
void setShape(const btCollisionShape* shape) override;
void cacheShapeDiameter();
// the dtor had been made protected to force the compiler to verify that it is only // the dtor had been made protected to force the compiler to verify that it is only
// ever called by the Avatar class dtor. // ever called by the Avatar class dtor.
~AvatarMotionState(); ~AvatarMotionState();

View file

@ -1135,7 +1135,6 @@ void MyAvatar::saveData() {
settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn); settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("userHeight", getUserHeight()); settings.setValue("userHeight", getUserHeight());
settings.setValue("flyingDesktop", getFlyingDesktopPref());
settings.setValue("flyingHMD", getFlyingHMDPref()); settings.setValue("flyingHMD", getFlyingHMDPref());
settings.endGroup(); settings.endGroup();
@ -1289,7 +1288,6 @@ void MyAvatar::loadData() {
// Flying preferences must be loaded before calling setFlyingEnabled() // Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle<bool> firstRunVal { Settings::firstRun, true }; Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool());
setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
setFlyingEnabled(getFlyingEnabled()); setFlyingEnabled(getFlyingEnabled());

View file

@ -26,7 +26,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
/**jsdoc /**jsdoc
* The Audio API features tools to help control audio contexts and settings. * The <code>Audio</code> API provides facilities to interact with audio inputs and outputs and to play sounds.
* *
* @namespace Audio * @namespace Audio
* *
@ -35,12 +35,21 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* @hifi-server-entity * @hifi-server-entity
* @hifi-assignment-client * @hifi-assignment-client
* *
* @property {boolean} muted * @property {boolean} muted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @property {boolean} noiseReduction * @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
* @property {number} inputVolume * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
* @property {number} inputLevel <em>Read-only.</em> * above the noise floor.
* @property {string} context <em>Read-only.</em> * @property {number} inputLevel - The loudness of the audio input, range <code>0.0</code> (no sound) &ndash;
* @property {} devices <em>Read-only.</em> * <code>1.0</code> (the onset of clipping). <em>Read-only.</em>
* @property {number} inputVolume - Adjusts the volume of the input audio; range <code>0.0</code> &ndash; <code>1.0</code>.
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
* devices, and others might only support values of <code>0.0</code> and <code>1.0</code>.
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
* <code>false</code>. Some devices do not support stereo, in which case the value is always <code>false</code>.
* @property {string} context - The current context of the audio: either <code>"Desktop"</code> or <code>"HMD"</code>.
* <em>Read-only.</em>
* @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
* removed.
*/ */
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
@ -69,45 +78,91 @@ public:
/**jsdoc /**jsdoc
* @function Audio.setInputDevice * @function Audio.setInputDevice
* @param {} device * @param {object} device
* @param {boolean} isHMD * @param {boolean} isHMD
* @deprecated This function is deprecated and will be removed.
*/ */
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
/**jsdoc /**jsdoc
* @function Audio.setOutputDevice * @function Audio.setOutputDevice
* @param {} device * @param {object} device
* @param {boolean} isHMD * @param {boolean} isHMD
* @deprecated This function is deprecated and will be removed.
*/ */
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
/**jsdoc /**jsdoc
* Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
* come from either the domain's audio zone if used &mdash; configured on the server &mdash; or as scripted by
* {@link Audio.setReverbOptions|setReverbOptions}.
* @function Audio.setReverb * @function Audio.setReverb
* @param {boolean} enable * @param {boolean} enable - <code>true</code> to enable reverberation, <code>false</code> to disable.
*/ * @example <caption>Enable reverberation for a short while.</caption>
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* var injector;
* var injectorOptions = {
* position: MyAvatar.position
* };
*
* Script.setTimeout(function () {
* print("Reverb OFF");
* Audio.setReverb(false);
* injector = Audio.playSound(sound, injectorOptions);
* }, 1000);
*
* Script.setTimeout(function () {
* var reverbOptions = new AudioEffectOptions();
* reverbOptions.roomSize = 100;
* Audio.setReverbOptions(reverbOptions);
* print("Reverb ON");
* Audio.setReverb(true);
* }, 4000);
*
* Script.setTimeout(function () {
* print("Reverb OFF");
* Audio.setReverb(false);
* }, 8000); */
Q_INVOKABLE void setReverb(bool enable); Q_INVOKABLE void setReverb(bool enable);
/**jsdoc /**jsdoc
* Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation.
* @function Audio.setReverbOptions * @function Audio.setReverbOptions
* @param {AudioEffectOptions} options * @param {AudioEffectOptions} options - The reverberation options.
*/ */
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
/**jsdoc /**jsdoc
* Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
* @function Audio.startRecording * @function Audio.startRecording
* @param {string} filename * @param {string} filename - The path and name of the file to make the recording in. Should have a <code>.wav</code>
* @returns {boolean} * extension. The file is overwritten if it already exists.
* @returns {boolean} <code>true</code> if the specified file could be opened and audio recording has started, otherwise
* <code>false</code>.
* @example <caption>Make a 10 second audio recording.</caption>
* var filename = File.getTempDir() + "/audio.wav";
* if (Audio.startRecording(filename)) {
* Script.setTimeout(function () {
* Audio.stopRecording();
* print("Audio recording made in: " + filename);
* }, 10000);
*
* } else {
* print("Could not make an audio recording in: " + filename);
* }
*/ */
Q_INVOKABLE bool startRecording(const QString& filename); Q_INVOKABLE bool startRecording(const QString& filename);
/**jsdoc /**jsdoc
* Finish making an audio recording started with {@link Audio.startRecording|startRecording}.
* @function Audio.stopRecording * @function Audio.stopRecording
*/ */
Q_INVOKABLE void stopRecording(); Q_INVOKABLE void stopRecording();
/**jsdoc /**jsdoc
* Check whether an audio recording is currently being made.
* @function Audio.getRecording * @function Audio.getRecording
* @returns {boolean} * @returns {boolean} <code>true</code> if an audio recording is currently being made, otherwise <code>false</code>.
*/ */
Q_INVOKABLE bool getRecording(); Q_INVOKABLE bool getRecording();
@ -116,40 +171,54 @@ signals:
/**jsdoc /**jsdoc
* @function Audio.nop * @function Audio.nop
* @returns {Signal} * @returns {Signal}
* @deprecated This signal is deprecated and will be removed.
*/ */
void nop(); void nop();
/**jsdoc /**jsdoc
* Triggered when the audio input is muted or unmuted.
* @function Audio.mutedChanged * @function Audio.mutedChanged
* @param {boolean} isMuted * @param {boolean} isMuted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @returns {Signal} * @returns {Signal}
* @example <caption>Report when audio input is muted or unmuted</caption>
* Audio.mutedChanged.connect(function (isMuted) {
* print("Audio muted: " + isMuted);
* });
*/ */
void mutedChanged(bool isMuted); void mutedChanged(bool isMuted);
/**jsdoc /**jsdoc
* Triggered when the audio input noise reduction is enabled or disabled.
* @function Audio.noiseReductionChanged * @function Audio.noiseReductionChanged
* @param {boolean} isEnabled * @param {boolean} isEnabled - <code>true</code> if audio input noise reduction is enabled, otherwise <code>false</code>.
* @returns {Signal} * @returns {Signal}
*/ */
void noiseReductionChanged(bool isEnabled); void noiseReductionChanged(bool isEnabled);
/**jsdoc /**jsdoc
* Triggered when the input audio volume changes.
* @function Audio.inputVolumeChanged * @function Audio.inputVolumeChanged
* @param {number} volume * @param {number} volume - The requested volume to be applied to the audio input, range <code>0.0</code> &ndash;
* <code>1.0</code>. The resulting value of <code>Audio.inputVolume</code> depends on the capabilities of the device:
* for example, the volume can't be changed on some devices, and others might only support values of <code>0.0</code>
* and <code>1.0</code>.
* @returns {Signal} * @returns {Signal}
*/ */
void inputVolumeChanged(float volume); void inputVolumeChanged(float volume);
/**jsdoc /**jsdoc
* Triggered when the input audio level changes.
* @function Audio.inputLevelChanged * @function Audio.inputLevelChanged
* @param {number} level * @param {number} level - The loudness of the input audio, range <code>0.0</code> (no sound) &ndash; <code>1.0</code> (the
* onset of clipping).
* @returns {Signal} * @returns {Signal}
*/ */
void inputLevelChanged(float level); void inputLevelChanged(float level);
/**jsdoc /**jsdoc
* Triggered when the current context of the audio changes.
* @function Audio.contextChanged * @function Audio.contextChanged
* @param {string} context * @param {string} context - The current context of the audio: either <code>"Desktop"</code> or <code>"HMD"</code>.
* @returns {Signal} * @returns {Signal}
*/ */
void contextChanged(const QString& context); void contextChanged(const QString& context);
@ -158,7 +227,7 @@ public slots:
/**jsdoc /**jsdoc
* @function Audio.onContextChanged * @function Audio.onContextChanged
* @returns {Signal} * @deprecated This function is deprecated and will be removed.
*/ */
void onContextChanged(); void onContextChanged();

View file

@ -265,42 +265,6 @@ void setupPreferences() {
preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter));
} }
static const QString MOVEMENT{ "Movement" };
{
static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler");
auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); };
auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
preferences->addPreference(new CheckPreference(MOVEMENT,
QStringLiteral("Advanced movement for hand controllers"),
getter, setter));
}
{
auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); };
auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter);
QStringList items;
items << "Snap turn" << "Smooth turn";
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [=]()->float { return myAvatar->getUserHeight(); };
auto setter = [=](float value) { myAvatar->setUserHeight(value); };
auto preference = new SpinnerPreference(MOVEMENT, "User real-world height (meters)", getter, setter);
preference->setMin(1.0f);
preference->setMax(2.2f);
preference->setDecimals(3);
preference->setStep(0.001f);
preferences->addPreference(preference);
}
{
auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] {
qApp->resetSensors();
});
preferences->addPreference(preference);
}
static const QString VR_MOVEMENT{ "VR Movement" }; static const QString VR_MOVEMENT{ "VR Movement" };
{ {
@ -314,7 +278,7 @@ void setupPreferences() {
{ {
auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); };
auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); };
preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping (HMD)", getter, setter));
} }
{ {
auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };

View file

@ -59,28 +59,29 @@ static void setOption(QScriptValue arguments, const QString name, float defaultV
} }
/**jsdoc /**jsdoc
* Reverberation options that can be used to initialize an {@link AudioEffectOptions} object when created.
* @typedef {object} AudioEffectOptions.ReverbOptions * @typedef {object} AudioEffectOptions.ReverbOptions
* @property {number} bandwidth * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input.
* @property {number} preDelay * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections.
* @property {number} lateDelay * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail.
* @property {number} reverbTime * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60.
* @property {number} earlyDiffusion * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%.
* @property {number} lateDiffusion * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%.
* @property {number} roomSize * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%).
* @property {number} density * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%.
* @property {number} bassMult * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime.
* @property {number} bassFreq * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult.
* @property {number} highGain * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB).
* @property {number} highFreq * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain.
* @property {number} modRate * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines.
* @property {number} modDepth * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines.
* @property {number} earlyGain * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections.
* @property {number} lateGain * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail.
* @property {number} earlyMixLeft * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections.
* @property {number} earlyMixRight * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections.
* @property {number} lateMixLeft * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail.
* @property {number} lateMixRight * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail.
* @property {number} wetDryMix * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%).
*/ */
AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) { AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) {
setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth); setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth);

View file

@ -16,35 +16,39 @@
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
/**jsdoc /**jsdoc
* Audio effect options used by the {@link Audio} API.
*
* <p>Create using <code>new AudioEffectOptions(reverbOptions)</code>.</p>
*
* @class AudioEffectOptions * @class AudioEffectOptions
* @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] - Reverberation options.
* *
* @hifi-interface * @hifi-interface
* @hifi-client-entity * @hifi-client-entity
* @hifi-server-entity * @hifi-server-entity
* @hifi-assignment-client * @hifi-assignment-client
* *
* @property {number} bandwidth=10000 * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input.
* @property {number} preDelay=20 * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections.
* @property {number} lateDelay=0 * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail.
* @property {number} reverbTime=2 * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60.
* @property {number} earlyDiffusion=100 * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%.
* @property {number} lateDiffusion=100 * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%.
* @property {number} roomSize=50 * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%).
* @property {number} density=100 * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%.
* @property {number} bassMult=1.5 * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime.
* @property {number} bassFreq=250 * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult.
* @property {number} highGain=-6 * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB).
* @property {number} highFreq=3000 * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain.
* @property {number} modRate=2.3 * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines.
* @property {number} modDepth=50 * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines.
* @property {number} earlyGain=0 * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections.
* @property {number} lateGain=0 * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail.
* @property {number} earlyMixLeft=20 * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections.
* @property {number} earlyMixRight=20 * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections.
* @property {number} lateMixLeft=90 * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail.
* @property {number} lateMixRight=90 * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail.
* @property {number} wetDryMix=50 * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%).
*/ */
class AudioEffectOptions : public QObject { class AudioEffectOptions : public QObject {

View file

@ -45,6 +45,23 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje
return obj; return obj;
} }
/**jsdoc
* Configures how an audio injector plays its audio.
* @typedef {object} AudioInjector.AudioInjectorOptions
* @property {Vec3} position=Vec3.ZERO - The position in the domain to play the sound.
* @property {Quat} orientation=Quat.IDENTITY - The orientation in the domain to play the sound in.
* @property {number} volume=1.0 - Playback volume, between <code>0.0</code> and <code>1.0</code>.
* @property {number} pitch=1.0 - Alter the pitch of the sound, within +/- 2 octaves. The value is the relative sample rate to
* resample the sound at, range <code>0.0625</code> &ndash; <code>16.0</code>. A value of <code>0.0625</code> lowers the
* pitch by 2 octaves; <code>1.0</code> is no change in pitch; <code>16.0</code> raises the pitch by 2 octaves.
* @property {boolean} loop=false - If <code>true</code>, the sound is played repeatedly until playback is stopped.
* @property {number} secondOffset=0 - Starts playback from a specified time (seconds) within the sound file, &ge;
* <code>0</code>.
* @property {boolean} localOnly=false - IF <code>true</code>, the sound is played back locally on the client rather than to
* others via the audio mixer.
* @property {boolean} ignorePenumbra=false - <strong>Deprecated:</strong> This property is deprecated and will be
* removed.
*/
void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) { void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) {
if (!object.isObject()) { if (!object.isObject()) {
qWarning() << "Audio injector options is not an object."; qWarning() << "Audio injector options is not an object.";

View file

@ -79,6 +79,14 @@ private:
typedef QSharedPointer<Sound> SharedSoundPointer; typedef QSharedPointer<Sound> SharedSoundPointer;
/**jsdoc /**jsdoc
* An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}.
* <p>Supported formats:</p>
* <ul>
* <li>WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2(stereo), or 4 (ambisonic) channels.</li>
* <li>MP3: Mono or stereo, at any sample rate.</li>
* <li>RAW: 48khz 16-bit mono or stereo. Filename must include <code>".stereo"</code> to be interpreted as stereo.</li>
* </ul>
*
* @class SoundObject * @class SoundObject
* *
* @hifi-interface * @hifi-interface
@ -86,8 +94,9 @@ typedef QSharedPointer<Sound> SharedSoundPointer;
* @hifi-server-entity * @hifi-server-entity
* @hifi-assignment-client * @hifi-assignment-client
* *
* @property {boolean} downloaded * @property {boolean} downloaded - <code>true</code> if the sound has been downloaded and is ready to be played, otherwise
* @property {number} duration * <code>false</code>.
* @property {number} duration - The duration of the sound, in seconds.
*/ */
class SoundScriptingInterface : public QObject { class SoundScriptingInterface : public QObject {
Q_OBJECT Q_OBJECT
@ -103,6 +112,7 @@ public:
float getDuration() { return _sound->getDuration(); } float getDuration() { return _sound->getDuration(); }
/**jsdoc /**jsdoc
* Triggered when the sound has been downloaded and is ready to be played.
* @function SoundObject.ready * @function SoundObject.ready
* @returns {Signal} * @returns {Signal}
*/ */

View file

@ -48,9 +48,11 @@ public:
SoundCacheScriptingInterface(); SoundCacheScriptingInterface();
/**jsdoc /**jsdoc
* Loads the content of an audio file into a {@link SoundObject}, ready for playback by {@link Audio.playSound}.
* @function SoundCache.getSound * @function SoundCache.getSound
* @param {string} url * @param {string} url - The URL of the audio file to load &mdash; Web, ATP, or file. See {@link SoundObject} for supported
* @returns {SoundObject} * formats.
* @returns {SoundObject} The sound ready for playback.
*/ */
Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url);
}; };

View file

@ -328,9 +328,10 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
if (sourceNode) { if (sourceNode) {
bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType);
bool ignoreVerification = isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType); bool verificationEnabled = !(isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType))
&& _useAuthentication;
if (verifiedPacket && !ignoreVerification) { if (verifiedPacket && verificationEnabled) {
QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet);
QByteArray expectedHash; QByteArray expectedHash;
@ -383,7 +384,7 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAut
packet.writeSourceID(getSessionLocalID()); packet.writeSourceID(getSessionLocalID());
} }
if (hmacAuth if (_useAuthentication && hmacAuth
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
packet.writeVerificationHash(*hmacAuth); packet.writeVerificationHash(*hmacAuth);

View file

@ -307,6 +307,8 @@ public:
bool isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode = nullptr); bool isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode = nullptr);
bool isPacketVerified(const udt::Packet& packet) { return isPacketVerifiedWithSource(packet); } bool isPacketVerified(const udt::Packet& packet) { return isPacketVerifiedWithSource(packet); }
void setAuthenticatePackets(bool useAuthentication) { _useAuthentication = useAuthentication; }
bool getAuthenticatePackets() const { return _useAuthentication; }
static void makeSTUNRequestPacket(char* stunRequestPacket); static void makeSTUNRequestPacket(char* stunRequestPacket);
@ -394,6 +396,7 @@ protected:
HifiSockAddr _publicSockAddr; HifiSockAddr _publicSockAddr;
HifiSockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT }; HifiSockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT };
bool _hasTCPCheckedLocalSocket { false }; bool _hasTCPCheckedLocalSocket { false };
bool _useAuthentication { true };
PacketReceiver* _packetReceiver; PacketReceiver* _packetReceiver;

View file

@ -665,6 +665,10 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
NodePermissions newPermissions; NodePermissions newPermissions;
packetStream >> newPermissions; packetStream >> newPermissions;
setPermissions(newPermissions); setPermissions(newPermissions);
// Is packet authentication enabled?
bool isAuthenticated;
packetStream >> isAuthenticated;
setAuthenticatePackets(isAuthenticated);
// pull each node in the packet // pull each node in the packet
while (packetStream.device()->pos() < message->getSize()) { while (packetStream.device()->pos() < message->getSize()) {

View file

@ -120,7 +120,7 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server" qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server"
<< "Stopping the current assignment"; << "Stopping the current assignment";
setFinished(true); stop();
} else { } else {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn");
@ -132,5 +132,5 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
void ThreadedAssignment::domainSettingsRequestFailed() { void ThreadedAssignment::domainSettingsRequestFailed() {
qCDebug(networking) << "Failed to retreive settings object from domain-server. Bailing on assignment."; qCDebug(networking) << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true); stop();
} }

View file

@ -24,7 +24,6 @@ public:
ThreadedAssignment(ReceivedMessage& message); ThreadedAssignment(ReceivedMessage& message);
~ThreadedAssignment() { stop(); } ~ThreadedAssignment() { stop(); }
void setFinished(bool isFinished);
virtual void aboutToFinish() { }; virtual void aboutToFinish() { };
void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); void addPacketStatsAndSendStatsPacket(QJsonObject statsObject);
@ -43,6 +42,7 @@ signals:
protected: protected:
void commonInit(const QString& targetName, NodeType_t nodeType); void commonInit(const QString& targetName, NodeType_t nodeType);
void setFinished(bool isFinished);
bool _isFinished; bool _isFinished;
QTimer _domainServerTimer; QTimer _domainServerTimer;

View file

@ -27,7 +27,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::StunResponse: case PacketType::StunResponse:
return 17; return 17;
case PacketType::DomainList: case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport); return static_cast<PacketVersion>(DomainListVersion::AuthenticationOptional);
case PacketType::EntityAdd: case PacketType::EntityAdd:
case PacketType::EntityClone: case PacketType::EntityClone:
case PacketType::EntityEdit: case PacketType::EntityEdit:

View file

@ -315,7 +315,8 @@ enum class DomainListVersion : PacketVersion {
PrePermissionsGrid = 18, PrePermissionsGrid = 18,
PermissionsGrid, PermissionsGrid,
GetUsernameFromUUIDSupport, GetUsernameFromUUIDSupport,
GetMachineFingerprintFromUUIDSupport GetMachineFingerprintFromUUIDSupport,
AuthenticationOptional
}; };
enum class AudioVersion : PacketVersion { enum class AudioVersion : PacketVersion {

View file

@ -64,9 +64,9 @@ ShapeManager* ObjectMotionState::getShapeManager() {
} }
ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) : ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
_shape(shape),
_lastKinematicStep(worldSimulationStep) _lastKinematicStep(worldSimulationStep)
{ {
setShape(shape);
} }
ObjectMotionState::~ObjectMotionState() { ObjectMotionState::~ObjectMotionState() {

View file

@ -175,13 +175,13 @@ protected:
virtual void setMotionType(PhysicsMotionType motionType); virtual void setMotionType(PhysicsMotionType motionType);
void updateCCDConfiguration(); void updateCCDConfiguration();
void setRigidBody(btRigidBody* body); virtual void setRigidBody(btRigidBody* body);
virtual void setShape(const btCollisionShape* shape); virtual void setShape(const btCollisionShape* shape);
MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState
PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC
const btCollisionShape* _shape; const btCollisionShape* _shape { nullptr };
btRigidBody* _body { nullptr }; btRigidBody* _body { nullptr };
float _density { 1.0f }; float _density { 1.0f };

View file

@ -40,7 +40,8 @@ static QSize clampSize(const QSize& qsize, uint32_t maxDimension) {
return fromGlm(clampSize(toGlm(qsize), maxDimension)); return fromGlm(clampSize(toGlm(qsize), maxDimension));
} }
const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_OBJECT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
const QmlContextCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*) {};
void OffscreenSurface::initializeEngine(QQmlEngine* engine) { void OffscreenSurface::initializeEngine(QQmlEngine* engine) {
} }
@ -266,8 +267,8 @@ void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const
loadInternal(qmlSource, createNewContext, nullptr, callback); loadInternal(qmlSource, createNewContext, nullptr, callback);
} }
void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) {
load(qmlSource, true, callback); loadInternal(qmlSource, true, nullptr, callback, contextCallback);
} }
void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) {
@ -281,7 +282,8 @@ void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObject
void OffscreenSurface::loadInternal(const QUrl& qmlSource, void OffscreenSurface::loadInternal(const QUrl& qmlSource,
bool createNewContext, bool createNewContext,
QQuickItem* parent, QQuickItem* parent,
const QmlContextObjectCallback& callback) { const QmlContextObjectCallback& callback,
const QmlContextCallback& contextCallback) {
PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) }); PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) });
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qFatal("Called load on a non-surface thread"); qFatal("Called load on a non-surface thread");
@ -310,6 +312,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource,
} }
auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext);
contextCallback(targetContext);
QQmlComponent* qmlComponent; QQmlComponent* qmlComponent;
{ {
PROFILE_RANGE(app, "new QQmlComponent"); PROFILE_RANGE(app, "new QQmlComponent");

View file

@ -37,13 +37,15 @@ namespace impl {
class SharedObject; class SharedObject;
} }
using QmlContextCallback = ::std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = ::std::function<void(QQmlContext*, QQuickItem*)>; using QmlContextObjectCallback = ::std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenSurface : public QObject { class OffscreenSurface : public QObject {
Q_OBJECT Q_OBJECT
public: public:
static const QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK; static const QmlContextObjectCallback DEFAULT_CONTEXT_OBJECT_CALLBACK;
static const QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
using TextureAndFence = std::pair<uint32_t, void*>; using TextureAndFence = std::pair<uint32_t, void*>;
using MouseTranslator = std::function<QPoint(const QPointF&)>; using MouseTranslator = std::function<QPoint(const QPointF&)>;
@ -85,10 +87,15 @@ public:
Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
// For use from C++ // For use from C++
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); Q_INVOKABLE void load(const QUrl& qmlSource,
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); bool createNewContext,
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile,
const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource,
const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK,
const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK);
public slots: public slots:
virtual void onFocusObjectChanged(QObject* newFocus) {} virtual void onFocusObjectChanged(QObject* newFocus) {}
@ -103,19 +110,21 @@ protected:
virtual void initializeEngine(QQmlEngine* engine); virtual void initializeEngine(QQmlEngine* engine);
virtual void loadInternal(const QUrl& qmlSource, virtual void loadInternal(const QUrl& qmlSource,
bool createNewContext, bool createNewContext,
QQuickItem* parent, QQuickItem* parent,
const QmlContextObjectCallback& callback) final; const QmlContextObjectCallback& callback,
const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK) final;
virtual void finishQmlLoad(QQmlComponent* qmlComponent, virtual void finishQmlLoad(QQmlComponent* qmlComponent,
QQmlContext* qmlContext, QQmlContext* qmlContext,
QQuickItem* parent, QQuickItem* parent,
const QmlContextObjectCallback& onQmlLoadedCallback) final; const QmlContextObjectCallback& onQmlLoadedCallback) final;
virtual void onRootCreated() {} virtual void onRootCreated() {}
virtual void onItemCreated(QQmlContext* context, QQuickItem* newItem) {} virtual void onItemCreated(QQmlContext* context, QQuickItem* newItem) {}
virtual void onRootContextCreated(QQmlContext* qmlContext) {} virtual void onRootContextCreated(QQmlContext* qmlContext) {}
virtual QQmlContext* contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext); virtual QQmlContext* contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext);
private: private:
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
friend class hifi::qml::impl::SharedObject; friend class hifi::qml::impl::SharedObject;

View file

@ -23,6 +23,7 @@ class AudioScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
// JSDoc for property is in Audio.h.
Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged)
public: public:
@ -35,91 +36,121 @@ protected:
// these methods are protected to stop C++ callers from calling, but invokable from script // these methods are protected to stop C++ callers from calling, but invokable from script
/**jsdoc /**jsdoc
* Starts playing &mdash; "injecting" &mdash; the content of an audio file. The sound is played globally (sent to the audio
* mixer) so that everyone hears it, unless the <code>injectorOptions</code> has <code>localOnly</code> set to
* <code>true</code> in which case only the client hears the sound played. No sound is played if sent to the audio mixer
* but the client is not connected to an audio mixer. The {@link AudioInjector} object returned by the function can be used
* to control the playback and get information about its current state.
* @function Audio.playSound * @function Audio.playSound
* @param {} sound * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See
* @param {} [injectorOptions=null] * {@link SoundObject} for supported formats.
* @returns {object} * @param {AudioInjector.AudioInjectorOptions} [injectorOptions={}] - Audio injector configuration.
* @returns {AudioInjector} The audio injector that plays the audio file.
* @example <caption>Play a sound.</caption>
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* var injector;
* var injectorOptions = {
* position: MyAvatar.position
* };
*
* Script.setTimeout(function () { // Give the sound time to load.
* injector = Audio.playSound(sound, injectorOptions);
* }, 1000);
*/ */
Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions());
/**jsdoc /**jsdoc
* Start playing the content of an audio file, locally (isn't sent to the audio mixer). This is the same as calling
* {@link Audio.playSound} with {@link AudioInjector.AudioInjectorOptions} <code>localOnly</code> set <code>true</code> and
* the specified <code>position</code>.
* @function Audio.playSystemSound * @function Audio.playSystemSound
* @param {} sound * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See
* @param {} position * {@link SoundObject} for supported formats.
* @returns {object} * @param {Vec3} position - The position in the domain to play the sound.
* @returns {AudioInjector} The audio injector that plays the audio file.
*/ */
// FIXME: there is no way to play a positionless sound // FIXME: there is no way to play a positionless sound
Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position);
/**jsdoc /**jsdoc
* Set whether or not the audio input should be used in stereo. If the audio input does not support stereo then setting a
* value of <code>true</code> has no effect.
* @function Audio.setStereoInput * @function Audio.setStereoInput
* @param {boolean} stereo * @param {boolean} stereo - <code>true</code> if the audio input should be used in stereo, otherwise <code>false</code>.
*/ */
Q_INVOKABLE void setStereoInput(bool stereo); Q_INVOKABLE void setStereoInput(bool stereo);
/**jsdoc /**jsdoc
* Get whether or not the audio input is used in stereo.
* @function Audio.isStereoInput * @function Audio.isStereoInput
* @returns {boolean} * @returns {boolean} <code>true</code> if the audio input is used in stereo, otherwise <code>false</code>.
*/ */
Q_INVOKABLE bool isStereoInput(); Q_INVOKABLE bool isStereoInput();
signals: signals:
/**jsdoc /**jsdoc
* The client has been muted by the mixer. * Triggered when the client is muted by the mixer because their loudness value for the noise background has reached the
* threshold set for the domain in the server settings.
* @function Audio.mutedByMixer * @function Audio.mutedByMixer
* @returns {Signal} * @returns {Signal}
*/ */
void mutedByMixer(); void mutedByMixer();
/**jsdoc /**jsdoc
* The entire environment has been muted by the mixer. * Triggered when the client is muted by the mixer because they're within a certain radius (50m) of someone who requested
* the mute through Developer &gt; Audio &gt; Mute Environment.
* @function Audio.environmentMuted * @function Audio.environmentMuted
* @returns {Signal} * @returns {Signal}
*/ */
void environmentMuted(); void environmentMuted();
/**jsdoc /**jsdoc
* The client has received its first packet from the audio mixer. * Triggered when the client receives its first packet from the audio mixer.
* @function Audio.receivedFirstPacket * @function Audio.receivedFirstPacket
* @returns {Signal} * @returns {Signal}
*/ */
void receivedFirstPacket(); void receivedFirstPacket();
/**jsdoc /**jsdoc
* The client has been disconnected from the audio mixer. * Triggered when the client is disconnected from the audio mixer.
* @function Audio.disconnected * @function Audio.disconnected
* @returns {Signal} * @returns {Signal}
*/ */
void disconnected(); void disconnected();
/**jsdoc /**jsdoc
* The noise gate has opened. * Triggered when the noise gate is opened: the input audio signal is no longer blocked (fully attenuated) because it has
* risen above an adaptive threshold set just above the noise floor. Only occurs if <code>Audio.noiseReduction</code> is
* <code>true</code>.
* @function Audio.noiseGateOpened * @function Audio.noiseGateOpened
* @returns {Signal} * @returns {Signal}
*/ */
void noiseGateOpened(); void noiseGateOpened();
/**jsdoc /**jsdoc
* The noise gate has closed. * Triggered when the noise gate is closed: the input audio signal is blocked (fully attenuated) because it has fallen
* below an adaptive threshold set just above the noise floor. Only occurs if <code>Audio.noiseReduction</code> is
* <code>true</code>.
* @function Audio.noiseGateClosed * @function Audio.noiseGateClosed
* @returns {Signal} * @returns {Signal}
*/ */
void noiseGateClosed(); void noiseGateClosed();
/**jsdoc /**jsdoc
* A frame of mic input audio has been received and processed. * Triggered when a frame of audio input is processed.
* @function Audio.inputReceived * @function Audio.inputReceived
* @param {} inputSamples * @param {Int16Array} inputSamples - The audio input processed.
* @returns {Signal} * @returns {Signal}
*/ */
void inputReceived(const QByteArray& inputSamples); void inputReceived(const QByteArray& inputSamples);
/**jsdoc /**jsdoc
* @function Audio.isStereoInputChanged * Triggered when the input audio use changes between mono and stereo.
* @param {boolean} isStereo * @function Audio.isStereoInputChanged
* @returns {Signal} * @param {boolean} isStereo - <code>true</code> if the input audio is stereo, otherwise <code>false</code>.
*/ * @returns {Signal}
*/
void isStereoInputChanged(bool isStereo); void isStereoInputChanged(bool isStereo);
private: private:

View file

@ -16,6 +16,22 @@
#include <AudioInjector.h> #include <AudioInjector.h>
/**jsdoc
* Plays &mdash; "injects" &mdash; the content of an audio file. Used in the {@link Audio} API.
*
* @class AudioInjector
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {boolean} playing - <code>true</code> if the audio is currently playing, otherwise <code>false</code>.
* <em>Read-only.</em>
* @property {number} loudness - The loudness in the last frame of audio, range <code>0.0</code> &ndash; <code>1.0</code>.
* <em>Read-only.</em>
* @property {AudioInjector.AudioInjectorOptions} options - Configures how the injector plays the audio.
*/
class ScriptAudioInjector : public QObject { class ScriptAudioInjector : public QObject {
Q_OBJECT Q_OBJECT
@ -26,19 +42,103 @@ public:
ScriptAudioInjector(const AudioInjectorPointer& injector); ScriptAudioInjector(const AudioInjectorPointer& injector);
~ScriptAudioInjector(); ~ScriptAudioInjector();
public slots: public slots:
/**jsdoc
* Stop current playback, if any, and start playing from the beginning.
* @function AudioInjector.restart
*/
void restart() { _injector->restart(); } void restart() { _injector->restart(); }
/**jsdoc
* Stop audio playback.
* @function AudioInjector.stop
* @example <caption>Stop playing a sound before it finishes.</caption>
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* var injector;
* var injectorOptions = {
* position: MyAvatar.position
* };
*
* Script.setTimeout(function () { // Give the sound time to load.
* injector = Audio.playSound(sound, injectorOptions);
* }, 1000);
*
* Script.setTimeout(function () {
* injector.stop();
* }, 2000);
*/
void stop() { _injector->stop(); } void stop() { _injector->stop(); }
/**jsdoc
* Get the current configuration of the audio injector.
* @function AudioInjector.getOptions
* @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio.
*/
const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); }
/**jsdoc
* Configure how the injector plays the audio.
* @function AudioInjector.setOptions
* @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio.
*/
void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); }
/**jsdoc
* Get the loudness of the most recent frame of audio played.
* @function AudioInjector.getLoudness
* @returns {number} The loudness of the most recent frame of audio played, range <code>0.0</code> &ndash; <code>1.0</code>.
*/
float getLoudness() const { return _injector->getLoudness(); } float getLoudness() const { return _injector->getLoudness(); }
/**jsdoc
* Get whether or not the audio is currently playing.
* @function AudioInjector.isPlaying
* @returns {boolean} <code>true</code> if the audio is currently playing, otherwise <code>false</code>.
* @example <caption>See if a sound is playing.</caption>
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* var injector;
* var injectorOptions = {
* position: MyAvatar.position
* };
*
* Script.setTimeout(function () { // Give the sound time to load.
* injector = Audio.playSound(sound, injectorOptions);
* }, 1000);
*
* Script.setTimeout(function () {
* print("Sound is playing: " + injector.isPlaying());
* }, 2000);
*/
bool isPlaying() const { return _injector->isPlaying(); } bool isPlaying() const { return _injector->isPlaying(); }
signals: signals:
/**jsdoc
* Triggered when the audio has finished playing.
* @function AudioInjector.finished
* @returns {Signal}
* @example <caption>Report when a sound has finished playing.</caption>
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* var injector;
* var injectorOptions = {
* position: MyAvatar.position
* };
*
* Script.setTimeout(function () { // Give the sound time to load.
* injector = Audio.playSound(sound, injectorOptions);
* injector.finished.connect(function () {
* print("Finished playing sound");
* });
* }, 1000);
*/
void finished(); void finished();
protected slots: protected slots:
/**jsdoc
* Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.)
* @function AudioInjector.stopInjectorImmediately
*/
void stopInjectorImmediately(); void stopInjectorImmediately();
private: private:
AudioInjectorPointer _injector; AudioInjectorPointer _injector;

View file

@ -59,7 +59,11 @@ const int32_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC;
// MY_AVATAR does not collide with itself // MY_AVATAR does not collide with itself
const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR); const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR);
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT; // OTHER_AVATARs are dynamic, but are slammed to whatever the avatar-mixer says, which means
// their motion can't actually be affected by the local physics simulation -- we rely on the remote simulation
// to move its avatar around correctly and to communicate its motion through the avatar-mixer.
// Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR;
// COLLISIONLESS gets an empty mask. // COLLISIONLESS gets an empty mask.
const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;

View file

@ -20,10 +20,10 @@
std::mutex QmlFragmentClass::_mutex; std::mutex QmlFragmentClass::_mutex;
std::map<QString, QScriptValue> QmlFragmentClass::_fragments; std::map<QString, QScriptValue> QmlFragmentClass::_fragments;
QmlFragmentClass::QmlFragmentClass(QString id) : qml(id) { } QmlFragmentClass::QmlFragmentClass(bool restricted, QString id) : QmlWindowClass(restricted), qml(id) { }
// Method called by Qt scripts to create a new bottom menu bar in Android // Method called by Qt scripts to create a new bottom menu bar in Android
QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) {
std::lock_guard<std::mutex> guard(_mutex); std::lock_guard<std::mutex> guard(_mutex);
auto qml = context->argument(0).toVariant().toMap().value("qml"); auto qml = context->argument(0).toVariant().toMap().value("qml");
@ -41,7 +41,7 @@ QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngin
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlFragmentClass* retVal = new QmlFragmentClass(qml.toString()); QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString());
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {
retVal->moveToThread(qApp->thread()); retVal->moveToThread(qApp->thread());

View file

@ -13,9 +13,19 @@
class QmlFragmentClass : public QmlWindowClass { class QmlFragmentClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
private:
static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted);
public: public:
QmlFragmentClass(QString id); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) {
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); return internal_constructor(context, engine, false);
}
static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){
return internal_constructor(context, engine, true);
}
QmlFragmentClass(bool restricted, QString id);
/**jsdoc /**jsdoc
* Creates a new button, adds it to this and returns it. * Creates a new button, adds it to this and returns it.

View file

@ -20,10 +20,10 @@ static const char* const URL_PROPERTY = "source";
static const char* const SCRIPT_PROPERTY = "scriptUrl"; static const char* const SCRIPT_PROPERTY = "scriptUrl";
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) {
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlWebWindowClass* retVal = new QmlWebWindowClass(); QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted);
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {
retVal->moveToThread(qApp->thread()); retVal->moveToThread(qApp->thread());

View file

@ -57,8 +57,18 @@ class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString url READ getURL CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT)
private:
static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted);
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); QmlWebWindowClass(bool restricted) : QmlWindowClass(restricted) {}
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) {
return internal_constructor(context, engine, false);
}
static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){
return internal_constructor(context, engine, true);
}
public slots: public slots:

View file

@ -25,6 +25,8 @@
#include <shared/QtHelpers.h> #include <shared/QtHelpers.h>
#include "OffscreenUi.h" #include "OffscreenUi.h"
#include "ui/types/HFWebEngineProfile.h"
#include "ui/types/FileTypeProfile.h"
static const char* const SOURCE_PROPERTY = "source"; static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title"; static const char* const TITLE_PROPERTY = "title";
@ -68,10 +70,10 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) {
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlWindowClass* retVal = new QmlWindowClass(); QmlWindowClass* retVal = new QmlWindowClass(restricted);
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {
retVal->moveToThread(qApp->thread()); retVal->moveToThread(qApp->thread());
@ -83,7 +85,7 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
return engine->newQObject(retVal); return engine->newQObject(retVal);
} }
QmlWindowClass::QmlWindowClass() { QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) {
} }
@ -99,8 +101,7 @@ void QmlWindowClass::initQml(QVariantMap properties) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
_source = properties[SOURCE_PROPERTY].toString(); _source = properties[SOURCE_PROPERTY].toString();
// Build the event bridge and wrapper on the main thread auto objectInitLambda = [&](QQmlContext* context, QObject* object) {
offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
_qmlWindow = object; _qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
@ -128,7 +129,24 @@ void QmlWindowClass::initQml(QVariantMap properties) {
if (metaObject->indexOfSignal("moved") >= 0) if (metaObject->indexOfSignal("moved") >= 0)
connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
}); };
auto contextInitLambda = [&](QQmlContext* context) {
#if !defined(Q_OS_ANDROID)
// If the restricted flag is on, override the FileTypeProfile and HFWebEngineProfile objects in the
// QML surface root context with local ones
qDebug() << "Context initialization lambda";
if (_restricted) {
qDebug() << "Restricting web content";
ContextAwareProfile::restrictContext(context);
FileTypeProfile::registerWithContext(context);
HFWebEngineProfile::registerWithContext(context);
}
#endif
};
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(qmlSource(), objectInitLambda, contextInitLambda);
Q_ASSERT(_qmlWindow); Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data())); Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));

View file

@ -38,9 +38,18 @@ class QmlWindowClass : public QObject {
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged)
private:
static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted);
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWindowClass(); return internal_constructor(context, engine, false);
}
static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){
return internal_constructor(context, engine, true);
}
QmlWindowClass(bool restricted);
~QmlWindowClass(); ~QmlWindowClass();
/**jsdoc /**jsdoc
@ -51,6 +60,8 @@ public:
QQuickItem* asQuickItem() const; QQuickItem* asQuickItem() const;
public slots: public slots:
/**jsdoc /**jsdoc
@ -250,10 +261,12 @@ protected:
QPointer<QObject> _qmlWindow; QPointer<QObject> _qmlWindow;
QString _source; QString _source;
const bool _restricted;
private: private:
// QmlWindow content may include WebView requiring EventBridge. // QmlWindow content may include WebView requiring EventBridge.
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false); void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
}; };
#endif #endif

View file

@ -265,19 +265,6 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) {
if (!javaScriptToInject.isEmpty()) { if (!javaScriptToInject.isEmpty()) {
rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
} }
#if !defined(Q_OS_ANDROID)
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
{
PROFILE_RANGE(startup, "FileTypeProfile");
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
}
{
PROFILE_RANGE(startup, "HFWebEngineProfile");
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
}
#endif
rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data()); rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data()); rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
rootContext->setContextProperty("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data()); rootContext->setContextProperty("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
@ -300,6 +287,17 @@ void OffscreenQmlSurface::onRootContextCreated(QQmlContext* qmlContext) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated // Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, qmlContext)); qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, qmlContext));
#if !defined(Q_OS_ANDROID)
{
PROFILE_RANGE(startup, "FileTypeProfile");
FileTypeProfile::registerWithContext(qmlContext);
}
{
PROFILE_RANGE(startup, "HFWebEngineProfile");
HFWebEngineProfile::registerWithContext(qmlContext);
}
#endif
} }
QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) {

View file

@ -334,6 +334,8 @@ static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml";
class TabletRootWindow : public QmlWindowClass { class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
public:
TabletRootWindow() : QmlWindowClass(false) {}
}; };
TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) { TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) {

View file

@ -0,0 +1,32 @@
//
// FileTypeProfile.cpp
// interface/src/networking
//
// Created by Kunal Gosar on 2017-03-10.
// Copyright 2017 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
//
#include "ContextAwareProfile.h"
#if !defined(Q_OS_ANDROID)
#include <QtQml/QQmlContext>
static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess";
ContextAwareProfile::ContextAwareProfile(QQmlContext* parent) :
QQuickWebEngineProfile(parent), _context(parent) { }
void ContextAwareProfile::restrictContext(QQmlContext* context) {
context->setContextProperty(RESTRICTED_FLAG_PROPERTY, true);
}
bool ContextAwareProfile::isRestricted(QQmlContext* context) {
return context->contextProperty(RESTRICTED_FLAG_PROPERTY).toBool();
}
#endif

View file

@ -0,0 +1,42 @@
//
// Created by Bradley Austin Davis on 2018/07/27
// Copyright 2013-2018 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
//
#pragma once
#ifndef hifi_ContextAwareProfile_h
#define hifi_ContextAwareProfile_h
#include <QtCore/QtGlobal>
#if !defined(Q_OS_ANDROID)
#include <QtWebEngine/QQuickWebEngineProfile>
#include <QtWebEngineCore/QWebEngineUrlRequestInterceptor>
class QQmlContext;
class ContextAwareProfile : public QQuickWebEngineProfile {
public:
static void restrictContext(QQmlContext* context);
static bool isRestricted(QQmlContext* context);
QQmlContext* getContext() const { return _context; }
protected:
class RequestInterceptor : public QWebEngineUrlRequestInterceptor {
public:
RequestInterceptor(ContextAwareProfile* parent) : QWebEngineUrlRequestInterceptor(parent), _profile(parent) {}
QQmlContext* getContext() const { return _profile->getContext(); }
protected:
ContextAwareProfile* _profile;
};
ContextAwareProfile(QQmlContext* parent);
QQmlContext* _context;
};
#endif
#endif // hifi_FileTypeProfile_h

View file

@ -11,18 +11,31 @@
#include "FileTypeProfile.h" #include "FileTypeProfile.h"
#include "FileTypeRequestInterceptor.h" #include <QtQml/QQmlContext>
#include "RequestFilters.h"
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine";
FileTypeProfile::FileTypeProfile(QObject* parent) : FileTypeProfile::FileTypeProfile(QQmlContext* parent) :
QQuickWebEngineProfile(parent) ContextAwareProfile(parent)
{ {
static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)";
setHttpUserAgent(WEB_ENGINE_USER_AGENT); setHttpUserAgent(WEB_ENGINE_USER_AGENT);
auto requestInterceptor = new FileTypeRequestInterceptor(this); auto requestInterceptor = new RequestInterceptor(this);
setRequestInterceptor(requestInterceptor); setRequestInterceptor(requestInterceptor);
} }
void FileTypeProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
RequestFilters::interceptHFWebEngineRequest(info, getContext());
RequestFilters::interceptFileType(info, getContext());
}
void FileTypeProfile::registerWithContext(QQmlContext* context) {
context->setContextProperty("FileTypeProfile", new FileTypeProfile(context));
}
#endif #endif

View file

@ -17,12 +17,23 @@
#include <QtCore/QtGlobal> #include <QtCore/QtGlobal>
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
#include <QtWebEngine/QQuickWebEngineProfile> #include "ContextAwareProfile.h"
class FileTypeProfile : public ContextAwareProfile {
using Parent = ContextAwareProfile;
class FileTypeProfile : public QQuickWebEngineProfile {
public: public:
FileTypeProfile(QObject* parent = Q_NULLPTR); static void registerWithContext(QQmlContext* parent);
protected:
FileTypeProfile(QQmlContext* parent);
class RequestInterceptor : public Parent::RequestInterceptor {
public:
RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {}
void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
}; };
#endif #endif
#endif // hifi_FileTypeProfile_h #endif // hifi_FileTypeProfile_h

View file

@ -1,25 +0,0 @@
//
// FileTypeRequestInterceptor.cpp
// interface/src/networking
//
// Created by Kunal Gosar on 2017-03-10.
// Copyright 2017 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
//
#include "FileTypeRequestInterceptor.h"
#include <QtCore/QDebug>
#include "RequestFilters.h"
#if !defined(Q_OS_ANDROID)
void FileTypeRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
RequestFilters::interceptHFWebEngineRequest(info);
RequestFilters::interceptFileType(info);
}
#endif

View file

@ -1,30 +0,0 @@
//
// FileTypeRequestInterceptor.h
// interface/src/networking
//
// Created by Kunal Gosar on 2017-03-10.
// Copyright 2017 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
//
#pragma once
#ifndef hifi_FileTypeRequestInterceptor_h
#define hifi_FileTypeRequestInterceptor_h
#include <QtCore/QtGlobal>
#if !defined(Q_OS_ANDROID)
#include <QWebEngineUrlRequestInterceptor>
class FileTypeRequestInterceptor : public QWebEngineUrlRequestInterceptor {
public:
FileTypeRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {};
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
#endif
#endif // hifi_FileTypeRequestInterceptor_h

View file

@ -1,23 +0,0 @@
//
// HFTabletWebEngineProfile.h
// interface/src/networking
//
// Created by Dante Ruiz on 2017-03-31.
// Copyright 2017 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
//
#ifndef hifi_HFTabletWebEngineProfile_h
#define hifi_HFTabletWebEngineProfile_h
#include <QtWebEngine/QQuickWebEngineProfile>
class HFTabletWebEngineProfile : public QQuickWebEngineProfile {
public:
HFTabletWebEngineProfile(QObject* parent = Q_NULLPTR);
};
#endif // hifi_HFTabletWebEngineProfile_h

View file

@ -1,30 +0,0 @@
//
// HFTabletWebEngineRequestInterceptor.h
// interface/src/networking
//
// Created by Dante Ruiz on 2017-3-31.
// Copyright 2017 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
//
#ifndef hifi_HFTabletWebEngineRequestInterceptor_h
#define hifi_HFTabletWebEngineRequestInterceptor_h
#if !defined(Q_OS_ANDROID)
#include <QtCore/QObject>
#include <QWebEngineUrlRequestInterceptor>
class HFTabletWebEngineRequestInterceptor
: public QWebEngineUrlRequestInterceptor
{
public:
HFTabletWebEngineRequestInterceptor(QObject* parent)
: QWebEngineUrlRequestInterceptor(parent)
{};
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
#endif
#endif // hifi_HFWebEngineRequestInterceptor_h

View file

@ -11,20 +11,28 @@
#include "HFWebEngineProfile.h" #include "HFWebEngineProfile.h"
#include "HFWebEngineRequestInterceptor.h" #include <QtQml/QQmlContext>
#include "RequestFilters.h"
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine";
HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : HFWebEngineProfile::HFWebEngineProfile(QQmlContext* parent) : Parent(parent)
QQuickWebEngineProfile(parent)
{ {
setStorageName(QML_WEB_ENGINE_STORAGE_NAME); setStorageName(QML_WEB_ENGINE_STORAGE_NAME);
// we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user
auto requestInterceptor = new HFWebEngineRequestInterceptor(this); setRequestInterceptor(new RequestInterceptor(this));
setRequestInterceptor(requestInterceptor); }
void HFWebEngineProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
RequestFilters::interceptHFWebEngineRequest(info, getContext());
}
void HFWebEngineProfile::registerWithContext(QQmlContext* context) {
context->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(context));
} }
#endif #endif

View file

@ -14,15 +14,24 @@
#ifndef hifi_HFWebEngineProfile_h #ifndef hifi_HFWebEngineProfile_h
#define hifi_HFWebEngineProfile_h #define hifi_HFWebEngineProfile_h
#include <QtCore/QtGlobal> #include "ContextAwareProfile.h"
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
#include <QtWebEngine/QQuickWebEngineProfile>
class HFWebEngineProfile : public QQuickWebEngineProfile { class HFWebEngineProfile : public ContextAwareProfile {
using Parent = ContextAwareProfile;
public: public:
HFWebEngineProfile(QObject* parent = Q_NULLPTR); static void registerWithContext(QQmlContext* parent);
protected:
HFWebEngineProfile(QQmlContext* parent);
class RequestInterceptor : public Parent::RequestInterceptor {
public:
RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {}
void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
}; };
#endif #endif
#endif // hifi_HFWebEngineProfile_h #endif // hifi_HFWebEngineProfile_h

View file

@ -1,25 +0,0 @@
//
// HFWebEngineRequestInterceptor.cpp
// interface/src/networking
//
// Created by Stephen Birarda on 2016-10-14.
// 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
//
#include "HFWebEngineRequestInterceptor.h"
#include <QtCore/QDebug>
#include "AccountManager.h"
#include "RequestFilters.h"
#if !defined(Q_OS_ANDROID)
void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
RequestFilters::interceptHFWebEngineRequest(info);
}
#endif

View file

@ -1,30 +0,0 @@
//
// HFWebEngineRequestInterceptor.h
// interface/src/networking
//
// Created by Stephen Birarda on 2016-10-14.
// 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
//
#pragma once
#ifndef hifi_HFWebEngineRequestInterceptor_h
#define hifi_HFWebEngineRequestInterceptor_h
#include <QtCore/QtGlobal>
#if !defined(Q_OS_ANDROID)
#include <QWebEngineUrlRequestInterceptor>
class HFWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor {
public:
HFWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {};
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
#endif
#endif // hifi_HFWebEngineRequestInterceptor_h

View file

@ -10,12 +10,15 @@
// //
#include "RequestFilters.h" #include "RequestFilters.h"
#include "NetworkingConstants.h"
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <SettingHandle.h> #include <QtCore/QFileInfo>
#include "AccountManager.h" #include <SettingHandle.h>
#include <NetworkingConstants.h>
#include <AccountManager.h>
#include "ContextAwareProfile.h"
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
@ -42,9 +45,29 @@ namespace {
return filename.endsWith(".json", Qt::CaseInsensitive); return filename.endsWith(".json", Qt::CaseInsensitive);
} }
bool blockLocalFiles(QWebEngineUrlRequestInfo& info) {
auto requestUrl = info.requestUrl();
if (!requestUrl.isLocalFile()) {
// Not a local file, do not block
return false;
}
// We can potentially add whitelisting logic or development environment variables that
// will allow people to override this setting on a per-client basis here.
QString targetFilePath = QFileInfo(requestUrl.toLocalFile()).canonicalFilePath();
// If we get here, we've determined it's a local file and we have no reason not to block it
qWarning() << "Blocking web access to local file path" << targetFilePath;
info.block(true);
return true;
}
} }
void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) { void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context) {
if (ContextAwareProfile::isRestricted(context) && blockLocalFiles(info)) {
return;
}
// check if this is a request to a highfidelity URL // check if this is a request to a highfidelity URL
bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); bool isAuthable = isAuthableHighFidelityURL(info.requestUrl());
if (isAuthable) { if (isAuthable) {
@ -71,7 +94,7 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info)
info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit());
} }
void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context) {
QString filename = info.requestUrl().fileName(); QString filename = info.requestUrl().fileName();
if (isScript(filename) || isJSON(filename)) { if (isScript(filename) || isJSON(filename)) {
static const QString CONTENT_HEADER = "Accept"; static const QString CONTENT_HEADER = "Accept";

View file

@ -20,10 +20,12 @@
#include <QObject> #include <QObject>
#include <QWebEngineUrlRequestInfo> #include <QWebEngineUrlRequestInfo>
class QQmlContext;
class RequestFilters : public QObject { class RequestFilters : public QObject {
public: public:
static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info); static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context);
static void interceptFileType(QWebEngineUrlRequestInfo& info); static void interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context);
}; };
#endif #endif