Merge branch 'vive-ui' into feature/equip-hotspots

This commit is contained in:
Anthony J. Thibault 2016-06-23 15:42:55 -07:00
commit eabb8d08c0
100 changed files with 1572 additions and 588 deletions

View file

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

View file

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

View file

@ -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"
}
]
}
]
},

View file

@ -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 += "<td class='key'>" + rowIndexOrName + "</td>"
}
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 += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
+ "<input type='checkbox' class='form-control table-checkbox' "
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
} else if (isArray && col.type === "time" && col.editable) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
+ "<input type='time' class='form-control table-time' "
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>";
} else {
// Use a hidden input so that the values are posted.
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
@ -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")
}

View file

@ -10,16 +10,18 @@
#include "DomainMetadata.h"
#include <HifiConfigVariantMap.h>
#include <AccountManager.h>
#include <DependencyManager.h>
#include <HifiConfigVariantMap.h>
#include <LimitedNodeList.h>
#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));
DomainServer* server = static_cast<DomainServer*>(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<void(DomainMetadata::*)()>(&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<QVariantList>());
auto& targetList = *static_cast<QVariantList*>(target.data());
// if/when multiple ranges are allowed, this list will need to be iterated
assert(targetList[0].canConvert<QVariantList>());
auto& hours = *static_cast<QVariantList*>(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<QString>());
auto close = deltaHours.find(Hours::CLOSE);
if (close != deltaHours.end()) {
hours[CLOSE_INDEX] = close.value();
}
assert(hours[CLOSE_INDEX].canConvert<QString>());
}
void DomainMetadata::descriptorsChanged() {
// get descriptors
assert(_metadata[DESCRIPTORS].canConvert<QVariantMap>());
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
auto settings = static_cast<DomainServer*>(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<QVariantMap>());
auto& hours = *static_cast<QVariantMap*>(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<QVariantMap>());
auto& state = *static_cast<QVariantMap*>(_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<DomainServer*>(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<LimitedNodeList>();
@ -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<QVariantMap>());
auto& users = *static_cast<QVariantMap*>(_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<LimitedNodeList>()->getSessionUUID();
if (!domainID.isNull()) {
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
QString path { DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)) };
DependencyManager::get<AccountManager>()->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
}
}

View file

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

View file

@ -94,10 +94,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("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)));

View file

@ -172,13 +172,12 @@ private:
DomainServerSettingsManager _settingsManager;
DomainMetadata _metadata;
uint32_t _metadataTic{ 0 };
HifiSockAddr _iceServerSocket;
std::unique_ptr<NLPacket> _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<QHostAddress> _iceServerAddresses;
QSet<QHostAddress> _failedIceServerAddresses;
@ -190,6 +189,7 @@ private:
bool _hasAccessToken { false };
friend class DomainGatekeeper;
friend class DomainMetadata;
};

View file

