diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 44f27d3d02..1c9c5a9796 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -13,13 +13,13 @@ We no longer require install of qt5 via our [homebrew formulas repository](https Assuming you've installed OpenSSL or Qt 5 using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR and QT_CMAKE_PREFIX_PATH so CMake can find your installations. For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: - export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2d_1 + export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/ For Qt 5.5.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows. - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.5.1_2/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt55/5.5.1/lib/cmake -Not that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. +Note that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/cmake/externals/neuron/CMakeLists.txt b/cmake/externals/neuron/CMakeLists.txt index 6936725571..76dda8f8c5 100644 --- a/cmake/externals/neuron/CMakeLists.txt +++ b/cmake/externals/neuron/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME neuron) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.zip") -set(NEURON_URL_MD5 "0ab54ca04c9cc8094e0fa046c226e574") +set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.2.zip") +set(NEURON_URL_MD5 "84273ad2200bf86a9279d1f412a822ca") ExternalProject_Add(${EXTERNAL_NAME} URL ${NEURON_URL} diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bad24dd3a1..59fa9c4f3d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.4, + "version": 1.5, "settings": [ { "name": "metaverse", @@ -116,6 +116,7 @@ "name": "hosts", "label": "Hosts", "type": "table", + "can_add_new_rows": true, "help": "Usernames of hosts who can reliably show your domain to new visitors.", "numbered": false, "columns": [ @@ -130,6 +131,7 @@ "name": "tags", "label": "Tags", "type": "table", + "can_add_new_rows": true, "help": "Common categories under which your domain falls.", "numbered": false, "columns": [ @@ -139,6 +141,218 @@ "can_set": true } ] + }, + { + "label": "Operating Hours", + "help": "\"Open\" domains can be searched using their operating hours. Hours are entered in the local timezone, selected below.", + + "name": "weekday_hours", + "caption": "Weekday Hours (Monday-Friday)", + "type": "table", + "can_add_new_rows": false, + "columns": [ + { + "name": "open", + "label": "Opening Time", + "type": "time", + "default": "00:00", + "editable": true + }, + { + "name": "close", + "label": "Closing Time", + "type": "time", + "default": "23:59", + "editable": true + } + ] + }, + { + "name": "weekend_hours", + "label": "Weekend Hours (Saturday/Sunday)", + "type": "table", + "can_add_new_rows": false, + "columns": [ + { + "name": "open", + "label": "Opening Time", + "type": "time", + "default": "00:00", + "editable": true + }, + { + "name": "close", + "label": "Closing Time", + "type": "time", + "default": "23:59", + "editable": true + } + ] + }, + { + "label": "Time Zone", + "name": "utc_offset", + "caption": "Time Zone", + "help": "This server's time zone. Used to define your server's operating hours.", + "type": "select", + "options": [ + { + "value": "-12", + "label": "UTC-12:00" + }, + { + "value": "-11", + "label": "UTC-11:00" + }, + { + "value": "-10", + "label": "UTC-10:00" + }, + { + "value": "-9.5", + "label": "UTC-09:30" + }, + { + "value": "-9", + "label": "UTC-09:00" + }, + { + "value": "-8", + "label": "UTC-08:00" + }, + { + "value": "-7", + "label": "UTC-07:00" + }, + { + "value": "-6", + "label": "UTC-06:00" + }, + { + "value": "-5", + "label": "UTC-05:00" + }, + { + "value": "-4", + "label": "UTC-04:00" + }, + { + "value": "-3.5", + "label": "UTC-03:30" + }, + { + "value": "-3", + "label": "UTC-03:00" + }, + { + "value": "-2", + "label": "UTC-02:00" + }, + { + "value": "-1", + "label": "UTC-01:00" + }, + { + "value": "", + "label": "UTC±00:00" + }, + { + "value": "1", + "label": "UTC+01:00" + }, + { + "value": "2", + "label": "UTC+02:00" + }, + { + "value": "3", + "label": "UTC+03:00" + }, + { + "value": "3.5", + "label": "UTC+03:30" + }, + { + "value": "4", + "label": "UTC+04:00" + }, + { + "value": "4.5", + "label": "UTC+04:30" + }, + { + "value": "5", + "label": "UTC+05:00" + }, + { + "value": "5.5", + "label": "UTC+05:30" + }, + { + "value": "5.75", + "label": "UTC+05:45" + }, + { + "value": "6", + "label": "UTC+06:00" + }, + { + "value": "6.5", + "label": "UTC+06:30" + }, + { + "value": "7", + "label": "UTC+07:00" + }, + { + "value": "8", + "label": "UTC+08:00" + }, + { + "value": "8.5", + "label": "UTC+08:30" + }, + { + "value": "8.75", + "label": "UTC+08:45" + }, + { + "value": "9", + "label": "UTC+09:00" + }, + { + "value": "9.5", + "label": "UTC+09:30" + }, + { + "value": "10", + "label": "UTC+10:00" + }, + { + "value": "10.5", + "label": "UTC+10:30" + }, + { + "value": "11", + "label": "UTC+11:00" + }, + { + "value": "12", + "label": "UTC+12:00" + }, + { + "value": "12.75", + "label": "UTC+12:45" + }, + { + "value": "13", + "label": "UTC+13:00" + }, + { + "value": "14", + "label": "UTC+14:00" + } + ] } ] }, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index aecc48b31f..c2cb2ecb80 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -243,6 +243,16 @@ $(document).ready(function(){ } }); + $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeSidebarForDifferences($(this)); + } + }); + $('.advanced-toggle').click(function(){ Settings.showAdvanced = !Settings.showAdvanced var advancedSelector = $('.' + Settings.ADVANCED_CLASS) @@ -987,7 +997,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + rowIndexOrName + "" } - var isNonDeletableRow = false; + var isNonDeletableRow = !setting.can_add_new_rows; _.each(setting.columns, function(col) { @@ -1007,6 +1017,10 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + ""; + } else if (isArray && col.type === "time" && col.editable) { + html += "" + + ""; } else { // Use a hidden input so that the values are posted. html += "" @@ -1196,15 +1210,21 @@ function addTableRow(add_glyphicon) { // Hide inputs var input = $(element).find("input") var isCheckbox = false; + var isTime = false; if (input.hasClass("table-checkbox")) { input = $(input).parent(); isCheckbox = true; + } else if (input.hasClass("table-time")) { + input = $(input).parent(); + isTime = true; } var val = input.val(); if (isCheckbox) { - val = $(input).find("input").is(':checked'); // don't hide the checkbox + val = $(input).find("input").is(':checked'); + } else if (isTime) { + // don't hide the time } else { input.attr("type", "hidden") } diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index 26d2bb87ce..c5048ea9d8 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -10,16 +10,18 @@ #include "DomainMetadata.h" -#include +#include #include +#include #include +#include "DomainServer.h" #include "DomainServerNodeData.h" const QString DomainMetadata::USERS = "users"; -const QString DomainMetadata::USERS_NUM_TOTAL = "num_users"; -const QString DomainMetadata::USERS_NUM_ANON = "num_anon_users"; -const QString DomainMetadata::USERS_HOSTNAMES = "user_hostnames"; +const QString DomainMetadata::Users::NUM_TOTAL = "num_users"; +const QString DomainMetadata::Users::NUM_ANON = "num_anon_users"; +const QString DomainMetadata::Users::HOSTNAMES = "user_hostnames"; // users metadata will appear as (JSON): // { "num_users": Number, // "num_anon_users": Number, @@ -27,26 +29,30 @@ const QString DomainMetadata::USERS_HOSTNAMES = "user_hostnames"; // } const QString DomainMetadata::DESCRIPTORS = "descriptors"; -const QString DomainMetadata::DESCRIPTORS_DESCRIPTION = "description"; -const QString DomainMetadata::DESCRIPTORS_CAPACITY = "capacity"; // parsed from security -const QString DomainMetadata::DESCRIPTORS_RESTRICTION = "restriction"; // parsed from ACL -const QString DomainMetadata::DESCRIPTORS_MATURITY = "maturity"; -const QString DomainMetadata::DESCRIPTORS_HOSTS = "hosts"; -const QString DomainMetadata::DESCRIPTORS_TAGS = "tags"; +const QString DomainMetadata::Descriptors::DESCRIPTION = "description"; +const QString DomainMetadata::Descriptors::CAPACITY = "capacity"; // parsed from security +const QString DomainMetadata::Descriptors::RESTRICTION = "restriction"; // parsed from ACL +const QString DomainMetadata::Descriptors::MATURITY = "maturity"; +const QString DomainMetadata::Descriptors::HOSTS = "hosts"; +const QString DomainMetadata::Descriptors::TAGS = "tags"; +const QString DomainMetadata::Descriptors::HOURS = "hours"; +const QString DomainMetadata::Descriptors::Hours::WEEKDAY = "weekday"; +const QString DomainMetadata::Descriptors::Hours::WEEKEND = "weekend"; +const QString DomainMetadata::Descriptors::Hours::UTC_OFFSET = "utc_offset"; +const QString DomainMetadata::Descriptors::Hours::OPEN = "open"; +const QString DomainMetadata::Descriptors::Hours::CLOSE = "close"; // descriptors metadata will appear as (JSON): -// { "capacity": Number, -// TODO: "hours": String, // UTF-8 representation of the week, split into 15" segments +// { "description": String, // capped description +// "capacity": Number, // "restriction": String, // enum of either open, hifi, or acl // "maturity": String, // enum corresponding to ESRB ratings // "hosts": [ String ], // capped list of usernames -// "description": String, // capped description -// TODO: "img": { -// "src": String, -// "type": String, -// "size": Number, -// "updated_at": Number, -// }, // "tags": [ String ], // capped list of tags +// "hours": { +// "utc_offset": Number, +// "weekday": [ { "open": Time, "close": Time } ], +// "weekend": [ { "open": Time, "close": Time } ], +// } // } // metadata will appear as (JSON): @@ -54,36 +60,167 @@ const QString DomainMetadata::DESCRIPTORS_TAGS = "tags"; // // it is meant to be sent to and consumed by an external API -DomainMetadata::DomainMetadata() { - _metadata[USERS] = {}; - _metadata[DESCRIPTORS] = {}; +DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) { + // set up the structure necessary for casting during parsing (see parseHours, esp.) + _metadata[USERS] = QVariantMap {}; + _metadata[DESCRIPTORS] = QVariantMap { { + Descriptors::HOURS, QVariantMap { + { Descriptors::Hours::WEEKDAY, QVariantList { + QVariantList{ QVariant{}, QVariant{} } } + }, + { Descriptors::Hours::WEEKEND, QVariantList { + QVariantList{ QVariant{}, QVariant{} } } + } + } + } }; + + assert(dynamic_cast(domainServer)); + DomainServer* server = static_cast(domainServer); + + // update the metadata when a user (dis)connects + connect(server, &DomainServer::userConnected, this, &DomainMetadata::usersChanged); + connect(server, &DomainServer::userDisconnected, this, &DomainMetadata::usersChanged); + + // update the metadata when security changes + connect(&server->_settingsManager, &DomainServerSettingsManager::updateNodePermissions, + this, static_cast(&DomainMetadata::securityChanged)); + + // initialize the descriptors + securityChanged(false); + descriptorsChanged(); } -void DomainMetadata::setDescriptors(QVariantMap& settings) { +QJsonObject DomainMetadata::get() { + maybeUpdateUsers(); + return QJsonObject::fromVariantMap(_metadata); +} + +QJsonObject DomainMetadata::get(const QString& group) { + maybeUpdateUsers(); + return QJsonObject::fromVariantMap(_metadata[group].toMap()); +} + +// merge delta into target +// target should be of the form [ OpenTime, CloseTime ], +// delta should be of the form [ { open: Time, close: Time } ] +void parseHours(QVariant delta, QVariant& target) { + using Hours = DomainMetadata::Descriptors::Hours; + + assert(target.canConvert()); + auto& targetList = *static_cast(target.data()); + + // if/when multiple ranges are allowed, this list will need to be iterated + assert(targetList[0].canConvert()); + auto& hours = *static_cast(targetList[0].data()); + + auto deltaHours = delta.toList()[0].toMap(); + if (deltaHours.isEmpty()) { + return; + } + + // merge delta into base + static const int OPEN_INDEX = 0; + static const int CLOSE_INDEX = 1; + auto open = deltaHours.find(Hours::OPEN); + if (open != deltaHours.end()) { + hours[OPEN_INDEX] = open.value(); + } + assert(hours[OPEN_INDEX].canConvert()); + auto close = deltaHours.find(Hours::CLOSE); + if (close != deltaHours.end()) { + hours[CLOSE_INDEX] = close.value(); + } + assert(hours[CLOSE_INDEX].canConvert()); +} + +void DomainMetadata::descriptorsChanged() { + // get descriptors + assert(_metadata[DESCRIPTORS].canConvert()); + auto& state = *static_cast(_metadata[DESCRIPTORS].data()); + auto settings = static_cast(parent())->_settingsManager.getSettingsMap(); + auto descriptors = settings[DESCRIPTORS].toMap(); + + // copy simple descriptors (description/maturity) + state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; + state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; + + // copy array descriptors (hosts/tags) + state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); + state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); + + // parse capacity const QString CAPACITY = "security.maximum_user_capacity"; const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; + state[Descriptors::CAPACITY] = capacity; - // TODO: Keep parity with ACL development. - const QString RESTRICTION = "security.restricted_access"; - const QString RESTRICTION_OPEN = "open"; - // const QString RESTRICTION_HIFI = "hifi"; - const QString RESTRICTION_ACL = "acl"; - const QVariant* isRestrictedVariant = valueForKeyPath(settings, RESTRICTION); - bool isRestricted = isRestrictedVariant ? isRestrictedVariant->toBool() : false; - QString restriction = isRestricted ? RESTRICTION_ACL : RESTRICTION_OPEN; - - QVariantMap descriptors = settings[DESCRIPTORS].toMap(); - descriptors[DESCRIPTORS_CAPACITY] = capacity; - descriptors[DESCRIPTORS_RESTRICTION] = restriction; - _metadata[DESCRIPTORS] = descriptors; + // parse operating hours + const QString WEEKDAY_HOURS = "weekday_hours"; + const QString WEEKEND_HOURS = "weekend_hours"; + const QString UTC_OFFSET = "utc_offset"; + assert(state[Descriptors::HOURS].canConvert()); + auto& hours = *static_cast(state[Descriptors::HOURS].data()); + parseHours(descriptors.take(WEEKDAY_HOURS), hours[Descriptors::Hours::WEEKDAY]); + parseHours(descriptors.take(WEEKEND_HOURS), hours[Descriptors::Hours::WEEKEND]); + hours[Descriptors::Hours::UTC_OFFSET] = descriptors.take(UTC_OFFSET); #if DEV_BUILD || PR_BUILD - qDebug() << "Domain metadata descriptors set:" << descriptors; + qDebug() << "Domain metadata descriptors set:" << QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap()); +#endif + + sendDescriptors(); +} + +void DomainMetadata::securityChanged(bool send) { + // get descriptors + assert(_metadata[DESCRIPTORS].canConvert()); + auto& state = *static_cast(_metadata[DESCRIPTORS].data()); + + const QString RESTRICTION_OPEN = "open"; + const QString RESTRICTION_ANON = "anon"; + const QString RESTRICTION_HIFI = "hifi"; + const QString RESTRICTION_ACL = "acl"; + + QString restriction; + + const auto& settingsManager = static_cast(parent())->_settingsManager; + bool hasAnonymousAccess = + settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain; + bool hasHifiAccess = + settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain; + if (hasAnonymousAccess) { + restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON; + } else if (hasHifiAccess) { + restriction = RESTRICTION_HIFI; + } else { + restriction = RESTRICTION_ACL; + } + + state[Descriptors::RESTRICTION] = restriction; + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata restriction set:" << restriction; +#endif + + if (send) { + sendDescriptors(); + } +} + +void DomainMetadata::usersChanged() { + ++_tic; + +#if DEV_BUILD || PR_BUILD + qDebug() << "Domain metadata users change detected"; #endif } -void DomainMetadata::updateUsers() { +void DomainMetadata::maybeUpdateUsers() { + if (_lastTic == _tic) { + return; + } + _lastTic = _tic; + static const QString DEFAULT_HOSTNAME = "*"; auto nodeList = DependencyManager::get(); @@ -112,21 +249,32 @@ void DomainMetadata::updateUsers() { } }); - QVariantMap users = { - { USERS_NUM_TOTAL, numConnected }, - { USERS_NUM_ANON, numConnectedAnonymously }, - { USERS_HOSTNAMES, userHostnames }}; - _metadata[USERS] = users; + assert(_metadata[USERS].canConvert()); + auto& users = *static_cast(_metadata[USERS].data()); + users[Users::NUM_TOTAL] = numConnected; + users[Users::NUM_ANON] = numConnectedAnonymously; + users[Users::HOSTNAMES] = userHostnames; #if DEV_BUILD || PR_BUILD - qDebug() << "Domain metadata users updated:" << users; + qDebug() << "Domain metadata users set:" << QJsonObject::fromVariantMap(_metadata[USERS].toMap()); #endif } -void DomainMetadata::usersChanged() { - ++_tic; +void DomainMetadata::sendDescriptors() { + QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); + const QUuid& domainID = DependencyManager::get()->getSessionUUID(); + if (!domainID.isNull()) { + static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; + QString path { DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)) }; + DependencyManager::get()->sendRequest(path, + AccountManagerAuth::Required, + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), + domainUpdateJSON.toUtf8()); #if DEV_BUILD || PR_BUILD - qDebug() << "Domain metadata users change detected"; + qDebug() << "Domain metadata sent to" << path; + qDebug() << "Domain metadata update:" << domainUpdateJSON; #endif + } } diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index 7d58d43182..41f3a60832 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -19,46 +19,56 @@ class DomainMetadata : public QObject { Q_OBJECT +public: + using Tic = uint32_t; + static const QString USERS; - static const QString USERS_NUM_TOTAL; - static const QString USERS_NUM_ANON; - static const QString USERS_HOSTNAMES; + class Users { + public: + static const QString NUM_TOTAL; + static const QString NUM_ANON; + static const QString HOSTNAMES; + }; static const QString DESCRIPTORS; - static const QString DESCRIPTORS_DESCRIPTION; - static const QString DESCRIPTORS_CAPACITY; - static const QString DESCRIPTORS_HOURS; - static const QString DESCRIPTORS_RESTRICTION; - static const QString DESCRIPTORS_MATURITY; - static const QString DESCRIPTORS_HOSTS; - static const QString DESCRIPTORS_TAGS; - static const QString DESCRIPTORS_IMG; - static const QString DESCRIPTORS_IMG_SRC; - static const QString DESCRIPTORS_IMG_TYPE; - static const QString DESCRIPTORS_IMG_SIZE; - static const QString DESCRIPTORS_IMG_UPDATED_AT; + class Descriptors { + public: + static const QString DESCRIPTION; + static const QString CAPACITY; + static const QString RESTRICTION; + static const QString MATURITY; + static const QString HOSTS; + static const QString TAGS; + static const QString HOURS; + class Hours { + public: + static const QString WEEKDAY; + static const QString WEEKEND; + static const QString UTC_OFFSET; + static const QString OPEN; + static const QString CLOSE; + }; + }; -public: - DomainMetadata(); + DomainMetadata(QObject* domainServer); + DomainMetadata() = delete; - // Returns the last set metadata - // If connected users have changed, metadata may need to be updated - // this should be checked by storing tic = getTic() between calls - // and testing it for equality before the next get (tic == getTic()) - QJsonObject get() { return QJsonObject::fromVariantMap(_metadata); } - QJsonObject getUsers() { return QJsonObject::fromVariantMap(_metadata[USERS].toMap()); } - QJsonObject getDescriptors() { return QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap()); } - - uint32_t getTic() { return _tic; } - - void setDescriptors(QVariantMap& settings); - void updateUsers(); + // Get cached metadata + QJsonObject get(); + QJsonObject get(const QString& group); public slots: + void descriptorsChanged(); + void securityChanged(bool send); + void securityChanged() { securityChanged(true); } void usersChanged(); protected: + void maybeUpdateUsers(); + void sendDescriptors(); + QVariantMap _metadata; + uint32_t _lastTic{ (uint32_t)-1 }; uint32_t _tic{ 0 }; }; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7c596bb187..223cab61da 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -94,10 +94,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : qRegisterMetaType("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators("DomainServerWebSessionData"); - // update the metadata when a user (dis)connects - connect(this, &DomainServer::userConnected, &_metadata, &DomainMetadata::usersChanged); - connect(this, &DomainServer::userDisconnected, &_metadata, &DomainMetadata::usersChanged); - // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); @@ -124,8 +120,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : optionallyGetTemporaryName(args); } - // update the metadata with current descriptors - _metadata.setDescriptors(_settingsManager.getSettingsMap()); + _metadata = new DomainMetadata(this); } DomainServer::~DomainServer() { @@ -1101,14 +1096,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; - // Add the metadata to the heartbeat - static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; - auto tic = _metadata.getTic(); - if (_metadataTic != tic) { - _metadataTic = tic; - _metadata.updateUsers(); + if (_metadata) { + // Add the metadata to the heartbeat + static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; + domainObject[DOMAIN_HEARTBEAT_KEY] = _metadata->get(DomainMetadata::USERS); } - domainObject[DOMAIN_HEARTBEAT_KEY] = _metadata.getUsers(); QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact))); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index bdcc36c1ac..c742dbc9b3 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -172,13 +172,12 @@ private: DomainServerSettingsManager _settingsManager; - DomainMetadata _metadata; - uint32_t _metadataTic{ 0 }; - HifiSockAddr _iceServerSocket; std::unique_ptr _iceServerHeartbeatPacket; - QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer + // These will be parented to this, they are not dangling + DomainMetadata* _metadata { nullptr }; + QTimer* _iceHeartbeatTimer { nullptr }; QList _iceServerAddresses; QSet _failedIceServerAddresses; @@ -190,6 +189,7 @@ private: bool _hasAccessToken { false }; friend class DomainGatekeeper; + friend class DomainMetadata; }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5790eb9178..543e61f485 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include #include #include @@ -69,7 +71,7 @@ DomainServerSettingsManager::DomainServerSettingsManager() : } static const QString MISSING_SETTINGS_DESC_MSG = - QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") + QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") .arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset); static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; @@ -241,7 +243,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } QList> permissionsSets; - permissionsSets << _standardAgentPermissions << _agentPermissions; + permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); foreach (auto permissionsSet, permissionsSets) { foreach (QString userName, permissionsSet.keys()) { if (onlyEditorsAreRezzers) { @@ -258,6 +260,27 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _standardAgentPermissions.clear(); _agentPermissions.clear(); } + + if (oldVersion < 1.5) { + // This was prior to operating hours, so add default hours + static const QString WEEKDAY_HOURS{ "descriptors.weekday_hours" }; + static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" }; + static const QString UTC_OFFSET{ "descriptors.utc_offset" }; + + QVariant* weekdayHours = valueForKeyPath(_configMap.getUserConfig(), WEEKDAY_HOURS, true); + QVariant* weekendHours = valueForKeyPath(_configMap.getUserConfig(), WEEKEND_HOURS, true); + QVariant* utcOffset = valueForKeyPath(_configMap.getUserConfig(), UTC_OFFSET, true); + + *weekdayHours = QVariantList { QVariantMap{ { "open", QVariant("00:00") }, { "close", QVariant("23:59") } } }; + *weekendHours = QVariantList { QVariantMap{ { "open", QVariant("00:00") }, { "close", QVariant("23:59") } } }; + *utcOffset = QVariant(QTimeZone::systemTimeZone().offsetFromUtc(QDateTime::currentDateTime()) / (float)3600); + + // write the new settings to file + persistToFile(); + + // reload the master and user config so the merged config is correct + _configMap.loadMasterAndUserConfig(_argumentList); + } } unpackPermissions(); @@ -267,7 +290,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } void DomainServerSettingsManager::packPermissionsForMap(QString mapName, - QHash agentPermissions, + NodePermissionsMap& agentPermissions, QString keyPath) { QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); if (!security || !security->canConvert(QMetaType::QVariantMap)) { @@ -378,7 +401,7 @@ void DomainServerSettingsManager::unpackPermissions() { #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; - permissionsSets << _standardAgentPermissions << _agentPermissions; + permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); foreach (auto permissionSet, permissionsSets) { QHashIterator i(permissionSet); while (i.hasNext()) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 446e9a2eed..ec1d3b637d 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -72,11 +72,11 @@ private: friend class DomainServer; - void packPermissionsForMap(QString mapName, QHash agentPermissions, QString keyPath); + void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); void packPermissions(); void unpackPermissions(); - QHash _standardAgentPermissions; // anonymous, logged-in, localhost - QHash _agentPermissions; // specific account-names + NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost + NodePermissionsMap _agentPermissions; // specific account-names }; #endif // hifi_DomainServerSettingsManager_h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ae84705da3..cf5a2b60ad 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -139,7 +139,7 @@ link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater - controllers plugins display-plugins input-plugins steamworks-wrapper) + controllers plugins ui-plugins display-plugins input-plugins steamworks-wrapper) # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index e269a63cde..9f10cfc64a 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -65,7 +65,7 @@ FocusScope { var oldChildren = expectedChildren; var newChildren = d.getRepositionChildren(); - if (oldRecommendedRect != Qt.rect(0,0,0,0) + if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) && (oldRecommendedRect != newRecommendedRect || oldChildren != newChildren) ) { @@ -297,6 +297,9 @@ FocusScope { onPinnedChanged: { if (pinned) { + nullFocus.focus = true; + nullFocus.forceActiveFocus(); + // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ return !d.isTopLevelWindow(child) && child.visible && !child.pinned; @@ -478,6 +481,8 @@ FocusScope { FocusHack { id: focusHack; } + FocusScope { id: nullFocus; } + Rectangle { id: focusDebugger; objectName: "focusDebugger" diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index fc10bd4f68..5372028da5 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -614,12 +614,6 @@ ModalWindow { readOnly: !root.saveDialog activeFocusOnTab: !readOnly onActiveFocusChanged: if (activeFocus) { selectAll(); } - onTextChanged: { - if (root.saveDialog && text !== "") { - fileTableView.selection.clear(); - fileTableView.currentRow = -1; - } - } onAccepted: okAction.trigger(); } diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index c14d55cb00..169542c0f0 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -3,12 +3,12 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1; import Qt.labs.settings 1.0 -import "../desktop" +import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -Desktop { +OriginalDesktop.Desktop { id: desktop MouseArea { @@ -54,12 +54,13 @@ Desktop { WebEngine.settings.localContentCanAccessRemoteUrls = true; var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); - //toolbars[sysToolbar.objectName] = sysToolbar var toggleHudButton = sysToolbar.addButton({ + objectName: "hudToggle", imageURL: "../../../icons/hud-01.svg", visible: true, - + pinned: true, }); + toggleHudButton.yOffset = Qt.binding(function(){ return desktop.pinned ? 50 : 0 }); diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 35c816569b..75c06e4199 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -7,14 +7,16 @@ import "." Window { id: window - frame: ToolFrame { } + frame: ToolFrame { + horizontalSpacers: horizontal + verticalSpacers: !horizontal + } hideBackground: true resizable: false destroyOnCloseButton: false destroyOnHidden: false closable: false shown: true - pinned: true width: content.width height: content.height visible: true @@ -32,54 +34,77 @@ Window { } onHorizontalChanged: { - var oldParent = horizontal ? column : row; var newParent = horizontal ? row : column; - var move = []; - - var i; - for (i in oldParent.children) { - var child = oldParent.children[i]; - if (child.spacer) { - continue; - } - move.push(oldParent.children[i]); - } - for (i in move) { - move[i].parent = newParent; + for (var i in buttons) { + var child = buttons[i]; + child.parent = newParent; if (horizontal) { - move[i].y = 0 + child.y = 0 } else { - move[i].x = 0 + child.x = 0 } } - fixSpacers(); } Item { id: content implicitHeight: horizontal ? row.height : column.height implicitWidth: horizontal ? row.width : column.width + Row { id: row spacing: 6 - visible: window.horizontal - Rectangle{ readonly property bool spacer: true; id: rowSpacer1; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer2; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer3; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer4; width: 1; height: row.height } } Column { id: column spacing: 6 - visible: !window.horizontal - Rectangle{ readonly property bool spacer: true; id: colSpacer1; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer2; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer3; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer4; width: column.width; height: 1 } } Component { id: toolbarButtonBuilder; ToolbarButton { } } + + Connections { + target: desktop + onPinnedChanged: { + if (!window.pinned) { + return; + } + var newPinned = desktop.pinned; + for (var i in buttons) { + var child = buttons[i]; + if (desktop.pinned) { + if (!child.pinned) { + child.visible = false; + } + } else { + child.visible = true; + } + } + } + } + } + + + function findButtonIndex(name) { + if (!name) { + return -1; + } + + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === name) { + return i; + } + } + return -1; + } + + function findButton(name) { + var index = findButtonIndex(name); + if (index < 0) { + return; + } + return buttons[index]; } function addButton(properties) { @@ -88,30 +113,39 @@ Window { // If a name is specified, then check if there's an existing button with that name // and return it if so. This will allow multiple clients to listen to a single button, // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - if (properties.objectName) { - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === properties.objectName) { - return child; - } - } + var result = findButton(properties.objectName); + if (result) { + return result; } - properties.toolbar = this; - var result = toolbarButtonBuilder.createObject(container, properties); + properties.opacity = 0; + result = toolbarButtonBuilder.createObject(container, properties); buttons.push(result); - fixSpacers(); + result.opacity = 1; + updatePinned(); return result; } - function fixSpacers() { - colSpacer3.parent = null - colSpacer4.parent = null - rowSpacer3.parent = null - rowSpacer4.parent = null - colSpacer3.parent = column - colSpacer4.parent = column - rowSpacer3.parent = row - rowSpacer4.parent = row + function removeButton(name) { + var index = findButtonIndex(name); + if (index < -1) { + console.warn("Tried to remove non-existent button " + name); + return; + } + buttons[index].destroy(); + buttons.splice(index, 1); + updatePinned(); + } + + function updatePinned() { + var newPinned = false; + for (var i in buttons) { + var child = buttons[i]; + if (child.pinned) { + newPinned = true; + break; + } + } + pinned = newPinned; } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index a8514689e8..a3be4533d2 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -7,11 +7,40 @@ Item { property alias alpha: button.opacity property var subImage; property int yOffset: 0 + property int buttonState: 0 property var toolbar; property real size: 50 // toolbar ? toolbar.buttonSize : 50 width: size; height: size + property bool pinned: false clip: true + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutCubic + } + } + + property alias fadeTargetProperty: button.opacity + + onFadeTargetPropertyChanged: { + visible = (fadeTargetProperty !== 0.0); + } + + onVisibleChanged: { + if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { + var target = visible; + visible = !visible; + fadeTargetProperty = target ? 1.0 : 0.0; + return; + } + } + + + onButtonStateChanged: { + yOffset = size * buttonState + } + Component.onCompleted: { if (subImage) { if (subImage.y) { @@ -30,10 +59,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: { - console.log("Clicked on button " + image.source + " named " + button.objectName) - button.clicked(); - } + onClicked: button.clicked(); } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index bc8ecc35ec..88d8c3ad41 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -59,7 +59,7 @@ Item { height: 1.66 * window.height x: (window.width - width) / 2 y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible + visible: gradientsSupported && window && window.focus && window.content.visible gradient: Gradient { // GradientStop position 0.5 is at full circumference of circle that fits inside the square. GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index ac1093092e..eff5fc0377 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -16,20 +16,67 @@ import "../styles-uit" Frame { HifiConstants { id: hifi } + property bool horizontalSpacers: false + property bool verticalSpacers: false Rectangle { // Dialog frame id: frameContent readonly property int frameMargin: 6 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: frameMargin - readonly property int frameMarginBottom: frameMargin + readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0) + readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0) + + Rectangle { + visible: horizontalSpacers + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: horizontalSpacers + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.top: parent.top + anchors.topMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + anchors { - topMargin: -frameMargin - leftMargin: -frameMargin - rightMargin: -frameMargin - bottomMargin: -frameMargin + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + topMargin: -frameMarginTop + bottomMargin: -frameMarginBottom } anchors.fill: parent color: hifi.colors.baseGrayHighlight40 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6dc575b572..37c3b361bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -83,7 +84,6 @@ #include #include #include -#include #include #include #include @@ -119,7 +119,6 @@ #include "InterfaceLogging.h" #include "LODManager.h" #include "ModelPackager.h" -#include "PluginContainerProxy.h" #include "scripting/AccountScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/AudioDeviceScriptingInterface.h" @@ -152,6 +151,8 @@ #include "InterfaceParentFinder.h" #include "FrameTimingsScriptingInterface.h" +#include +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -439,7 +440,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); @@ -466,7 +467,6 @@ bool setupEssentials(int& argc, char** argv) { // continuing to overburden Application.cpp Cube3DOverlay* _keyboardFocusHighlight{ nullptr }; int _keyboardFocusHighlightID{ -1 }; -PluginContainer* _pluginContainer; // FIXME hack access to the internal share context for the Chromium helper @@ -506,6 +506,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0) { + + + PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care + PluginManager::getInstance()->setContainer(pluginContainer); + // FIXME this may be excessively conservative. On the other hand // maybe I'm used to having an 8-core machine // Perhaps find the ideal thread count and subtract 2 or 3 @@ -523,7 +528,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _entityClipboard->createRootElement(); - _pluginContainer = new PluginContainerProxy(); #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif @@ -674,10 +678,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : accountManager->setIsAgent(true); accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get()); - auto addressManager = DependencyManager::get(); // use our MyAvatar position and quat for address manager path @@ -767,6 +767,39 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Make sure we don't time out during slow operations at startup updateHeartbeat(); + + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["slVersion"] }, + { "gl_renderer", glContextData["renderer"] } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + UserActivityLogger::getInstance().logAction("launch", properties); + + // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); _entityEditSender.setMyAvatar(getMyAvatar()); @@ -1062,6 +1095,89 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } }); + // Add periodic checks to send user activity data + static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; + static int SEND_STATS_INTERVAL_MS = 10000; + static int NEARBY_AVATAR_RADIUS_METERS = 10; + + // Periodically send fps as a user activity event + QTimer* sendStatsTimer = new QTimer(this); + sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); + connect(sendStatsTimer, &QTimer::timeout, this, [this]() { + QJsonObject properties = {}; + MemoryInfo memInfo; + if (getMemoryInfo(memInfo)) { + properties["system_memory_total"] = static_cast(memInfo.totalMemoryBytes); + properties["system_memory_used"] = static_cast(memInfo.usedMemoryBytes); + properties["process_memory_used"] = static_cast(memInfo.processUsedMemoryBytes); + } + + auto displayPlugin = qApp->getActiveDisplayPlugin(); + + properties["fps"] = _frameCounter.rate(); + properties["present_rate"] = displayPlugin->presentRate(); + properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); + properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); + properties["sim_rate"] = getAverageSimsPerSecond(); + properties["avatar_sim_rate"] = getAvatarSimrate(); + + auto bandwidthRecorder = DependencyManager::get(); + properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); + properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); + properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); + properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); + + auto nodeList = DependencyManager::get(); + SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer); + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer); + SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer); + properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1; + properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1; + properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1; + properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1; + properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; + + auto loadingRequests = ResourceCache::getLoadingRequests(); + properties["active_downloads"] = loadingRequests.size(); + properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); + + properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; + + UserActivityLogger::getInstance().logAction("stats", properties); + }); + sendStatsTimer->start(); + + + // Periodically check for count of nearby avatars + static int lastCountOfNearbyAvatars = -1; + QTimer* checkNearbyAvatarsTimer = new QTimer(this); + checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); + connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { + auto avatarManager = DependencyManager::get(); + int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(), + NEARBY_AVATAR_RADIUS_METERS) - 1; + if (nearbyAvatars != lastCountOfNearbyAvatars) { + lastCountOfNearbyAvatars = nearbyAvatars; + UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } }); + } + }); + checkNearbyAvatarsTimer->start(); + + // Track user activity event when we receive a mute packet + auto onMutedByMixer = []() { + UserActivityLogger::getInstance().logAction("received_mute_packet"); + }; + connect(DependencyManager::get().data(), &AudioClient::mutedByMixer, this, onMutedByMixer); + + // Track when the address bar is opened + auto onAddressBarToggled = [this]() { + // Record time + UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } }); + }; + connect(DependencyManager::get().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled); + // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -2037,9 +2153,9 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Return: if (isOption) { if (_window->isFullScreen()) { - _pluginContainer->unsetFullscreen(); + unsetFullscreen(); } else { - _pluginContainer->setFullscreen(nullptr); + setFullscreen(nullptr); } } else { Menu::getInstance()->triggerOption(MenuOption::AddressBar); @@ -2953,7 +3069,6 @@ void Application::loadSettings() { //DependencyManager::get()->setAutomaticLODAdjust(false); Menu::getInstance()->loadSettings(); - // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. auto pluginManager = PluginManager::getInstance(); auto plugins = pluginManager->getPreferredDisplayPlugins(); @@ -4578,6 +4693,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); + + scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } bool Application::canAcceptURL(const QString& urlString) const { @@ -5180,6 +5297,11 @@ void Application::updateDisplayMode() { return; } + UserActivityLogger::getInstance().logAction("changed_display_mode", { + { "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" }, + { "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" } + }); + auto offscreenUi = DependencyManager::get(); // Make the switch atomic from the perspective of other threads @@ -5310,3 +5432,49 @@ void Application::showDesktop() { CompositorHelper& Application::getApplicationCompositor() const { return *DependencyManager::get(); } + + +// virtual functions required for PluginContainer +ui::Menu* Application::getPrimaryMenu() { + auto appMenu = _window->menuBar(); + auto uiMenu = dynamic_cast(appMenu); + return uiMenu; +} + +void Application::showDisplayPluginsTools(bool show) { + DependencyManager::get()->hmdTools(show); +} + +GLWidget* Application::getPrimaryWidget() { + return _glWidget; +} + +MainWindow* Application::getPrimaryWindow() { + return getWindow(); +} + +QOpenGLContext* Application::getPrimaryContext() { + return _glWidget->context()->contextHandle(); +} + +bool Application::makeRenderingContextCurrent() { + return _offscreenContext->makeCurrent(); +} + +void Application::releaseSceneTexture(const gpu::TexturePointer& texture) { + Q_ASSERT(QThread::currentThread() == thread()); + auto& framebufferMap = _lockedFramebufferMap; + Q_ASSERT(framebufferMap.contains(texture)); + auto framebufferPointer = framebufferMap[texture]; + framebufferMap.remove(texture); + auto framebufferCache = DependencyManager::get(); + framebufferCache->releaseFramebuffer(framebufferPointer); +} + +void Application::releaseOverlayTexture(const gpu::TexturePointer& texture) { + _applicationOverlay.releaseOverlay(texture); +} + +bool Application::isForeground() const { + return _isForeground && !_window->isMinimized(); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 6b6148be32..5beaa5b455 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -86,14 +87,32 @@ class Application; #endif #define qApp (static_cast(QCoreApplication::instance())) -class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, public AbstractUriHandler { +class Application : public QApplication, + public AbstractViewStateInterface, + public AbstractScriptingServicesInterface, + public AbstractUriHandler, + public PluginContainer { Q_OBJECT // TODO? Get rid of those friend class OctreePacketProcessor; - friend class PluginContainerProxy; public: + + // virtual functions required for PluginContainer + virtual ui::Menu* getPrimaryMenu() override; + virtual void requestReset() override { resetSensors(true); } + virtual void showDisplayPluginsTools(bool show) override; + virtual GLWidget* getPrimaryWidget() override; + virtual MainWindow* getPrimaryWindow() override; + virtual QOpenGLContext* getPrimaryContext() override; + virtual bool makeRenderingContextCurrent() override; + virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override; + virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override; + virtual bool isForeground() const override; + + virtual DisplayPluginPointer getActiveDisplayPlugin() const override; + enum Event { Present = DisplayPlugin::Present, Paint = Present + 1, @@ -163,7 +182,6 @@ public: Overlays& getOverlays() { return _overlays; } - bool isForeground() const { return _isForeground; } size_t getFrameCount() const { return _frameCount; } float getFps() const { return _frameCounter.rate(); } @@ -185,8 +203,6 @@ public: void setActiveDisplayPlugin(const QString& pluginName); - DisplayPluginPointer getActiveDisplayPlugin() const; - FileLogger* getLogger() const { return _logger; } glm::vec2 getViewportDimensions() const; @@ -195,6 +211,8 @@ public: float getRenderResolutionScale() const; + qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } + bool isAboutToQuit() const { return _aboutToQuit; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 24256fdf39..c4d985419e 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -80,7 +80,8 @@ void DiscoverabilityManager::updateLocation() { locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends)); // if we have a session ID add it now, otherwise add a null value - rootObject[SESSION_ID_KEY] = _sessionID.isEmpty() ? QJsonValue() : _sessionID; + auto sessionID = accountManager->getSessionID(); + rootObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString(); JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = this; @@ -110,11 +111,8 @@ void DiscoverabilityManager::updateLocation() { callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; QJsonObject heartbeatObject; - if (!_sessionID.isEmpty()) { - heartbeatObject[SESSION_ID_KEY] = _sessionID; - } else { - heartbeatObject[SESSION_ID_KEY] = QJsonValue(); - } + auto sessionID = accountManager->getSessionID(); + heartbeatObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString(); accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, @@ -126,11 +124,11 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply auto dataObject = AccountManager::dataObjectFromResponse(requestReply); if (!dataObject.isEmpty()) { - _sessionID = dataObject[SESSION_ID_KEY].toString(); + auto sessionID = dataObject[SESSION_ID_KEY].toString(); // give that session ID to the account manager auto accountManager = DependencyManager::get(); - accountManager->setSessionID(_sessionID); + accountManager->setSessionID(sessionID); } } diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 9a1fa7b39c..196b0cdf81 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -49,7 +49,6 @@ private: DiscoverabilityManager(); Setting::Handle _mode; - QString _sessionID; QJsonObject _lastLocationObject; }; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp deleted file mode 100644 index b651a1520d..0000000000 --- a/interface/src/PluginContainerProxy.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "PluginContainerProxy.h" - -#include -#include - -#include -#include -#include -#include -#include - -#include "Application.h" -#include "MainWindow.h" -#include "GLCanvas.h" -#include "ui/DialogsManager.h" - -#include -#include - -PluginContainerProxy::PluginContainerProxy() { -} - -PluginContainerProxy::~PluginContainerProxy() { -} - -ui::Menu* PluginContainerProxy::getPrimaryMenu() { - auto appMenu = qApp->_window->menuBar(); - auto uiMenu = dynamic_cast(appMenu); - return uiMenu; -} - -bool PluginContainerProxy::isForeground() { - return qApp->isForeground() && !qApp->getWindow()->isMinimized(); -} - -void PluginContainerProxy::requestReset() { - // We could signal qApp to sequence this, but it turns out that requestReset is only used from within the main thread anyway. - qApp->resetSensors(true); -} - -void PluginContainerProxy::showDisplayPluginsTools(bool show) { - DependencyManager::get()->hmdTools(show); -} - -GLWidget* PluginContainerProxy::getPrimaryWidget() { - return qApp->_glWidget; -} - -MainWindow* PluginContainerProxy::getPrimaryWindow() { - return qApp->getWindow(); -} - -QOpenGLContext* PluginContainerProxy::getPrimaryContext() { - return qApp->_glWidget->context()->contextHandle(); -} - -const DisplayPluginPointer PluginContainerProxy::getActiveDisplayPlugin() const { - return qApp->getActiveDisplayPlugin(); -} - -bool PluginContainerProxy::makeRenderingContextCurrent() { - return qApp->_offscreenContext->makeCurrent(); -} - -void PluginContainerProxy::releaseSceneTexture(const gpu::TexturePointer& texture) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - auto& framebufferMap = qApp->_lockedFramebufferMap; - Q_ASSERT(framebufferMap.contains(texture)); - auto framebufferPointer = framebufferMap[texture]; - framebufferMap.remove(texture); - auto framebufferCache = DependencyManager::get(); - framebufferCache->releaseFramebuffer(framebufferPointer); -} - -void PluginContainerProxy::releaseOverlayTexture(const gpu::TexturePointer& texture) { - qApp->_applicationOverlay.releaseOverlay(texture); -} - diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h deleted file mode 100644 index a04a1b2977..0000000000 --- a/interface/src/PluginContainerProxy.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#ifndef hifi_PluginContainerProxy_h -#define hifi_PluginContainerProxy_h - -#include -#include - -#include -#include - -class QActionGroup; - -class PluginContainerProxy : public QObject, PluginContainer { - Q_OBJECT - PluginContainerProxy(); - virtual ~PluginContainerProxy(); - virtual void showDisplayPluginsTools(bool show = true) override; - virtual void requestReset() override; - virtual bool makeRenderingContextCurrent() override; - virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override; - virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override; - virtual GLWidget* getPrimaryWidget() override; - virtual MainWindow* getPrimaryWindow() override; - virtual ui::Menu* getPrimaryMenu() override; - virtual QOpenGLContext* getPrimaryContext() override; - virtual bool isForeground() override; - virtual const DisplayPluginPointer getActiveDisplayPlugin() const override; - - friend class Application; - -}; - -#endif diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d1efca902..6a69ee9a9a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -722,7 +722,7 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving); + settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.endGroup(); } @@ -842,7 +842,7 @@ void MyAvatar::loadData() { setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); + setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); settings.endGroup(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6fa6e1fd19..96fa999de5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -159,8 +159,8 @@ public: Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; } - Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; } + Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } + Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; } Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } @@ -405,7 +405,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; - bool _clearOverlayWhenDriving { false }; + bool _clearOverlayWhenMoving { true }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index fd96b6a809..82332b3187 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -15,15 +15,6 @@ class QmlWrapper : public QObject { public: QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) : QObject(parent), _qmlObject(qmlObject) { - - const QMetaObject *metaobject = qmlObject->metaObject(); - int count = metaobject->propertyCount(); - qDebug() << "Scanning properties for " << qmlObject; - for (int i = 0; i < count; ++i) { - QMetaProperty metaproperty = metaobject->property(i); - const char *name = metaproperty.name(); - qDebug() << "Property " << name; - } } Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h index d3706cb6e1..9379284e55 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.h +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -20,7 +20,7 @@ class ToolbarProxy; class ToolbarScriptingInterface : public QObject, public Dependency { Q_OBJECT public: - Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); }; #endif // hifi_ToolbarScriptingInterface_h diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 74e27122e4..6a99641ce4 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -32,7 +32,7 @@ bool OverlayConductor::headOutsideOverlay() const { glm::vec3 uiPos = uiTransform.getTranslation(); glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { @@ -124,7 +124,7 @@ void OverlayConductor::update(float dt) { _flags &= ~SuppressedByDrive; } } else { - if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { _flags |= SuppressedByDrive; } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 6decef3240..c1705da206 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -62,9 +62,9 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } { - auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); }; - auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); }; - preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter)); + auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); }; + auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b21f5a0e84..8aee2245c0 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -163,7 +163,6 @@ void Rig::destroyAnimGraph() { } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { - _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); setModelOffset(modelOffset); @@ -1224,8 +1223,7 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { } void Rig::copyJointsFromJointData(const QVector& jointDataVec) { - - if (_animSkeleton) { + if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._overrideFlags.size()) { // transform all the default poses into rig space. const AnimPose geometryToRigPose(_geometryToRigTransform); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6d99d6ad81..709cc76d01 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -269,7 +269,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { _lastSentJointData.resize(_jointData.size()); for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); + const JointData& data = _jointData[i]; if (sendAll || _lastSentJointData[i].rotation != data.rotation) { if (sendAll || !cullSmallChanges || @@ -294,7 +294,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { validityBit = 0; validity = *validityPosition++; for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[ i ]; + const JointData& data = _jointData[i]; if (validity & (1 << validityBit)) { destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); } @@ -317,7 +317,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { float maxTranslationDimension = 0.0; for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); + const JointData& data = _jointData[i]; if (sendAll || _lastSentJointData[i].translation != data.translation) { if (sendAll || !cullSmallChanges || @@ -348,7 +348,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { validityBit = 0; validity = *validityPosition++; for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[ i ]; + const JointData& data = _jointData[i]; if (validity & (1 << validityBit)) { destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); @@ -425,7 +425,6 @@ bool AvatarData::shouldLogError(const quint64& now) { // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); @@ -669,7 +668,9 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } JointData& data = _jointData[index]; data.rotation = rotation; + data.rotationSet = true; data.translation = translation; + data.translationSet = true; } void AvatarData::clearJointData(int index) { @@ -774,6 +775,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; + data.rotationSet = true; } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { @@ -789,6 +791,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { } JointData& data = _jointData[index]; data.translation = translation; + data.translationSet = true; } void AvatarData::clearJointData(const QString& name) { @@ -858,7 +861,6 @@ void AvatarData::setJointTranslations(QVector jointTranslations) { "setJointTranslations", Qt::BlockingQueuedConnection, Q_ARG(QVector, jointTranslations)); } - if (_jointData.size() < jointTranslations.size()) { _jointData.resize(jointTranslations.size()); } @@ -1100,6 +1102,7 @@ void AvatarData::sendIdentityPacket() { void AvatarData::updateJointMappings() { _jointIndices.clear(); _jointNames.clear(); + _jointData.clear(); if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); @@ -1457,7 +1460,6 @@ void AvatarData::fromJson(const QJsonObject& json) { auto joint = jointDataFromJsonValue(jointJson); jointArray.push_back(joint); setJointData(i, joint.rotation, joint.translation); - _jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose i++; } setRawJointData(jointArray); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 9084fd837b..d153cfd977 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -44,6 +44,20 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range return false; } +int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { + auto hashCopy = getHashCopy(); + auto rangeMeters2 = rangeMeters * rangeMeters; + int count = 0; + for (const AvatarSharedPointer& sharedAvatar : hashCopy) { + glm::vec3 avatarPosition = sharedAvatar->getPosition(); + auto distance2 = glm::distance2(avatarPosition, position); + if (distance2 < rangeMeters2) { + ++count; + } + } + return count; +} + AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 5f58074427..9d3ebb60f5 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -39,6 +39,7 @@ public: Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); } + int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); signals: void avatarAddedEvent(const QUuid& sessionUUID); diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index f2d58d825e..fe08647074 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins gl gpu-gl ui) +link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui) target_opengl() diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp index 4b8d957e5f..d068bef3b0 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.cpp @@ -11,7 +11,7 @@ #include #include "DisplayPlugin.h" -#include +#include static Setting::Handle IPD_SCALE_HANDLE("hmd.ipdScale", 1.0f); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 48dda1f73d..f488a805c6 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include const QString Basic2DWindowOpenGLDisplayPlugin::NAME("Desktop"); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index a17fe2fd49..032350a07c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -425,7 +425,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const d = glm::normalize(overlaySurfacePoint); } reticlePosition = headPosition + (d * getReticleDepth()); - quat reticleOrientation = quat(vec3(-spherical.y, spherical.x, 0.0f)); + quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose()); vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index 3f642072a0..4fadbdb94b 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -10,7 +10,7 @@ #include "NullDisplayPlugin.h" #include -#include +#include const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index b49b41d8b2..b72f52351f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -22,12 +22,13 @@ #include #include #include -#include +#include #include #include #include #include #include "CompositorHelper.h" +#include #if THREADED_PRESENT @@ -202,6 +203,7 @@ private: #endif + OpenGLDisplayPlugin::OpenGLDisplayPlugin() { _sceneTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture){ cleanupForSceneTexture(texture); @@ -234,10 +236,11 @@ bool OpenGLDisplayPlugin::activate() { cursorData.hotSpot = vec2(0.5f); } } - + if (!_container) { + return false; + } _vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported(); - #if THREADED_PRESENT // Start the present thread if necessary QSharedPointer presentThread; @@ -282,7 +285,11 @@ bool OpenGLDisplayPlugin::activate() { animation->start(); }); - return DisplayPlugin::activate(); + if (isHmd() && (getHmdScreen() >= 0)) { + _container->showDisplayPluginsTools(); + } + + return Parent::activate(); } void OpenGLDisplayPlugin::deactivate() { @@ -301,7 +308,16 @@ void OpenGLDisplayPlugin::deactivate() { _container->makeRenderingContextCurrent(); #endif internalDeactivate(); - DisplayPlugin::deactivate(); + + _container->showDisplayPluginsTools(false); + if (!_container->currentDisplayActions().isEmpty()) { + auto menu = _container->getPrimaryMenu(); + foreach(auto itemInfo, _container->currentDisplayActions()) { + menu->removeMenuItem(itemInfo.first, itemInfo.second); + } + _container->currentDisplayActions().clear(); + } + Parent::deactivate(); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index d8d4d8ff8c..068b236289 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -120,7 +120,7 @@ protected: QMap _sceneTextureToFrameIndexMap; uint32_t _currentPresentFrameIndex { 0 }; float _compositeOverlayAlpha{ 1.0f }; - + gpu::TexturePointer _currentSceneTexture; gpu::TexturePointer _currentOverlayTexture; @@ -165,4 +165,3 @@ private: float _overlayAlpha{ 1.0f }; }; - diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index efdb68226b..f1aa1edc81 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include @@ -420,8 +420,9 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } void HmdDisplayPlugin::compositeExtra() { - std::array handLasers; - std::array renderHandPoses; + const int NUMBER_OF_HANDS = 2; + std::array handLasers; + std::array renderHandPoses; Transform uiModelTransform; withPresentThreadLock([&] { handLasers = _handLasers; @@ -443,9 +444,9 @@ void HmdDisplayPlugin::compositeExtra() { using namespace oglplus; useProgram(_laserProgram); _laserGeometry->Use(); - std::array handLaserModelMatrices; + std::array handLaserModelMatrices; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (renderHandPoses[i] == identity) { continue; } @@ -485,7 +486,7 @@ void HmdDisplayPlugin::compositeExtra() { auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); auto view = glm::inverse(eyePose); const auto& projection = _eyeProjections[eye]; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (handLaserModelMatrices[i] == identity) { continue; } diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index 5f55841be1..5d9f812edf 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -9,7 +9,7 @@ #include "SideBySideStereoDisplayPlugin.h" #include #include -#include +#include #include #include "../CompositorHelper.h" diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 6c6716c8fa..cfdfb1fc21 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include "../CompositorHelper.h" diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 77a0c6d6fe..ec1f8a50bc 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -150,8 +150,10 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) { // we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones - qCDebug(entities) << "Refusing disallowed entity lifetime adjustment."; - return false; + if (properties.lifetimeChanged()) { + qCDebug(entities) << "Refusing disallowed entity lifetime adjustment."; + return false; + } } // enforce support for locked entities. If an entity is currently locked, then the only @@ -322,8 +324,8 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { float lifeTime = properties.getLifetime(); - if (lifeTime == 0.0f || lifeTime > _maxTmpEntityLifetime) { - // this is an attempt to rez a permanent entity. + if (lifeTime == ENTITY_ITEM_IMMORTAL_LIFETIME || lifeTime > _maxTmpEntityLifetime) { + // this is an attempt to rez a permanent or non-temporary entity. if (!canRez) { return false; } diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 302e0b8515..79b39a2331 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -5,6 +5,7 @@ #include #include #include +#include const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; @@ -39,6 +40,13 @@ const QGLFormat& getDefaultGLFormat() { return glFormat; } +int glVersionToInteger(QString glVersion) { + QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); + int majorNumber = versionParts[0].toInt(); + int minorNumber = versionParts[1].toInt(); + return majorNumber * 100 + minorNumber * 10; +} + QJsonObject getGLContextData() { if (!QOpenGLContext::currentContext()) { return QJsonObject(); diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index ddb254f1c5..477bf7abc8 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -27,5 +27,6 @@ void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVer const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); const QGLFormat& getDefaultGLFormat(); QJsonObject getGLContextData(); +int glVersionToInteger(QString glVersion); #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp index 19cf798b19..fa54e7c8fe 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp @@ -24,8 +24,15 @@ GLPipeline* GLPipeline::sync(const Pipeline& pipeline) { // No object allocated yet, let's see if it's worth it... ShaderPointer shader = pipeline.getProgram(); + + // If this pipeline's shader has already failed to compile, don't try again + if (shader->compilationHasFailed()) { + return nullptr; + } + GLShader* programObject = GLShader::sync(*shader); if (programObject == nullptr) { + shader->setCompilationHasFailed(true); return nullptr; } diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 59c6401150..e4643f2b7c 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -120,6 +120,9 @@ public: bool isProgram() const { return getType() > NUM_DOMAINS; } bool isDomain() const { return getType() < NUM_DOMAINS; } + void setCompilationHasFailed(bool compilationHasFailed) { _compilationHasFailed = compilationHasFailed; } + bool compilationHasFailed() const { return _compilationHasFailed; } + const Source& getSource() const { return _source; } const Shaders& getShaders() const { return _shaders; } @@ -180,6 +183,9 @@ protected: // The type of the shader, the master key Type _type; + + // Whether or not the shader compilation failed + bool _compilationHasFailed { false }; }; typedef Shader::Pointer ShaderPointer; diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index b81554511d..b0ea13843b 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME input-plugins) setup_hifi_library() -link_hifi_libraries(shared plugins controllers) +link_hifi_libraries(shared plugins ui-plugins controllers) GroupSources("src/input-plugins") diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 26b3801ec1..b6c5e691a6 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -44,6 +44,7 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; +static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, QObject* errorCallbackReceiver, const QString& errorCallbackMethod, @@ -222,8 +223,7 @@ void AccountManager::sendRequest(const QString& path, // if we're allowed to send usage data, include whatever the current session ID is with this request auto& activityLogger = UserActivityLogger::getInstance(); if (activityLogger.isEnabled()) { - static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID"; - networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); } @@ -322,6 +322,9 @@ void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); if (requestReply->error() == QNetworkReply::NoError) { + if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { + _sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER); + } passSuccessToCallback(requestReply); } else { passErrorToCallback(requestReply); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 4803d2625f..d30a05fb2c 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -26,9 +26,9 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(), - QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(), - QObject* updateReceiver = NULL, const QString& updateSlot = QString()); + JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(), + QObject* updateReceiver = nullptr, const QString& updateSlot = QString()); bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } @@ -86,6 +86,7 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + QUuid getSessionID() const { return _sessionID; } void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } public slots: @@ -139,7 +140,7 @@ private: bool _isWaitingForKeypairResponse { false }; QByteArray _pendingPrivateKey; - QUuid _sessionID; + QUuid _sessionID { QUuid::createUuid() }; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 80989acd2c..df9b4094b0 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -23,6 +23,7 @@ #include "AddressManager.h" #include "NodeList.h" #include "NetworkLogging.h" +#include "UserActivityLogger.h" const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; @@ -130,6 +131,10 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { } bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { + static QString URL_TYPE_USER = "user"; + static QString URL_TYPE_DOMAIN_ID = "domain_id"; + static QString URL_TYPE_PLACE = "place"; + static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; if (lookupUrl.scheme() == HIFI_URL_SCHEME) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); @@ -147,6 +152,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (handleUsername(lookupUrl.authority())) { // handled a username for lookup + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API _previousLookup = lookupUrl; @@ -157,6 +164,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // a network address lookup clears the previous lookup since we don't expect to re-attempt it _previousLookup.clear(); @@ -174,6 +183,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info _previousLookup = lookupUrl; @@ -181,6 +192,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // store this place name as the previous lookup in case we fail to connect and want to refresh API info _previousLookup = lookupUrl; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index c153878a7e..de46a0dae5 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -24,9 +24,9 @@ using NodePermissionsPointer = std::shared_ptr; class NodePermissions { public: NodePermissions() { _id = QUuid::createUuid().toString(); } - NodePermissions(const QString& name) { _id = name; } + NodePermissions(const QString& name) { _id = name.toLower(); } NodePermissions(QMap perms) { - _id = perms["permissions_id"].toString(); + _id = perms["permissions_id"].toString().toLower(); canConnectToDomain = perms["id_can_connect"].toBool(); canAdjustLocks = perms["id_can_adjust_locks"].toBool(); canRezPermanentEntities = perms["id_can_rez"].toBool(); @@ -38,7 +38,7 @@ public: QString getID() const { return _id; } // the _id member isn't authenticated and _username is. - void setUserName(QString userName) { _userName = userName; } + void setUserName(QString userName) { _userName = userName.toLower(); } QString getUserName() { return _userName; } bool isAssignment { false }; @@ -88,6 +88,23 @@ protected: QString _userName; }; + +// wrap QHash in a class that forces all keys to be lowercase +class NodePermissionsMap { +public: + NodePermissionsMap() { } + NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; } + NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); } + bool contains(const QString& key) const { return _data.contains(key.toLower()); } + QList keys() const { return _data.keys(); } + QHash get() { return _data; } + void clear() { _data.clear(); } + +private: + QHash _data; +}; + + const NodePermissions DEFAULT_AGENT_PERMISSIONS; QDebug operator<<(QDebug debug, const NodePermissions& perms); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 83c6eb304e..eba4d31167 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -18,6 +18,7 @@ #include "UserActivityLogger.h" #include +#include "AddressManager.h" static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; @@ -125,6 +126,19 @@ void UserActivityLogger::changedDomain(QString domainURL) { } void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) { + static QStringList DEVICE_BLACKLIST = { + "Desktop", + "NullDisplayPlugin", + "3D TV - Side by Side Stereo", + "3D TV - Interleaved", + + "Keyboard/Mouse" + }; + + if (DEVICE_BLACKLIST.contains(deviceName)) { + return; + } + const QString ACTION_NAME = "connected_device"; QJsonObject actionDetails; const QString TYPE_OF_DEVICE = "type_of_device"; @@ -148,12 +162,34 @@ void UserActivityLogger::loadedScript(QString scriptName) { } -void UserActivityLogger::wentTo(QString destinationType, QString destinationName) { +void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QString destinationType, QString destinationName) { + // Only accept these types of triggers. Other triggers are usually used internally in AddressManager. + QString trigger; + switch (lookupTrigger) { + case AddressManager::UserInput: + trigger = "UserInput"; + break; + case AddressManager::Back: + trigger = "Back"; + break; + case AddressManager::Forward: + trigger = "Forward"; + break; + case AddressManager::StartupFromSettings: + trigger = "StartupFromSettings"; + break; + default: + return; + } + + const QString ACTION_NAME = "went_to"; QJsonObject actionDetails; + const QString TRIGGER_TYPE_KEY = "trigger"; const QString DESTINATION_TYPE_KEY = "destination_type"; const QString DESTINATION_NAME_KEY = "detination_name"; + actionDetails.insert(TRIGGER_TYPE_KEY, trigger); actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); actionDetails.insert(DESTINATION_NAME_KEY, destinationName); diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index c2ab93db2f..b41960a8ad 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -20,6 +20,7 @@ #include #include +#include "AddressManager.h" class UserActivityLogger : public QObject { Q_OBJECT @@ -42,7 +43,7 @@ public slots: void changedDomain(QString domainURL); void connectedDevice(QString typeOfDevice, QString deviceName); void loadedScript(QString scriptName); - void wentTo(QString destinationType, QString destinationName); + void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName); private slots: void requestError(QNetworkReply& errorReply); diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp new file mode 100644 index 0000000000..8b22b8ff58 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "UserActivityLoggerScriptingInterface.h" +#include "UserActivityLogger.h" + +void UserActivityLoggerScriptingInterface::enabledEdit() { + logAction("enabled_edit"); +} + +void UserActivityLoggerScriptingInterface::openedMarketplace() { + logAction("opened_marketplace"); +} + +void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) { + logAction("toggled_away", { { "is_away", isAway } }); +} + +void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) { + QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction", + Q_ARG(QString, action), + Q_ARG(QJsonObject, details)); +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h new file mode 100644 index 0000000000..9d60d666e2 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_UserActivityLoggerScriptingInterface_h +#define hifi_UserActivityLoggerScriptingInterface_h + +#include +#include + +#include + +class UserActivityLoggerScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE void enabledEdit(); + Q_INVOKABLE void openedMarketplace(); + Q_INVOKABLE void toggledAway(bool isAway); + +private: + void logAction(QString action, QJsonObject details = {}); +}; + +#endif // hifi_UserActivityLoggerScriptingInterface_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8f22c576f0..dc57a82fd3 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -135,7 +135,14 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { _nextOwnershipBid = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { - _body->activate(); + if (_body->isKinematicObject()) { + // only force activate kinematic bodies (dynamic shouldn't need force and + // active static bodies are special (see PhysicsEngine::_activeStaticBodies)) + _body->activate(true); + _lastKinematicStep = ObjectMotionState::getWorldSimulationStep(); + } else { + _body->activate(); + } } } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index a217041f4e..747c72c08e 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -1,28 +1,6 @@ #include "DisplayPlugin.h" #include -#include - -#include "PluginContainer.h" - -bool DisplayPlugin::activate() { - if (isHmd() && (getHmdScreen() >= 0)) { - _container->showDisplayPluginsTools(); - } - return Parent::activate(); -} - -void DisplayPlugin::deactivate() { - _container->showDisplayPluginsTools(false); - if (!_container->currentDisplayActions().isEmpty()) { - auto menu = _container->getPrimaryMenu(); - foreach(auto itemInfo, _container->currentDisplayActions()) { - menu->removeMenuItem(itemInfo.first, itemInfo.second); - } - _container->currentDisplayActions().clear(); - } - Parent::deactivate(); -} int64_t DisplayPlugin::getPaintDelayUsecs() const { std::lock_guard lock(_paintDelayMutex); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 72bb6a315c..f0ba762ecb 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -132,8 +132,6 @@ public: Present = QEvent::User + 1 }; - bool activate() override; - void deactivate() override; virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } /// By default, all HMDs are stereo diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index fb5bf0ba55..0452c7fbfe 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -15,6 +15,7 @@ #include "Forward.h" class Plugin : public QObject { + Q_OBJECT public: /// \return human-readable name virtual const QString& getName() const = 0; @@ -63,6 +64,13 @@ public: virtual void saveSettings() const {} virtual void loadSettings() {} +signals: + // These signals should be emitted when a device is first known to be available. In some cases this will + // be in `init()`, in other cases, like Neuron, this isn't known until activation. + // SDL2 isn't a device itself, but can have 0+ subdevices. subdeviceConnected is used in this case. + void deviceConnected(QString pluginName) const; + void subdeviceConnected(QString pluginName, QString subdeviceName) const; + protected: bool _active { false }; PluginContainer* _container { nullptr }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 7161132c5e..29658eeb6b 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,10 +14,12 @@ #include #include +#include +#include + #include "RuntimePlugin.h" #include "DisplayPlugin.h" #include "InputPlugin.h" -#include "PluginContainer.h" PluginManager* PluginManager::getInstance() { @@ -120,6 +122,15 @@ static DisplayPluginList displayPlugins; const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", pluginName + " | " + deviceName); + }; + std::call_once(once, [&] { // Grab the built in plugins displayPlugins = ::getDisplayPlugins(); @@ -133,9 +144,10 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { } } } - auto& container = PluginContainer::getInstance(); for (auto plugin : displayPlugins) { - plugin->setContainer(&container); + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); + plugin->setContainer(_container); plugin->init(); } @@ -156,6 +168,15 @@ void PluginManager::disableDisplayPlugin(const QString& name) { const InputPluginList& PluginManager::getInputPlugins() { static InputPluginList inputPlugins; static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", pluginName + " | " + deviceName); + }; + std::call_once(once, [&] { inputPlugins = ::getInputPlugins(); @@ -171,9 +192,10 @@ const InputPluginList& PluginManager::getInputPlugins() { } } - auto& container = PluginContainer::getInstance(); for (auto plugin : inputPlugins) { - plugin->setContainer(&container); + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); + plugin->setContainer(_container); plugin->init(); } }); diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 2a94e6490b..7903bdd724 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -26,4 +26,7 @@ public: void disableDisplays(const QStringList& displays); void disableInputs(const QStringList& inputs); void saveSettings(); + void setContainer(PluginContainer* container) { _container = container; } +private: + PluginContainer* _container { nullptr }; }; diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 19838964a4..02f92d87e7 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -122,7 +122,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) } if (count > bestCount) { bestCount = count; - _name = sString; + _name = QString(sString).trimmed(); hr = spInstance->Get(CComBSTR(_T("DriverVersion")), 0, &var, 0, 0); if (hr == S_OK) { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index b80fac637c..edb6fe437d 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -28,6 +28,7 @@ #ifdef Q_OS_WIN #include "CPUIdent.h" +#include #endif @@ -843,3 +844,29 @@ void printSystemInformation() { (envVariables.contains(env) ? " = " + envVariables.value(env) : " NOT FOUND"); } } + +bool getMemoryInfo(MemoryInfo& info) { +#ifdef Q_OS_WIN + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(ms); + if (!GlobalMemoryStatusEx(&ms)) { + return false; + } + + info.totalMemoryBytes = ms.ullTotalPhys; + info.availMemoryBytes = ms.ullAvailPhys; + info.usedMemoryBytes = ms.ullTotalPhys - ms.ullAvailPhys; + + + PROCESS_MEMORY_COUNTERS_EX pmc; + if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&pmc), sizeof(pmc))) { + return false; + } + info.processUsedMemoryBytes = pmc.PrivateUsage; + info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage; + + return true; +#endif + + return false; +} \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 042396f474..f3e5625484 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -204,4 +204,14 @@ void disableQtBearerPoll(); void printSystemInformation(); +struct MemoryInfo { + uint64_t totalMemoryBytes; + uint64_t availMemoryBytes; + uint64_t usedMemoryBytes; + uint64_t processUsedMemoryBytes; + uint64_t processPeakUsedMemoryBytes; +}; + +bool getMemoryInfo(MemoryInfo& info); + #endif // hifi_SharedUtil_h diff --git a/libraries/ui-plugins/CMakeLists.txt b/libraries/ui-plugins/CMakeLists.txt new file mode 100644 index 0000000000..9ce189b117 --- /dev/null +++ b/libraries/ui-plugins/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME ui-plugins) +setup_hifi_library(OpenGL) +link_hifi_libraries(shared plugins ui) diff --git a/libraries/plugins/src/plugins/PluginContainer.cpp b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp similarity index 100% rename from libraries/plugins/src/plugins/PluginContainer.cpp rename to libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h similarity index 94% rename from libraries/plugins/src/plugins/PluginContainer.h rename to libraries/ui-plugins/src/ui-plugins/PluginContainer.h index e1d1a212e2..74ac834057 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h @@ -16,7 +16,7 @@ #include #include -#include "Forward.h" +#include class QAction; class GLWidget; @@ -63,8 +63,8 @@ public: virtual GLWidget* getPrimaryWidget() = 0; virtual MainWindow* getPrimaryWindow() = 0; virtual QOpenGLContext* getPrimaryContext() = 0; - virtual bool isForeground() = 0; - virtual const DisplayPluginPointer getActiveDisplayPlugin() const = 0; + virtual bool isForeground() const = 0; + virtual DisplayPluginPointer getActiveDisplayPlugin() const = 0; /// settings interface bool getBoolSetting(const QString& settingName, bool defaultValue); @@ -84,3 +84,4 @@ protected: std::map _exclusiveGroups; QRect _savedGeometry { 10, 120, 800, 600 }; }; + diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index 0a4bc7f8d2..e41472a8c5 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -387,6 +387,8 @@ bool NeuronPlugin::activate() { } else { qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort; + emit deviceConnected(getName()); + BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); return true; } diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 09e783864c..b9a19658e2 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -66,6 +66,7 @@ void SDL2Manager::init() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } } @@ -157,6 +158,7 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati _openJoysticks[id] = joystick; userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { if (_openJoysticks.contains(event.cdevice.which)) { diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index 589b5b8964..f907d7865f 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -8,5 +8,5 @@ set(TARGET_NAME hifiSixense) setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins) +link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) target_sixense() diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 9ea79a8b96..03028249a3 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -137,6 +137,12 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED + static bool sixenseHasBeenConnected { false }; + if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) { + sixenseHasBeenConnected = true; + emit deviceConnected(getName()); + } + auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); diff --git a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp index 3d9b93ff44..4641799b79 100644 --- a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp @@ -18,7 +18,6 @@ #include #include -#include #include const QString SpacemouseManager::NAME { "Spacemouse" }; @@ -59,7 +58,6 @@ bool SpacemouseManager::activate() { if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", NAME); } return true; } @@ -330,7 +328,6 @@ bool SpacemouseManager::RawInputEventFilter(void* msg, long* result) { auto userInputMapper = DependencyManager::get(); if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); } else if (!Is3dmouseAttached() && instance->getDeviceID() != controller::Input::INVALID_DEVICE) { userInputMapper->removeDevice(instance->getDeviceID()); @@ -857,7 +854,7 @@ void SpacemouseManager::init() { if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); + emit deviceConnected(getName()); } //let one axis be dominant //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL); diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index a91690ecdd..778be08dcf 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -13,7 +13,7 @@ if (WIN32) set(TARGET_NAME oculus) setup_hifi_plugin(Multimedia) - link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking) + link_hifi_libraries(shared gl gpu controllers ui plugins ui-plugins display-plugins input-plugins audio-client networking) include_hifi_library_headers(octree) diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 0e9ca21804..b3b1b20b2b 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include #include diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 1006d69f06..2b2ec5bdb0 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -28,6 +28,12 @@ bool OculusDisplayPlugin::internalActivate() { return result; } +void OculusDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusDisplayPlugin::cycleDebugOutput() { if (_session) { currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index d6cd6f6f3d..ed6e0d13ea 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -17,6 +17,8 @@ class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: const QString& getName() const override { return NAME; } + void init() override; + QString getPreferredAudioInDevice() const override; QString getPreferredAudioOutDevice() const override; diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index a4e00013f1..c1f2c6249f 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -12,7 +12,7 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu plugins ui display-plugins input-plugins) + link_hifi_libraries(shared gl gpu plugins ui ui-plugins display-plugins input-plugins) include_hifi_library_headers(octree) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 699891deaa..2c032f7005 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -26,7 +26,7 @@ #include #include -#include "plugins/PluginContainer.h" +#include #include "OculusHelpers.h" using namespace oglplus; @@ -36,6 +36,12 @@ const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } +void OculusLegacyDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusLegacyDisplayPlugin::resetSensors() { ovrHmd_RecenterPose(_hmd); } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 453a6f9168..6ffc1a7f44 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -23,6 +23,8 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + void init() override; + int getHmdScreen() const override; // Stereo specific methods diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index 1ba8d05b92..8263e87767 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -12,7 +12,7 @@ if (WIN32) set(TARGET_NAME openvr) setup_hifi_plugin(OpenGL Script Qml Widgets) link_hifi_libraries(shared gl networking controllers ui - plugins display-plugins input-plugins script-engine + plugins display-plugins ui-plugins input-plugins script-engine render-utils model gpu render model-networking fbx) include_hifi_library_headers(octree) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 9c4313dc13..5233ad644a 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -46,6 +46,12 @@ bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } +void OpenVrDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + bool OpenVrDisplayPlugin::internalActivate() { _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); @@ -267,7 +273,7 @@ void OpenVrDisplayPlugin::unsuppressKeyboard() { return; } if (1 == _keyboardSupressionCount.fetch_sub(1)) { - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); } } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index b22fadb52c..fca4dab9e9 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -21,6 +21,8 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + void init() override; + float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; } void customizeContext() override; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 4f02c0384d..e71c8942d6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include "../../interface/src/Menu.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -91,7 +94,7 @@ void releaseOpenVrSystem() { static char textArray[8192]; -static QMetaObject::Connection _focusConnection, _focusTextConnection; +static QMetaObject::Connection _focusConnection, _focusTextConnection, _overlayMenuConnection; extern bool _openVrDisplayActive; static vr::IVROverlay* _overlay { nullptr }; static QObject* _keyboardFocusObject { nullptr }; @@ -99,7 +102,8 @@ static QString _existingText; static Qt::InputMethodHints _currentHints; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; static bool _keyboardShown { false }; -static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; +static bool _overlayRevealed { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400; void showOpenVrKeyboard(bool show = true) { if (!_overlay) { @@ -175,13 +179,26 @@ void finishOpenVrKeyboardInput() { static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -void enableOpenVrKeyboard() { +void enableOpenVrKeyboard(PluginContainer* container) { if (disableSteamVrKeyboard) { return; } auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); + + auto menu = container->getPrimaryMenu(); + auto action = menu->getActionForOption(MenuOption::Overlays); + + // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second. + _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { + if (action->isChecked()) { + _overlayRevealed = true; + const int KEYBOARD_DELAY_MS = 100; + QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; }); + } + }); + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { if (object != _keyboardFocusObject) { showOpenVrKeyboard(false); @@ -190,6 +207,11 @@ void enableOpenVrKeyboard() { _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { if (_openVrDisplayActive) { + if (_overlayRevealed) { + // suppress at most one text focus event + _overlayRevealed = false; + return; + } showOpenVrKeyboard(focusText); } }); @@ -200,6 +222,7 @@ void disableOpenVrKeyboard() { if (disableSteamVrKeyboard) { return; } + QObject::disconnect(_overlayMenuConnection); QObject::disconnect(_focusTextConnection); QObject::disconnect(_focusConnection); } @@ -325,4 +348,4 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); result.angularVelocity = angularVelocity; return result; -} \ No newline at end of file +} diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 131db517ab..19c9cbfff5 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -13,6 +13,7 @@ #include #include +#include bool openVrSupported(); @@ -20,7 +21,7 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); -void enableOpenVrKeyboard(); +void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 7f78ab8553..4a515978c3 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -63,7 +63,7 @@ bool ViveControllerManager::activate() { } Q_ASSERT(_system); - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code @@ -228,7 +228,6 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); } } diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js index 20f349929f..e21fbd8e19 100644 --- a/scripts/developer/tests/toolbarTest.js +++ b/scripts/developer/tests/toolbarTest.js @@ -13,7 +13,7 @@ var toolBar = (function() { newZoneButton, newParticleButton - var toolIconUrl = Script.resolvePath("assets/images/tools/"); + var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/"); function initialize() { print("Toolbars: " + Toolbars); diff --git a/scripts/system/away.js b/scripts/system/away.js index 51d6277e21..04263d4223 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -158,6 +158,8 @@ function goAway() { return; } + UserActivityLogger.toggledAway(true); + isAway = true; print('going "away"'); wasMuted = AudioDevice.getMuted(); @@ -191,6 +193,9 @@ function goActive() { if (!isAway) { return; } + + UserActivityLogger.toggledAway(false); + isAway = false; print('going "active"'); if (!wasMuted) { diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 1772e55015..26f977f9b0 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -868,6 +868,7 @@ function MyController(hand) { }; this.createHotspots = function () { + var props, overlay; var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; var HAND_SPHERE_ALPHA = 0.7; @@ -884,7 +885,7 @@ function MyController(hand) { if (DRAW_HAND_SPHERES) { // add tiny spheres around the palm. var handPosition = this.getHandPosition(); - var overlay = Overlays.addOverlay("sphere", { + overlay = Overlays.addOverlay("sphere", { position: handPosition, size: HAND_SPHERE_RADIUS * 2, color: HAND_SPHERE_COLOR, @@ -909,7 +910,7 @@ function MyController(hand) { var _this = this; this.entityPropertyCache.getEntities().forEach(function (entityID) { if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) { - var props = _this.entityPropertyCache.getProps(entityID); + props = _this.entityPropertyCache.getProps(entityID); overlay = Overlays.addOverlay("sphere", { rotation: props.rotation, @@ -931,7 +932,7 @@ function MyController(hand) { } if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { - var props = _this.entityPropertyCache.getProps(entityID); + props = _this.entityPropertyCache.getProps(entityID); overlay = Overlays.addOverlay("sphere", { rotation: props.rotation, @@ -956,16 +957,17 @@ function MyController(hand) { this.updateHotspots = function() { var _this = this; + var props; this.hotspotOverlays.forEach(function (overlayInfo) { if (overlayInfo.type === "hand") { Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() }); } else if (overlayInfo.type === "equip") { _this.entityPropertyCache.updateEntity(overlayInfo.entityID); - var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + props = _this.entityPropertyCache.getProps(overlayInfo.entityID); Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); } else if (overlayInfo.type === "near") { _this.entityPropertyCache.updateEntity(overlayInfo.entityID); - var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + props = _this.entityPropertyCache.getProps(overlayInfo.entityID); Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); } }); diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 0f1e23b45c..374be0d1a1 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars: true, plusplus: true*/ -/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ // // handControllerPointer.js @@ -14,15 +14,11 @@ // // Control the "mouse" using hand controller. (HMD and desktop.) -// For now: -// Hydra thumb button 3 is left-mouse, button 4 is right-mouse. -// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse). // First-person only. // Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed. // (For now, the thumb buttons on both controllers are always on.) -// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. -// Otherwise, the active hand controller shows a red ball where a click will act. - +// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand +// controller beam intersects the HUD. // UTILITIES ------------- @@ -89,20 +85,15 @@ function Trigger(label) { state = 'full'; } else if (that.triggerSmoothedReleased()) { state = null; - // These depend on previous state: - // null -> squeezed ==> partial - // full -> !squeezed ==> partial - // Otherwise no change. } else if (that.triggerSmoothedSqueezed()) { - if (!state) { - state = 'partial'; - } - } else if (state === 'full') { + // Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use. + // In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from + // full to partial. state = 'partial'; } that.state = state; }; - // Answer a controller source function (answering either 0.0 or 1.0), with hysteresis. + // Answer a controller source function (answering either 0.0 or 1.0). that.partial = function () { return that.state ? 1.0 : 0.0; // either 'partial' or 'full' }; @@ -369,24 +360,9 @@ clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere -var BALL_SIZE = 0.011; -var BALL_ALPHA = 0.5; -var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; -var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: BALL_ALPHA}; -var fakeProjectionBall = Overlays.addOverlay("sphere", { - size: 5 * BALL_SIZE, - color: {red: 255, green: 10, blue: 10}, - ignoreRayIntersection: true, - alpha: BALL_ALPHA, - visible: false, - solid: true, - drawInFront: true // Even when burried inside of something, show it. -}); -var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers. -Script.scriptEnding.connect(function () { - overlays.forEach(Overlays.deleteOverlay); -}); -var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; function clearSystemLaser() { @@ -401,81 +377,30 @@ function setColoredLaser() { // answer trigger state if lasers supported, else f return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; } -function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD - if (!optionalEnableClicks) { - expireMouseCursor(); - clearSystemLaser(); - } else if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color - // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - systemLaserOn = setColoredLaser(); - Reticle.visible = !systemLaserOn; - } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { - clearSystemLaser(); - Reticle.visible = false; - } - - if (!visualizationIsShowing) { - return; - } - visualizationIsShowing = false; - overlays.forEach(function (overlay) { - Overlays.editOverlay(overlay, {visible: false}); - }); -} -var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. -function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { - ignore(controllerPosition, controllerDirection, hudPosition2d); - clearSystemLaser(); - // Show an indication of where the cursor will appear when crossing a HUD element, - // and where in-world clicking will occur. - // - // There are a number of ways we could do this, but for now, it's a blue sphere that rolls along - // the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click. - // We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired. - - function intersection3d(position, direction) { - // Answer in-world intersection (entity or 3d overlay), or way-out point - var pickRay = {origin: position, direction: direction}; - var result = findRayIntersection(pickRay); - return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction)); - } - - visualizationIsShowing = true; - // We'd rather in-world interactions be done at the termination of the hand beam - // -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab - // direclty manipulate both entity and 3d overlay objects. - // For now, though, we present a false projection of the cursor onto whatever is below it. This is - // different from the hand beam termination because the false projection is from the camera, while - // the hand beam termination is from the hand. - /* // FIXME: We can tighten this up later, once we know what will and won't be included. - var eye = Camera.getPosition(); - var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); - Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); - */ - Reticle.visible = false; - - return visualizationIsShowing; // In case we change caller to act conditionally. -} // MAIN OPERATIONS ----------- // function update() { var now = Date.now(); + function off() { + expireMouseCursor(); + clearSystemLaser(); + } if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); // Let them use mouse it in peace. + return off(); // Let them use mouse it in peace. } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); // What to do? menus can be behind hand! + return off(); // What to do? menus can be behind hand! } if (!Window.hasFocus() || !Reticle.allowMouseCapture) { - return turnOffVisualization(); // Don't mess with other apps or paused mouse activity + return off(); // Don't mess with other apps or paused mouse activity } leftTrigger.update(); rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { - return turnOffVisualization(); // Controller is cradled. + return off(); // Controller is cradled. } var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); @@ -487,7 +412,7 @@ function update() { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. } - return turnOffVisualization(); + return off(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); @@ -499,13 +424,22 @@ function update() { if (HMD.active) { Reticle.depth = hudReticleDistance(); } - return turnOffVisualization(true); + if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + systemLaserOn = setColoredLaser(); + Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; + } + return; } // We are not pointing at a HUD element (but it could be a 3d overlay). if (!activeTrigger.state) { - return turnOffVisualization(); // No trigger (with hysteresis). + return off(); // No trigger } - updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); + clearSystemLaser(); + Reticle.visible = false; } var UPDATE_INTERVAL = 50; // milliseconds. Script.update is too frequent. diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b39c843411..d1ac32e6f4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -150,6 +150,8 @@ function showMarketplace(marketplaceID) { marketplaceWindow.setURL(url); marketplaceWindow.setVisible(true); marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); } function hideMarketplace() { @@ -347,6 +349,7 @@ var toolBar = (function() { selectionManager.clearSelections(); cameraManager.disable(); } else { + UserActivityLogger.enabledEdit(); hasShownPropertiesTool = false; entityListTool.setVisible(true); gridTool.setVisible(true); diff --git a/scripts/system/examples.js b/scripts/system/examples.js index f850a9789e..a948f9e563 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -33,6 +33,8 @@ function showExamples(marketplaceID) { print("setting examples URL to " + url); examplesWindow.setURL(url); examplesWindow.setVisible(true); + + UserActivityLogger.openedMarketplace(); } function hideExamples() { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 82387cafa5..0af199ef56 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -452,6 +452,7 @@ var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); + var elTextFaceCamera = document.getElementById("property-text-face-camera"); var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); @@ -726,6 +727,7 @@ elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); + elTextFaceCamera = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; @@ -988,8 +990,8 @@ elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); + elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); - var textTextColorChangeFunction = createEmitColorPropertyUpdateFunction( 'textColor', elTextTextColorRed, elTextTextColorGreen, elTextTextColorBlue); elTextTextColorRed.addEventListener('change', textTextColorChangeFunction); @@ -1707,6 +1709,10 @@ +
+ + +
diff --git a/scripts/system/users.js b/scripts/system/users.js index c010b7ea24..5b0ba42a45 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var PopUpMenu = function (properties) { +var PopUpMenu = function(properties) { var value = properties.value, promptOverlay, valueOverlay, @@ -217,7 +217,7 @@ var PopUpMenu = function (properties) { }; }; -var usersWindow = (function () { +var usersWindow = (function() { var baseURL = Script.resolvePath("assets/images/tools/"), WINDOW_WIDTH = 260, @@ -253,7 +253,11 @@ var usersWindow = (function () { WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN, WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN, WINDOW_BORDER_RADIUS = 4, - WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 }, + WINDOW_BORDER_COLOR = { + red: 255, + green: 255, + blue: 255 + }, WINDOW_BORDER_ALPHA = 0.5, windowBorder, @@ -377,9 +381,12 @@ var usersWindow = (function () { isMirrorDisplay = false, isFullscreenMirror = false, - windowPosition = { }, // Bottom left corner of window pane. + windowPosition = {}, // Bottom left corner of window pane. isMovingWindow = false, - movingClickOffset = { x: 0, y: 0 }, + movingClickOffset = { + x: 0, + y: 0 + }, isUsingScrollbars = false, isMovingScrollbar = false, @@ -401,9 +408,7 @@ var usersWindow = (function () { } // Reserve space for title, friends button, and option controls - nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER - + windowLineHeight + VISIBILITY_SPACER - + windowLineHeight + WINDOW_BASE_MARGIN; + nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER + windowLineHeight + VISIBILITY_SPACER + windowLineHeight + WINDOW_BASE_MARGIN; // Limit window to height of viewport above window position minus VU meter and mirror if displayed windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight; @@ -456,17 +461,14 @@ var usersWindow = (function () { x: scrollbarBackgroundPosition.x, y: scrollbarBackgroundPosition.y }); - scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 - + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); Overlays.editOverlay(scrollbarBar, { x: scrollbarBackgroundPosition.x + 1, y: scrollbarBarPosition.y }); x = windowLeft + WINDOW_MARGIN; - y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER - - windowLineHeight - VISIBILITY_SPACER - - windowLineHeight - WINDOW_BASE_MARGIN; + y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER - windowLineHeight - VISIBILITY_SPACER - windowLineHeight - WINDOW_BASE_MARGIN; Overlays.editOverlay(friendsButton, { x: x, y: y @@ -554,9 +556,36 @@ var usersWindow = (function () { usersRequest.ontimeout = pollUsersTimedOut; usersRequest.onreadystatechange = processUsers; usersRequest.send(); + checkLoggedIn(); } - processUsers = function () { + var loggedIn = false; + + function checkLoggedIn() { + loggedIn = Account.isLoggedIn(); + if (loggedIn === false) { + Overlays.editOverlay(friendsButton, { + visible: false + }); + visibilityControl.setVisible(false); + displayControl.setVisible(false); + } else { + if (isMinimized === true) { + loggedIn = true; + return + } + Overlays.editOverlay(friendsButton, { + visible: true + }); + visibilityControl.setVisible(true); + displayControl.setVisible(true); + loggedIn = true; + + } + } + + + processUsers = function() { var response, myUsername, user, @@ -609,7 +638,7 @@ var usersWindow = (function () { } }; - pollUsersTimedOut = function () { + pollUsersTimedOut = function() { print("Error: Request for users status timed out"); usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. }; @@ -633,11 +662,15 @@ var usersWindow = (function () { Overlays.editOverlay(scrollbarBar, { visible: isVisible && isUsingScrollbars && !isMinimized }); - Overlays.editOverlay(friendsButton, { - visible: isVisible && !isMinimized - }); - displayControl.setVisible(isVisible && !isMinimized); - visibilityControl.setVisible(isVisible && !isMinimized); + + if (loggedIn === true) { + Overlays.editOverlay(friendsButton, { + visible: isVisible && !isMinimized + }); + displayControl.setVisible(isVisible && !isMinimized); + visibilityControl.setVisible(isVisible && !isMinimized); + } + } function setVisible(visible) { @@ -730,9 +763,7 @@ var usersWindow = (function () { userClicked = firstUserToDisplay + lineClicked; - if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX - && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { - //print("Go to " + usersOnline[linesOfUsers[userClicked]].username); + if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { location.goToUser(usersOnline[linesOfUsers[userClicked]].username); } @@ -800,12 +831,8 @@ var usersWindow = (function () { var isVisible; if (isMovingScrollbar) { - if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x - && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN - && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y - && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) { - scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) - / (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) { + scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) / (scrollbarBackgroundHeight - scrollbarBarHeight - 2); scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0); firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); updateOverlayPositions(); @@ -831,13 +858,9 @@ var usersWindow = (function () { isVisible = isBorderVisible; if (isVisible) { - isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x - && event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH - && windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y - && event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN; + isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x && event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH && windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y && event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN; } else { - isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH - && windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y; + isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH && windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y; } if (isVisible !== isBorderVisible) { isBorderVisible = isVisible; @@ -878,8 +901,7 @@ var usersWindow = (function () { isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM); isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM); - if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay - || isFullscreenMirror !== oldIsFullscreenMirror) { + if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay || isFullscreenMirror !== oldIsFullscreenMirror) { calculateWindowHeight(); updateUsersDisplay(); } @@ -929,8 +951,8 @@ var usersWindow = (function () { } else { hmdViewport = Controller.getRecommendedOverlayRect(); windowPosition = { - x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen. - y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far. + x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen. + y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far. }; } @@ -938,7 +960,7 @@ var usersWindow = (function () { windowBorder = Overlays.addOverlay("rectangle", { x: 0, - y: viewport.y, // Start up off-screen + y: viewport.y, // Start up off-screen width: WINDOW_BORDER_WIDTH, height: windowBorderHeight, radius: WINDOW_BORDER_RADIUS, @@ -1101,6 +1123,11 @@ var usersWindow = (function () { visible: isVisible && !isMinimized }); + + Script.setTimeout(function() { + checkLoggedIn() + }, 0); + Controller.mousePressEvent.connect(onMousePressEvent); Controller.mouseMoveEvent.connect(onMouseMoveEvent); Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); @@ -1143,4 +1170,4 @@ var usersWindow = (function () { setUp(); Script.scriptEnding.connect(tearDown); -}()); +}()); \ No newline at end of file diff --git a/scripts/tutorials/getDomainMetadata.js b/scripts/tutorials/getDomainMetadata.js new file mode 100644 index 0000000000..54c356ae7b --- /dev/null +++ b/scripts/tutorials/getDomainMetadata.js @@ -0,0 +1,129 @@ +// +// Created by Zach Pomerantz on June 16, 2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var SERVER = 'https://metaverse.highfidelity.com/api/v1'; + +var OVERLAY = null; + +// Every time you enter a domain, display the domain's metadata +location.hostChanged.connect(function(host) { + print('Detected host change:', host); + + // Fetch the domain ID from the metaverse + var placeData = request(SERVER + '/places/' + host); + if (!placeData) { + print('Cannot find place name - abandoning metadata request for', host); + return; + + } + var domainID = placeData.data.place.domain.id; + print('Domain ID:', domainID); + + // Fetch the domain metadata from the metaverse + var domainData = request(SERVER + '/domains/' + domainID); + print(SERVER + '/domains/' + domainID); + if (!domainData) { + print('Cannot find domain data - abandoning metadata request for', domainID); + return; + } + var metadata = domainData.domain; + print('Domain metadata:', JSON.stringify(metadata)); + + // Display the fetched metadata in an overlay + displayMetadata(host, metadata); +}); + +Script.scriptEnding.connect(clearMetadata); + +function displayMetadata(place, metadata) { + clearMetadata(); + + var COLOR_TEXT = { red: 255, green: 255, blue: 255 }; + var COLOR_BACKGROUND = { red: 0, green: 0, blue: 0 }; + var MARGIN = 200; + var STARTING_OPACITY = 0.8; + var FADE_AFTER_SEC = 2; + var FADE_FOR_SEC = 4; + + var fade_per_sec = STARTING_OPACITY / FADE_FOR_SEC; + var properties = { + color: COLOR_TEXT, + alpha: STARTING_OPACITY, + backgroundColor: COLOR_BACKGROUND, + backgroundAlpha: STARTING_OPACITY, + font: { size: 24 }, + x: MARGIN, + y: MARGIN + }; + + // Center the overlay on the screen + properties.width = Window.innerWidth - MARGIN*2; + properties.height = Window.innerHeight - MARGIN*2; + + // Parse the metadata into text + parsed = [ 'Welcome to ' + place + '!',, ]; + if (metadata.description) { + parsed.push(description); + } + if (metadata.tags && metadata.tags.length) { + parsed.push('Tags: ' + metadata.tags.join(',')); + } + if (metadata.capacity) { + parsed.push('Capacity (max users): ' + metadata.capacity); + } + if (metadata.maturity) { + parsed.push('Maturity: ' + metadata.maturity); + } + if (metadata.hosts && metadata.hosts.length) { + parsed.push('Hosts: ' + metadata.tags.join(',')); + } + if (metadata.online_users) { + parsed.push('Users online: ' + metadata.online_users); + } + + properties.text = parsed.join('\n\n'); + + // Display the overlay + OVERLAY = Overlays.addOverlay('text', properties); + + // Fade out the overlay over 10 seconds + !function() { + var overlay = OVERLAY; + var alpha = STARTING_OPACITY; + + var fade = function() { + // Only fade so long as the same overlay is up + if (overlay == OVERLAY) { + alpha -= fade_per_sec / 10; + if (alpha <= 0) { + clearMetadata(); + } else { + Overlays.editOverlay(overlay, { alpha: alpha, backgroundAlpha: alpha }); + Script.setTimeout(fade, 100); + } + } + }; + Script.setTimeout(fade, FADE_AFTER_SEC * 1000); + }(); +} + +function clearMetadata() { + if (OVERLAY) { + Overlays.deleteOverlay(OVERLAY); + } +} + +// Request JSON from a url, synchronously +function request(url) { + var req = new XMLHttpRequest(); + req.responseType = 'json'; + req.open('GET', url, false); + req.send(); + return req.status == 200 ? req.response : null; +} + diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index cf1152da02..3aac4db0a8 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -6,7 +6,7 @@ setup_hifi_project(Script Qml) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared gl script-engine plugins render-utils input-plugins display-plugins controllers) +link_hifi_libraries(shared gl script-engine plugins render-utils ui-plugins input-plugins display-plugins controllers) if (WIN32) diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 36ed566ea7..15b768bb36 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include #include @@ -90,8 +90,8 @@ public: virtual MainWindow* getPrimaryWindow() override { return nullptr; } virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual ui::Menu* getPrimaryMenu() override { return nullptr; } - virtual bool isForeground() override { return true; } - virtual const DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } + virtual bool isForeground() const override { return true; } + virtual DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; class MyControllerScriptingInterface : public controller::ScriptingInterface { diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 19f6a55bfd..47d0f6d601 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -57,7 +57,7 @@ ApplicationWindow { "particle-01.svg", ] property int iconIndex: 0 - readonly property string toolIconUrl: "file:///C:/Users/bdavi/git/hifi/scripts/system/assets/images/tools/" + readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" text: "Create Button" onClicked: { var name = icons[iconIndex];