@ -21,6 +21,8 @@
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QTimeZone>
#include <Assignment.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
@ -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<QHash<QString, NodePermissionsPointer>> 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<QString, NodePermissionsPointer> 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<QHash<QString, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions << _agentPermissions;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get();
foreach (auto permissionSet, permissionsSets) {
QHashIterator<QString, NodePermissionsPointer> i(permissionSet);
while (i.hasNext()) {

View file

@ -72,11 +72,11 @@ private:
friend class DomainServer;
void packPermissionsForMap(QString mapName, QHash<QString, NodePermissionsPointer> agentPermissions, QString keyPath);
void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath);
void packPermissions();
void unpackPermissions();
QHash<QString, NodePermissionsPointer> _standardAgentPermissions; // anonymous, logged-in, localhost
QHash<QString, NodePermissionsPointer> _agentPermissions; // specific account-names
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost
NodePermissionsMap _agentPermissions; // specific account-names
};
#endif // hifi_DomainServerSettingsManager_h

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -68,6 +68,7 @@
#include <input-plugins/InputPlugin.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StateController.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <LogHandler.h>
#include <MainWindow.h>
#include <MessagesClient.h>
@ -83,7 +84,6 @@
#include <PerfStat.h>
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
#include <plugins/PluginContainer.h>
#include <plugins/PluginManager.h>
#include <RenderableWebEntityItem.h>
#include <RenderShadowTask.h>
@ -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 <GPUIdent.h>
#include <gl/GLHelpers.h>
// 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<HMDScriptingInterface>();
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<ToolbarScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set<SpeechRecognizer>();
@ -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<PluginContainer*>(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<AddressManager>();
// 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<qint64>(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<qint64>(memInfo.totalMemoryBytes);
properties["system_memory_used"] = static_cast<qint64>(memInfo.usedMemoryBytes);
properties["process_memory_used"] = static_cast<qint64>(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<BandwidthRecorder>();
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<NodeList>();
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<AvatarManager>();
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<AudioClient>().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<DialogsManager>().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<LODManager>()->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<ScriptEngines>().data());
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().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<OffscreenUi>();
// Make the switch atomic from the perspective of other threads
@ -5310,3 +5432,49 @@ void Application::showDesktop() {
CompositorHelper& Application::getApplicationCompositor() const {
return *DependencyManager::get<CompositorHelper>();
}
// virtual functions required for PluginContainer
ui::Menu* Application::getPrimaryMenu() {
auto appMenu = _window->menuBar();
auto uiMenu = dynamic_cast<ui::Menu*>(appMenu);
return uiMenu;
}
void Application::showDisplayPluginsTools(bool show) {
DependencyManager::get<DialogsManager>()->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>();
framebufferCache->releaseFramebuffer(framebufferPointer);
}
void Application::releaseOverlayTexture(const gpu::TexturePointer& texture) {
_applicationOverlay.releaseOverlay(texture);
}
bool Application::isForeground() const {
return _isForeground && !_window->isMinimized();
}

View file

@ -34,6 +34,7 @@
#include <PhysicsEngine.h>
#include <plugins/Forward.h>
#include <plugins/DisplayPlugin.h>
#include <ui-plugins/PluginContainer.h>
#include <ScriptEngine.h>
#include <ShapeManager.h>
#include <SimpleMovingAverage.h>
@ -86,14 +87,32 @@ class Application;
#endif
#define qApp (static_cast<Application*>(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

View file

@ -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>();
accountManager->setSessionID(_sessionID);
accountManager->setSessionID(sessionID);
}
}

View file

@ -49,7 +49,6 @@ private:
DiscoverabilityManager();
Setting::Handle<int> _mode;
QString _sessionID;
QJsonObject _lastLocationObject;
};

View file

@ -1,78 +0,0 @@
#include "PluginContainerProxy.h"
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <plugins/Plugin.h>
#include <plugins/PluginManager.h>
#include <display-plugins/DisplayPlugin.h>
#include <DependencyManager.h>
#include <FramebufferCache.h>
#include "Application.h"
#include "MainWindow.h"
#include "GLCanvas.h"
#include "ui/DialogsManager.h"
#include <gl/OffscreenGLCanvas.h>
#include <QtGui/QOpenGLContext>
PluginContainerProxy::PluginContainerProxy() {
}
PluginContainerProxy::~PluginContainerProxy() {
}
ui::Menu* PluginContainerProxy::getPrimaryMenu() {
auto appMenu = qApp->_window->menuBar();
auto uiMenu = dynamic_cast<ui::Menu*>(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<DialogsManager>()->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>();
framebufferCache->releaseFramebuffer(framebufferPointer);
}
void PluginContainerProxy::releaseOverlayTexture(const gpu::TexturePointer& texture) {
qApp->_applicationOverlay.releaseOverlay(texture);
}

View file

@ -1,33 +0,0 @@
#pragma once
#ifndef hifi_PluginContainerProxy_h
#define hifi_PluginContainerProxy_h
#include <QtCore/QObject>
#include <QtCore/QRect>
#include <plugins/Forward.h>
#include <plugins/PluginContainer.h>
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<JointData>& jointDataVec) const {
}
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
if (_animSkeleton) {
if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._overrideFlags.size()) {
// transform all the default poses into rig space.
const AnimPose geometryToRigPose(_geometryToRigTransform);

View file

@ -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<glm::vec3> jointTranslations) {
"setJointTranslations", Qt::BlockingQueuedConnection,
Q_ARG(QVector<glm::vec3>, 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);

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@
#include <SettingHandle.h>
#include "DisplayPlugin.h"
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
static Setting::Handle<float> IPD_SCALE_HANDLE("hmd.ipdScale", 1.0f);

View file

@ -13,7 +13,7 @@
#include <QtGui/QGuiApplication>
#include <QtWidgets/QAction>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
const QString Basic2DWindowOpenGLDisplayPlugin::NAME("Desktop");

View file

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

View file

@ -10,7 +10,7 @@
#include "NullDisplayPlugin.h"
#include <QtGui/QImage>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
const QString NullDisplayPlugin::NAME("NullDisplayPlugin");

View file

@ -22,12 +22,13 @@
#include <NumericalConstants.h>
#include <DependencyManager.h>
#include <shared/NsightHelpers.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <gl/Config.h>
#include <gl/GLEscrow.h>
#include <GLMHelpers.h>
#include <CursorManager.h>
#include "CompositorHelper.h"
#include <ui/Menu.h>
#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> 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();
}

View file

@ -120,7 +120,7 @@ protected:
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
uint32_t _currentPresentFrameIndex { 0 };
float _compositeOverlayAlpha{ 1.0f };
gpu::TexturePointer _currentSceneTexture;
gpu::TexturePointer _currentOverlayTexture;
@ -165,4 +165,3 @@ private:
float _overlayAlpha{ 1.0f };
};

View file

@ -16,7 +16,7 @@
#include <QtWidgets/QWidget>
#include <GLMHelpers.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <CursorManager.h>
#include <gl/GLWidget.h>
#include <shared/NsightHelpers.h>
@ -420,8 +420,9 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve
}
void HmdDisplayPlugin::compositeExtra() {
std::array<HandLaserInfo, 2> handLasers;
std::array<mat4, 2> renderHandPoses;
const int NUMBER_OF_HANDS = 2;
std::array<HandLaserInfo, NUMBER_OF_HANDS> handLasers;
std::array<mat4, NUMBER_OF_HANDS> renderHandPoses;
Transform uiModelTransform;
withPresentThreadLock([&] {
handLasers = _handLasers;
@ -443,9 +444,9 @@ void HmdDisplayPlugin::compositeExtra() {
using namespace oglplus;
useProgram(_laserProgram);
_laserGeometry->Use();
std::array<mat4, 2> handLaserModelMatrices;
std::array<mat4, NUMBER_OF_HANDS> 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;
}

View file

@ -9,7 +9,7 @@
#include "SideBySideStereoDisplayPlugin.h"
#include <GLMHelpers.h>
#include <CursorManager.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <gl/GLWidget.h>
#include "../CompositorHelper.h"

View file

@ -15,7 +15,7 @@
#include <ViewFrustum.h>
#include <MatrixStack.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <gl/GLWidget.h>
#include <CursorManager.h>
#include "../CompositorHelper.h"

View file

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

View file

@ -5,6 +5,7 @@
#include <QtGui/QSurfaceFormat>
#include <QtOpenGL/QGL>
#include <QOpenGLContext>
#include <QtCore/QRegularExpression>
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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,9 +24,9 @@ using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
class NodePermissions {
public:
NodePermissions() { _id = QUuid::createUuid().toString(); }
NodePermissions(const QString& name) { _id = name; }
NodePermissions(const QString& name) { _id = name.toLower(); }
NodePermissions(QMap<QString, QVariant> 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<QString> keys() const { return _data.keys(); }
QHash<QString, NodePermissionsPointer> get() { return _data; }
void clear() { _data.clear(); }
private:
QHash<QString, NodePermissionsPointer> _data;
};
const NodePermissions DEFAULT_AGENT_PERMISSIONS;
QDebug operator<<(QDebug debug, const NodePermissions& perms);

View file

@ -18,6 +18,7 @@
#include "UserActivityLogger.h"
#include <DependencyManager.h>
#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);

View file

@ -20,6 +20,7 @@
#include <QNetworkReply>
#include <SettingHandle.h>
#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);

View file

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

View file

@ -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 <QObject>
#include <QJsonObject>
#include <DependencyManager.h>
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

View file

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

View file

@ -1,28 +1,6 @@
#include "DisplayPlugin.h"
#include <NumericalConstants.h>
#include <ui/Menu.h>
#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<std::mutex> lock(_paintDelayMutex);

View file

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

View file

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

View file

@ -14,10 +14,12 @@
#include <QtCore/QDebug>
#include <QtCore/QPluginLoader>
#include <DependencyManager.h>
#include <UserActivityLogger.h>
#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();
}
});

View file

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

View file

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

View file

@ -28,6 +28,7 @@
#ifdef Q_OS_WIN
#include "CPUIdent.h"
#include <Psapi.h>
#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<PROCESS_MEMORY_COUNTERS*>(&pmc), sizeof(pmc))) {
return false;
}
info.processUsedMemoryBytes = pmc.PrivateUsage;
info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage;
return true;
#endif
return false;
}

View file

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

View file

@ -0,0 +1,3 @@
set(TARGET_NAME ui-plugins)
setup_hifi_library(OpenGL)
link_hifi_libraries(shared plugins ui)

View file

@ -16,7 +16,7 @@
#include <QtCore/QPair>
#include <QtCore/QRect>
#include "Forward.h"
#include <plugins/Forward.h>
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<QString, QActionGroup*> _exclusiveGroups;
QRect _savedGeometry { 10, 120, 800, 600 };
};

View file

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

View file

@ -66,6 +66,7 @@ void SDL2Manager::init() {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
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)) {

View file

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

View file

@ -28,7 +28,7 @@
#include <NumericalConstants.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <SettingHandle.h>
#include <QLoggingCategory>
@ -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<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData);

View file

@ -18,7 +18,6 @@
#include <UserActivityLogger.h>
#include <PathUtils.h>
#include <plugins/PluginContainer.h>
#include <controllers/UserInputMapper.h>
const QString SpacemouseManager::NAME { "Spacemouse" };
@ -59,7 +58,6 @@ bool SpacemouseManager::activate() {
if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
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<UserInputMapper>();
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>();
userInputMapper->registerDevice(instance);
UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse");
emit deviceConnected(getName());
}
//let one axis be dominant
//ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL);

View file

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

View file

@ -13,7 +13,7 @@
#include <QtCore/QLoggingCategory>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StandardControls.h>

View file

@ -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<ovrPerfHudMode>((currentDebugMode + 1) % ovrPerfHud_Count);

View file

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

View file

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

View file

@ -26,7 +26,7 @@
#include <gl/OglplusHelpers.h>
#include <ViewFrustum.h>
#include "plugins/PluginContainer.h"
#include <ui-plugins/PluginContainer.h>
#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);
}

View file

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

View file

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

View file

@ -20,7 +20,7 @@
#include <controllers/Pose.h>
#include <PerfStat.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <ViewFrustum.h>
#include <display-plugins/CompositorHelper.h>
#include <shared/NsightHelpers.h>
@ -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);
}
}

View file

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

View file

@ -21,6 +21,9 @@
#include <OffscreenUi.h>
#include <controllers/Pose.h>
#include <NumericalConstants.h>
#include <ui-plugins/PluginContainer.h>
#include <ui/Menu.h>
#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<OffscreenUi>();
_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;
}
}

View file

@ -13,6 +13,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <controllers/Forward.h>
#include <plugins/Forward.h>
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();

View file

@ -18,7 +18,7 @@
#include <gpu/Context.h>
#include <DeferredLightingEffect.h>
#include <NumericalConstants.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <UserActivityLogger.h>
#include <OffscreenUi.h>
@ -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");
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,6 +33,8 @@ function showExamples(marketplaceID) {
print("setting examples URL to " + url);
examplesWindow.setURL(url);
examplesWindow.setVisible(true);
UserActivityLogger.openedMarketplace();
}
function hideExamples() {

View file

@ -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 @@
<label for="property-text-text">Text content</label>
<input type="text" id="property-text-text">
</div>
<div class="text-group text-section property checkbox">
<input type="checkbox" id="property-text-face-camera">
<label for="property-text-face-camera">&nbsp;Face Camera</label>
</div>
<div class="text-group text-section property number">
<label>Line height <span class="unit">m</span></label>
<input type="number" id="property-text-line-height" min="0" step="0.005">

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@
#include <QtQml/QQmlContext>
#include <plugins/Plugin.h>
#include <plugins/PluginContainer.h>
#include <ui-plugins/PluginContainer.h>
#include <plugins/PluginManager.h>
#include <input-plugins/InputPlugin.h>
#include <input-plugins/KeyboardMouseDevice.h>
@ -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 {

View file

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