Merge branch 'master' into 21396

This commit is contained in:
David Rowe 2017-06-24 09:19:12 +12:00
commit 6e266dfbac
63 changed files with 893 additions and 600 deletions

View file

@ -1 +1,2 @@
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
set(GRAPHVIZ_IGNORE_TARGETS "shared;networking")

View file

@ -11,7 +11,7 @@ endif ()
link_hifi_libraries(
audio avatars octree gpu model fbx entities
networking animation recording shared script-engine embedded-webserver
controllers physics plugins
physics plugins
)
if (WIN32)

View file

@ -166,19 +166,6 @@ void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> mes
}
}
DisplayPluginList getDisplayPlugins() {
DisplayPluginList result;
return result;
}
InputPluginList getInputPlugins() {
InputPluginList result;
return result;
}
// must be here to satisfy a reference in PluginManager::saveSettings()
void saveInputPluginSettings(const InputPluginList& plugins) {}
const std::pair<QString, CodecPluginPointer> AudioMixer::negotiateCodec(std::vector<QString> codecs) {
QString selectedCodecName;
CodecPluginPointer selectedCodec;

View file

@ -39,10 +39,26 @@ ScrollingWindow {
property alias x: root.x
property alias y: root.y
}
Timer {
id: refreshTimer
interval: 100
repeat: false
running: false
onTriggered: updateRunningScripts();
}
Component {
id: listModelBuilder
ListModel { }
}
Connections {
target: ScriptDiscoveryService
onScriptCountChanged: updateRunningScripts();
onScriptCountChanged: {
runningScriptsModel = listModelBuilder.createObject(root);
refreshTimer.restart();
}
}
Component.onCompleted: {
@ -65,10 +81,16 @@ ScrollingWindow {
b = simplify(b.path);
return a < b ? -1 : a > b ? 1 : 0;
});
runningScriptsModel.clear()
// Calling `runningScriptsModel.clear()` here instead of creating a new object
// triggers some kind of weird heap corruption deep inside Qt. So instead of
// modifying the model in place, possibly triggering behaviors in the table
// instead we create a new `ListModel`, populate it and update the
// existing model atomically.
var newRunningScriptsModel = listModelBuilder.createObject(root);
for (var i = 0; i < runningScripts.length; ++i) {
runningScriptsModel.append(runningScripts[i]);
newRunningScriptsModel.append(runningScripts[i]);
}
runningScriptsModel = newRunningScriptsModel;
}
function loadScript(script) {

View file

@ -99,8 +99,8 @@
#include <OctalCode.h>
#include <OctreeSceneStats.h>
#include <OffscreenUi.h>
#include <gl/OffscreenQmlSurfaceCache.h>
#include <gl/OffscreenGLCanvas.h>
#include <ui/OffscreenQmlSurfaceCache.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <PhysicsEngine.h>
@ -125,7 +125,8 @@
#include <ScriptEngines.h>
#include <ScriptCache.h>
#include <SoundCache.h>
#include <TabletScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <Tooltip.h>
#include <udt/PacketHeaders.h>
#include <UserActivityLogger.h>
@ -165,7 +166,6 @@
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/ToolbarScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
@ -440,6 +440,11 @@ static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
static const QString STATE_GROUNDED = "Grounded";
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
// Statically provided display and input plugins
extern DisplayPluginList getDisplayPlugins();
extern InputPluginList getInputPlugins();
extern void saveInputPluginSettings(const InputPluginList& plugins);
bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
const char** constArgv = const_cast<const char**>(argv);
@ -479,6 +484,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
Setting::init();
// Tell the plugin manager about our statically linked plugins
PluginManager::setInputPluginProvider([] { return getInputPlugins(); });
PluginManager::setDisplayPluginProvider([] { return getDisplayPlugins(); });
PluginManager::setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); });
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->init();
}
@ -902,6 +912,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_saveAvatarOverrideUrl = true;
}
QString defaultScriptsLocation = getCmdOption(argc, constArgv, "--scripts");
if (!defaultScriptsLocation.isEmpty()) {
PathUtils::defaultScriptsLocation(defaultScriptsLocation);
}
_glWidget = new GLCanvas();
getApplicationCompositor().setRenderingWidget(_glWidget);
_window->setCentralWidget(_glWidget);
@ -1166,7 +1181,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// force the model the look at the correct directory (weird order of operations issue)
scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation());
// do this as late as possible so that all required subsystems are initialized
scriptEngines->loadScripts();
// If we've overridden the default scripts location, just load default scripts
// otherwise, load 'em all
if (!defaultScriptsLocation.isEmpty()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
}
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -1791,6 +1814,8 @@ void Application::cleanupBeforeQuit() {
#endif
// stop QML
DependencyManager::destroy<TabletScriptingInterface>();
DependencyManager::destroy<ToolbarScriptingInterface>();
DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
@ -5552,8 +5577,16 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get<OffscreenUi>()->getFlags());
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<ToolbarProxy>, wrapperFromScriptValue<ToolbarProxy>);
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<ToolbarButtonProxy>, wrapperFromScriptValue<ToolbarButtonProxy>);
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<TabletProxy>, wrapperFromScriptValue<TabletProxy>);
qScriptRegisterMetaType(scriptEngine, wrapperToScriptValue<TabletButtonProxy>, wrapperFromScriptValue<TabletButtonProxy>);
scriptEngine->registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(DependencyManager::get<ToolbarScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
@ -5627,7 +5660,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
@ -5636,6 +5668,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
connect(scriptEngine, &ScriptEngine::warningMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onWarningMessage);
connect(scriptEngine, &ScriptEngine::infoMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onInfoMessage);
connect(scriptEngine, &ScriptEngine::clearDebugWindow, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onClearDebugWindow);
}
bool Application::canAcceptURL(const QString& urlString) const {
@ -5872,7 +5905,7 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const
void Application::showScriptLogs() {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
}

View file

@ -19,7 +19,7 @@
#include <AudioClient.h>
#include <CrashHelpers.h>
#include <DependencyManager.h>
#include <TabletScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include <display-plugins/DisplayPlugin.h>
#include <PathUtils.h>
#include <SettingHandle.h>
@ -121,9 +121,14 @@ Menu::Menu() {
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
// Edit > Reload All Scripts... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R,
scriptEngines.data(), SLOT(reloadAllScripts()),
action = addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R,
nullptr, nullptr,
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
connect(action, &QAction::triggered, [] {
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
DependencyManager::get<OffscreenUi>()->clearCache();
});
// Edit > Console... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J,
@ -303,7 +308,7 @@ Menu::Menu() {
// Settings > Avatar...
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
QString("../../hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
});
@ -626,7 +631,7 @@ Menu::Menu() {
action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats...");
connect(action, &QAction::triggered, [] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
});

View file

@ -9,18 +9,30 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <map>
#include "AudioDevices.h"
#include "Application.h"
#include "AudioClient.h"
#include "Audio.h"
#include "UserActivityLogger.h"
using namespace scripting;
Setting::Handle<QString> inputDeviceDesktop { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};
Setting::Handle<QString> outputDeviceDesktop { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};
Setting::Handle<QString> inputDeviceHMD { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
Setting::Handle<QString> outputDeviceHMD { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
static Setting::Handle<QString> desktopInputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};
static Setting::Handle<QString> desktopOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};
static Setting::Handle<QString> hmdInputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
static Setting::Handle<QString> hmdOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
if (mode == QAudio::AudioInput) {
return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting;
} else { // if (mode == QAudio::AudioOutput)
return contextIsHMD ? hmdOutputDeviceSetting : desktopOutputDeviceSetting;
}
}
QHash<int, QByteArray> AudioDeviceList::_roles {
{ Qt::DisplayRole, "display" },
@ -43,32 +55,37 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
}
bool AudioDeviceList::setData(const QModelIndex& index, const QVariant& value, int role) {
if (!index.isValid() || index.row() >= _devices.size()) {
if (!index.isValid() || index.row() >= _devices.size() || role != Qt::CheckStateRole) {
return false;
}
// only allow switching to a new device, not deactivating an in-use device
auto selected = value.toBool();
if (!selected) {
return false;
}
return setDevice(index.row(), true);
}
bool AudioDeviceList::setDevice(int row, bool fromUser) {
bool success = false;
auto& device = _devices[row];
if (role == Qt::CheckStateRole) {
auto selected = value.toBool();
auto& device = _devices[index.row()];
// skip if already selected
if (!device.selected) {
auto client = DependencyManager::get<AudioClient>();
QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, success),
Q_ARG(QAudio::Mode, _mode),
Q_ARG(const QAudioDeviceInfo&, device.info));
// only allow switching to a new device, not deactivating an in-use device
if (selected
// skip if already selected
&& selected != device.selected) {
auto client = DependencyManager::get<AudioClient>();
QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, success),
Q_ARG(QAudio::Mode, _mode),
Q_ARG(const QAudioDeviceInfo&, device.info));
if (success) {
device.selected = true;
emit deviceSelected(device.info);
emit deviceChanged(device.info);
if (success) {
device.selected = true;
if (fromUser) {
emit deviceSelected(device.info, _selectedDevice);
}
emit deviceChanged(device.info);
}
}
@ -88,12 +105,12 @@ void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) {
}
}
if (i < rowCount()) {
success = setData(createIndex(i, 0), true, Qt::CheckStateRole);
success = setDevice(i, false);
}
// the selection failed - reset it
if (!success) {
emit deviceSelected(QAudioDeviceInfo());
emit deviceSelected();
}
}
@ -167,48 +184,55 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
_inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput));
_outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput));
connect(&_inputs, &AudioDeviceList::deviceSelected, this, &AudioDevices::onInputDeviceSelected);
connect(&_outputs, &AudioDeviceList::deviceSelected, this, &AudioDevices::onOutputDeviceSelected);
connect(&_inputs, &AudioDeviceList::deviceSelected, [&](const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
onDeviceSelected(QAudio::AudioInput, device, previousDevice);
});
connect(&_outputs, &AudioDeviceList::deviceSelected, [&](const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
onDeviceSelected(QAudio::AudioOutput, device, previousDevice);
});
}
void AudioDevices::onContextChanged(const QString& context) {
QString input;
QString output;
if (_contextIsHMD) {
input = inputDeviceHMD.get();
output = outputDeviceHMD.get();
} else {
input = inputDeviceDesktop.get();
output = outputDeviceDesktop.get();
}
auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get();
auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get();
_inputs.resetDevice(_contextIsHMD, input);
_outputs.resetDevice(_contextIsHMD, output);
}
void AudioDevices::onInputDeviceSelected(const QAudioDeviceInfo& device) {
QString deviceName;
void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
QString deviceName = device.isNull() ? QString() : device.deviceName();
auto& setting = getSetting(_contextIsHMD, mode);
// check for a previous device
auto wasDefault = setting.get().isNull();
// store the selected device
setting.set(deviceName);
// log the selected device
if (!device.isNull()) {
deviceName = device.deviceName();
}
QJsonObject data;
if (_contextIsHMD) {
inputDeviceHMD.set(deviceName);
} else {
inputDeviceDesktop.set(deviceName);
}
}
const QString MODE = "audio_mode";
const QString INPUT = "INPUT";
const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudio::AudioInput ? INPUT : OUTPUT;
void AudioDevices::onOutputDeviceSelected(const QAudioDeviceInfo& device) {
QString deviceName;
if (!device.isNull()) {
deviceName = device.deviceName();
}
const QString CONTEXT = "display_mode";
data[CONTEXT] = _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
if (_contextIsHMD) {
outputDeviceHMD.set(deviceName);
} else {
outputDeviceDesktop.set(deviceName);
const QString DISPLAY = "display_device";
data[DISPLAY] = qApp->getActiveDisplayPlugin()->getName();
const QString DEVICE = "device";
const QString PREVIOUS_DEVICE = "previous_device";
const QString WAS_DEFAULT = "was_default";
data[DEVICE] = deviceName;
data[PREVIOUS_DEVICE] = previousDevice.deviceName();
data[WAS_DEFAULT] = wasDefault;
UserActivityLogger::getInstance().logAction("selected_audio_device", data);
}
}
@ -239,4 +263,4 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceI
static std::once_flag outputFlag;
std::call_once(outputFlag, initialize);
}
}
}

View file

@ -43,7 +43,8 @@ public:
void resetDevice(bool contextIsHMD, const QString& device);
signals:
void deviceSelected(const QAudioDeviceInfo& device);
void deviceSelected(const QAudioDeviceInfo& device = QAudioDeviceInfo(),
const QAudioDeviceInfo& previousDevice = QAudioDeviceInfo());
void deviceChanged(const QAudioDeviceInfo& device);
private slots:
@ -53,6 +54,8 @@ private slots:
private:
friend class AudioDevices;
bool setDevice(int index, bool fromUser);
static QHash<int, QByteArray> _roles;
static Qt::ItemFlags _flags;
@ -76,8 +79,7 @@ signals:
private slots:
void onContextChanged(const QString& context);
void onInputDeviceSelected(const QAudioDeviceInfo& device);
void onOutputDeviceSelected(const QAudioDeviceInfo& device);
void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice);
void onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device);
void onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);

View file

@ -1,63 +0,0 @@
//
// Created by Anthony J. Thibault on 2016-12-12
// Copyright 2013-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_QmlWrapper_h
#define hifi_QmlWrapper_h
#include <QtCore/QObject>
#include <OffscreenUi.h>
#include <DependencyManager.h>
class QmlWrapper : public QObject {
Q_OBJECT
public:
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
: QObject(parent), _qmlObject(qmlObject) {
}
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
});
}
Q_INVOKABLE void writeProperties(QVariant propertyMap) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
QVariantMap map = propertyMap.toMap();
for (const QString& key : map.keys()) {
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
}
});
}
Q_INVOKABLE QVariant readProperty(const QString& propertyName) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
return _qmlObject->property(propertyName.toStdString().c_str());
});
}
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
QVariantMap result;
for (const QVariant& property : propertyList.toList()) {
QString propertyString = property.toString();
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
}
return result;
});
}
protected:
QObject* _qmlObject{ nullptr };
};
#endif

View file

@ -1,104 +0,0 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-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 "ToolbarScriptingInterface.h"
#include <QtCore/QThread>
#include <OffscreenUi.h>
#include "QmlWrapper.h"
class ToolbarButtonProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
std::lock_guard<std::mutex> guard(_mutex);
_qmlButton = qobject_cast<QQuickItem*>(qmlObject);
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
}
Q_INVOKABLE void editProperties(QVariantMap properties) {
std::lock_guard<std::mutex> guard(_mutex);
QVariantMap::const_iterator iter = properties.constBegin();
while (iter != properties.constEnd()) {
_properties[iter.key()] = iter.value();
if (_qmlButton) {
// [01/25 14:26:20] [WARNING] [default] QMetaObject::invokeMethod: No such method ToolbarButton_QMLTYPE_195::changeProperty(QVariant,QVariant)
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection,
Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
}
++iter;
}
}
signals:
void clicked();
protected:
mutable std::mutex _mutex;
QQuickItem* _qmlButton { nullptr };
QVariantMap _properties;
};
class ToolbarProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { }
Q_INVOKABLE QObject* addButton(const QVariant& properties) {
QVariant resultVar;
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != _qmlObject->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
if (!invokeResult) {
return nullptr;
}
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
if (!rawButton) {
return nullptr;
}
return new ToolbarButtonProxy(rawButton, this);
}
Q_INVOKABLE void removeButton(const QVariant& name) {
QMetaObject::invokeMethod(_qmlObject, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, name));
}
};
QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != desktop->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
if (!invokeResult) {
return nullptr;
}
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
if (!rawToolbar) {
return nullptr;
}
return new ToolbarProxy(rawToolbar);
}
#include "ToolbarScriptingInterface.moc"

View file

@ -1,26 +0,0 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-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_ToolbarScriptingInterface_h
#define hifi_ToolbarScriptingInterface_h
#include <mutex>
#include <QtCore/QObject>
#include <DependencyManager.h>
class ToolbarProxy;
class ToolbarScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE QObject* getToolbar(const QString& toolbarId);
};
#endif // hifi_ToolbarScriptingInterface_h

View file

@ -17,6 +17,7 @@
#include <Application.h>
#include <MainWindow.h>
#include <PathUtils.h>
#include <ui/TabletScriptingInterface.h>
#include "AddressBarDialog.h"
#include "ConnectionFailureDialog.h"
@ -28,7 +29,6 @@
#include "PreferencesDialog.h"
#include "UpdateDialog.h"
#include "TabletScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml";

View file

@ -19,13 +19,13 @@
#include <NetworkingConstants.h>
#include <plugins/PluginManager.h>
#include <plugins/SteamClientPlugin.h>
#include <ui/TabletScriptingInterface.h>
#include "AccountManager.h"
#include "DependencyManager.h"
#include "Menu.h"
#include "Application.h"
#include "TabletScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
HIFI_QML_DEF(LoginDialog)

View file

@ -25,16 +25,15 @@
#include <GeometryCache.h>
#include <GeometryUtil.h>
#include <scripting/HMDScriptingInterface.h>
#include <gl/OffscreenQmlSurface.h>
#include <ui/OffscreenQmlSurface.h>
#include <ui/OffscreenQmlSurfaceCache.h>
#include <ui/TabletScriptingInterface.h>
#include <PathUtils.h>
#include <RegisteredMetaTypes.h>
#include <TabletScriptingInterface.h>
#include <TextureCache.h>
#include <UsersScriptingInterface.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <AbstractViewStateInterface.h>
#include <gl/OffscreenQmlSurface.h>
#include <gl/OffscreenQmlSurfaceCache.h>
#include <AddressManager.h>
#include "scripting/AccountScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
@ -87,7 +86,7 @@ Web3DOverlay::~Web3DOverlay() {
if (rootItem && rootItem->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr);
}
// Fix for crash in QtWebEngineCore when rapidly switching domains
@ -206,7 +205,7 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
// mark the TabletProxy object as cpp ownership.
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");

View file

@ -1,7 +1,7 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model procedural render render-utils)
setup_hifi_library(Widgets Network Script)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image ui)
include_hifi_library_headers(networking)
include_hifi_library_headers(gl)
include_hifi_library_headers(ktx)

View file

@ -23,7 +23,7 @@
#include <PathUtils.h>
#include <TextureCache.h>
#include <gpu/Context.h>
#include <TabletScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include "EntityTreeRenderer.h"
#include "EntitiesRendererLogging.h"
@ -74,19 +74,6 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
qWarning() << "Too many concurrent web views to create new view";
return false;
}
QString javaScriptToInject;
QFile webChannelFile(":qtwebchannel/qwebchannel.js");
QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
if (webChannelFile.open(QFile::ReadOnly | QFile::Text) &&
createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) {
QString webChannelStr = QTextStream(&webChannelFile).readAll();
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
// concatenate these js files
_javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
} else {
qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js";
}
// Save the original GL context, because creating a QML surface will create a new context
QOpenGLContext* currentContext = QOpenGLContext::currentContext();
@ -266,10 +253,7 @@ void RenderableWebEntityItem::loadSourceURL() {
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
}
_webSurface->load("WebEntityView.qml", [&](QQmlContext* context, QObject* obj) {
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
});
_webSurface->load("WebEntityView.qml");
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant());
@ -280,8 +264,7 @@ void RenderableWebEntityItem::loadSourceURL() {
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system",
_webSurface->getRootItem(), _webSurface.data());
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
}
}
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
@ -386,7 +369,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
if (rootItem && rootItem->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr);
}
// Fix for crash in QtWebEngineCore when rapidly switching domains

View file

@ -13,10 +13,9 @@
#include <QMouseEvent>
#include <QTouchEvent>
#include <PointerEvent.h>
#include <gl/OffscreenQmlSurface.h>
#include <ui/OffscreenQmlSurface.h>
#include <WebEntityItem.h>
#include <gl/OffscreenQmlSurface.h>
#include "RenderableEntityItem.h"
@ -74,8 +73,6 @@ private:
QMetaObject::Connection _mouseMoveConnection;
QMetaObject::Connection _hoverLeaveConnection;
QString _javaScriptToInject;
enum contentType {
htmlContent,
qmlContent

View file

@ -1,5 +1,5 @@
set(TARGET_NAME networking)
setup_hifi_library(Network WebEngine)
setup_hifi_library(Network)
link_hifi_libraries(shared)
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")

View file

@ -9,6 +9,7 @@
#include <vector>
#include <memory>
#include <functional>
enum class PluginType {
DISPLAY_PLUGIN,
@ -26,8 +27,12 @@ class PluginManager;
using DisplayPluginPointer = std::shared_ptr<DisplayPlugin>;
using DisplayPluginList = std::vector<DisplayPluginPointer>;
using DisplayPluginProvider = std::function<DisplayPluginList()>;
using InputPluginPointer = std::shared_ptr<InputPlugin>;
using InputPluginList = std::vector<InputPluginPointer>;
using InputPluginProvider = std::function<InputPluginList()>;
using CodecPluginPointer = std::shared_ptr<CodecPlugin>;
using CodecPluginList = std::vector<CodecPluginPointer>;
using CodecPluginProvider = std::function<CodecPluginList()>;
using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>;
using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>;

View file

@ -23,6 +23,26 @@
#include "InputPlugin.h"
#include "PluginLogging.h"
DisplayPluginProvider PluginManager::_displayPluginProvider = []()->DisplayPluginList { return {}; };
InputPluginProvider PluginManager::_inputPluginProvider = []()->InputPluginList { return {}; };
CodecPluginProvider PluginManager::_codecPluginProvider = []()->CodecPluginList { return {}; };
InputPluginSettingsPersister PluginManager::_inputSettingsPersister = [](const InputPluginList& list) {};
void PluginManager::setDisplayPluginProvider(const DisplayPluginProvider& provider) {
_displayPluginProvider = provider;
}
void PluginManager::setInputPluginProvider(const InputPluginProvider& provider) {
_inputPluginProvider = provider;
}
void PluginManager::setCodecPluginProvider(const CodecPluginProvider& provider) {
_codecPluginProvider = provider;
}
void PluginManager::setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister) {
_inputSettingsPersister = persister;
}
PluginManager* PluginManager::getInstance() {
static PluginManager _manager;
@ -117,12 +137,12 @@ const LoaderList& getLoadedPlugins() {
PluginManager::PluginManager() {
}
extern CodecPluginList getCodecPlugins();
const CodecPluginList& PluginManager::getCodecPlugins() {
static CodecPluginList codecPlugins;
static std::once_flag once;
std::call_once(once, [&] {
codecPlugins = _codecPluginProvider();
// Now grab the dynamic plugins
for (auto loader : getLoadedPlugins()) {
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());
@ -163,11 +183,6 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() {
#ifndef Q_OS_ANDROID
// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
extern DisplayPluginList getDisplayPlugins();
extern InputPluginList getInputPlugins();
extern void saveInputPluginSettings(const InputPluginList& plugins);
static DisplayPluginList displayPlugins;
const DisplayPluginList& PluginManager::getDisplayPlugins() {
@ -183,7 +198,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
std::call_once(once, [&] {
// Grab the built in plugins
displayPlugins = ::getDisplayPlugins();
displayPlugins = _displayPluginProvider();
// Now grab the dynamic plugins
@ -229,7 +244,7 @@ const InputPluginList& PluginManager::getInputPlugins() {
};
std::call_once(once, [&] {
inputPlugins = ::getInputPlugins();
inputPlugins = _inputPluginProvider();
// Now grab the dynamic plugins
for (auto loader : getLoadedPlugins()) {
@ -288,7 +303,7 @@ void PluginManager::disableInputs(const QStringList& inputs) {
}
void PluginManager::saveSettings() {
saveInputPluginSettings(getInputPlugins());
_inputSettingsPersister(getInputPlugins());
}
void PluginManager::shutdown() {

View file

@ -31,6 +31,18 @@ public:
void setContainer(PluginContainer* container) { _container = container; }
void shutdown();
// Application that have statically linked plugins can expose them to the plugin manager with these function
static void setDisplayPluginProvider(const DisplayPluginProvider& provider);
static void setInputPluginProvider(const InputPluginProvider& provider);
static void setCodecPluginProvider(const CodecPluginProvider& provider);
static void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister);
private:
static DisplayPluginProvider _displayPluginProvider;
static InputPluginProvider _inputPluginProvider;
static CodecPluginProvider _codecPluginProvider;
static InputPluginSettingsPersister _inputSettingsPersister;
PluginContainer* _container { nullptr };
};

View file

@ -16,6 +16,6 @@ if (NOT ANDROID)
endif ()
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
# ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
include_hifi_library_headers(gl)

View file

@ -14,7 +14,6 @@
#include <QtScript/QScriptEngine>
#include <ui/Menu.h>
#include "KeyEvent.h"
@ -35,7 +34,7 @@ public:
QKeySequence shortcutKeySequence; // this is what we actually use, it's set from one of the above
// location related items: in order of priority
int position { ui::Menu::UNSPECIFIED_POSITION };
int position { UNSPECIFIED_POSITION };
QString beforeItem;
QString afterItem;
@ -45,6 +44,9 @@ public:
bool isSeparator { false };
QString grouping; /// Either: "", "Advanced", or "Developer"
private:
static const int UNSPECIFIED_POSITION = -1;
};
Q_DECLARE_METATYPE(MenuItemProperties)
QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuItemProperties& props);

View file

@ -24,6 +24,8 @@
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMenu>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
@ -48,7 +50,6 @@
#include <ScriptAvatarData.h>
#include <udt/PacketHeaders.h>
#include <UUID.h>
#include <ui/Menu.h>
#include <controllers/ScriptingInterface.h>
#include <AnimationObject.h>
@ -70,7 +71,6 @@
#include "WebSocketClass.h"
#include "RecordingScriptingInterface.h"
#include "ScriptEngines.h"
#include "TabletScriptingInterface.h"
#include "ModelScriptingInterface.h"
@ -172,7 +172,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
emit unhandledException(exception);
}
}, Qt::DirectConnection);
setProcessEventsInterval(MSECS_PER_SECOND);
if (isEntityServerScript()) {
qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1";
@ -281,7 +281,7 @@ void ScriptEngine::runDebuggable() {
scriptDebugMenu = nullptr;
}
}
disconnect(timer);
disconnect(timer);
});
connect(timer, &QTimer::timeout, [this, timer] {
@ -340,7 +340,7 @@ void ScriptEngine::runInThread() {
QThread* workerThread = new QThread();
workerThread->setObjectName(QString("js:") + getFilename().replace("about:",""));
moveToThread(workerThread);
// NOTE: If you connect any essential signals for proper shutdown or cleanup of
// the script engine, make sure to add code to "reconnect" them to the
// disconnectNonEssentialSignals() method
@ -697,8 +697,6 @@ void ScriptEngine::init() {
// constants
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
qScriptRegisterMetaType(this, tabletToScriptValue, tabletFromScriptValue);
registerGlobalObject("Assets", &_assetScriptingInterface);
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
@ -1047,26 +1045,26 @@ void ScriptEngine::run() {
auto beforeSleep = clock::now();
// Throttle to SCRIPT_FPS
// We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
// We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
// calculate a sleepUntil to be the time from our start time until the original target
// sleepUntil for this frame. This approach will allow us to "catch up" in the event
// that some of our script udpates/frames take a little bit longer than the target average
// sleepUntil for this frame. This approach will allow us to "catch up" in the event
// that some of our script udpates/frames take a little bit longer than the target average
// to execute.
// NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach
const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));
// However, if our sleepUntil is not at least our average update and timer execution time
// into the future it means our script is taking too long in its updates, and we want to
// punish the script a little bit. So we will force the sleepUntil to be at least our
// However, if our sleepUntil is not at least our average update and timer execution time
// into the future it means our script is taking too long in its updates, and we want to
// punish the script a little bit. So we will force the sleepUntil to be at least our
// averageUpdate + averageTimerPerFrame time into the future.
auto averageUpdate = totalUpdates / thisFrame;
auto averageTimerPerFrame = _totalTimerExecution / thisFrame;
auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame;
auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate);
// We don't want to actually sleep for too long, because it causes our scripts to hang
// on shutdown and stop... so we want to loop and sleep until we've spent our time in
// We don't want to actually sleep for too long, because it causes our scripts to hang
// on shutdown and stop... so we want to loop and sleep until we've spent our time in
// purgatory, constantly checking to see if our script was asked to end
bool processedEvents = false;
while (!_isFinished && clock::now() < sleepUntil) {
@ -1399,7 +1397,7 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return QString();
}
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
QUrl url(moduleId);
auto displayId = moduleId;
@ -1465,7 +1463,7 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re
canonical.setPath(file.canonicalFilePath());
}
bool disallowOutsideFiles = !defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) {
return throwResolveError(makeError(message.arg(
QString("path '%1' outside of origin script '%2' '%3'")
@ -1750,7 +1748,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
return;
}
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
+ includeFiles.join(",") + "parent script:" + getFilename());
return; // bail early
}
@ -1762,7 +1760,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
bool isStandardLibrary = false;
if (file.startsWith("/~/")) {
thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
if (!defaultScriptsLoc.isParentOf(thisURL)) {
scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries");
continue;
@ -1774,7 +1772,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
bool disallowOutsideFiles = thisURL.isLocalFile() && !isStandardLibrary && !currentSandboxURL.isLocalFile();
if (disallowOutsideFiles && !PathUtils::isDescendantOf(thisURL, currentSandboxURL)) {
scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString()
scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString()
+ "outside of original entity script" + currentSandboxURL.toString());
} else {
// We could also check here for CORS, but we don't yet.
@ -1844,7 +1842,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
+ includeFile + "parent script:" + getFilename());
return; // bail early
}
@ -1862,12 +1860,12 @@ void ScriptEngine::load(const QString& loadFile) {
return;
}
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
+ loadFile + "parent script:" + getFilename());
return; // bail early
}
if (!currentEntityIdentifier.isInvalidID()) {
scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
+ loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString());
return; // bail early
}
@ -2548,7 +2546,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
qCDebug(scriptengine) << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
#endif
if (HIFI_AUTOREFRESH_FILE_SCRIPTS) {
refreshFileScript(entityID);
}

View file

@ -16,8 +16,6 @@
#include <UserActivityLogger.h>
#include <PathUtils.h>
#include <OffscreenUi.h>
#include "ScriptEngine.h"
#include "ScriptEngineLogging.h"
@ -74,7 +72,7 @@ ScriptEngines::ScriptEngines(ScriptEngine::Context context)
QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
if (rawScriptURL.scheme() == "file") {
QUrl fullNormal = rawScriptURL;
QUrl defaultScriptLoc = defaultScriptsLocation();
QUrl defaultScriptLoc = PathUtils::defaultScriptsLocation();
// if this url is something "beneath" the default script url, replace the local path with ~
if (fullNormal.scheme() == defaultScriptLoc.scheme() &&
@ -93,7 +91,7 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
QString expandScriptPath(const QString& rawPath) {
QStringList splitPath = rawPath.split("/");
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
return defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/"); // 2 to skip the slashes in /~/
}
@ -112,7 +110,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) {
QFileInfo fileInfo(url.toLocalFile());
url = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
QUrl defaultScriptsLoc = defaultScriptsLocation();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
if (!defaultScriptsLoc.isParentOf(url)) {
qCWarning(scriptengine) << "Script.include() ignoring file path" << rawScriptURL
<< "-- outside of standard libraries: "
@ -327,6 +325,13 @@ void ScriptEngines::saveScripts() {
return;
}
// don't save scripts if we started with --scripts, as we would overwrite
// the scripts that the user expects to be there when launched without the
// --scripts override.
if (_defaultScriptsLocationOverridden) {
return;
}
// Saves all currently running user-loaded scripts
QVariantList list;
@ -439,7 +444,6 @@ void ScriptEngines::setScriptsLocation(const QString& scriptsLocation) {
void ScriptEngines::reloadAllScripts() {
qCDebug(scriptengine) << "reloadAllScripts -- clearing caches";
DependencyManager::get<ScriptCache>()->clearCache();
DependencyManager::get<OffscreenUi>()->clearCache();
qCDebug(scriptengine) << "reloadAllScripts -- stopping all scripts";
stopAllScripts(true);
}
@ -541,11 +545,11 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
initializer(scriptEngine);
}
// FIXME disabling 'shift key' debugging for now. If you start up the application with
// the shift key held down, it triggers a deadlock because of script interfaces running
// FIXME disabling 'shift key' debugging for now. If you start up the application with
// the shift key held down, it triggers a deadlock because of script interfaces running
// on the main thread
auto const wantDebug = scriptEngine->isDebuggable(); // || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier);
if (HIFI_SCRIPT_DEBUGGABLES && wantDebug) {
scriptEngine->runDebuggable();
} else {
@ -581,5 +585,5 @@ void ScriptEngines::onScriptEngineError(const QString& scriptFilename) {
}
QString ScriptEngines::getDefaultScriptsLocation() const {
return defaultScriptsLocation().toString();
return PathUtils::defaultScriptsLocation().toString();
}

View file

@ -66,6 +66,8 @@ public:
Q_PROPERTY(QString defaultScriptsPath READ getDefaultScriptsLocation)
void defaultScriptsLocationOverridden(bool overridden) { _defaultScriptsLocationOverridden = overridden; };
// Called at shutdown time
void shutdownScripting();
bool isStopped() const { return _isStopped; }
@ -113,6 +115,7 @@ protected:
ScriptsModelFilter _scriptsModelFilter;
std::atomic<bool> _isStopped { false };
std::atomic<bool> _isReloading { false };
bool _defaultScriptsLocationOverridden { false };
};
QUrl normalizeScriptURL(const QUrl& rawScriptURL);

View file

@ -125,15 +125,15 @@ int ScriptsModel::columnCount(const QModelIndex& parent) const {
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
_fsWatcher.removePath(_localDirectory.absolutePath());
if (!newPath.isEmpty()) {
_localDirectory.setPath(newPath);
if (!_localDirectory.absolutePath().isEmpty()) {
_fsWatcher.addPath(_localDirectory.absolutePath());
}
}
reloadLocalFiles();
}
@ -154,7 +154,7 @@ void ScriptsModel::reloadDefaultFiles() {
}
void ScriptsModel::requestDefaultFiles(QString marker) {
QUrl url(defaultScriptsLocation());
QUrl url(PathUtils::defaultScriptsLocation());
// targets that don't have a scripts folder in the appropriate location will have an empty URL here
if (!url.isEmpty()) {
@ -244,7 +244,7 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
lastKey = xml.text().toString();
if (jsRegex.exactMatch(xml.text().toString())) {
QString localPath = lastKey.split("/").mid(1).join("/");
QUrl fullPath = defaultScriptsLocation();
QUrl fullPath = PathUtils::defaultScriptsLocation();
fullPath.setPath(fullPath.path() + lastKey);
const QString fullPathStr = normalizeScriptURL(fullPath).toString();
_treeNodes.append(new TreeNodeScript(localPath, fullPathStr, SCRIPT_ORIGIN_DEFAULT));

View file

@ -71,21 +71,33 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector<QSt
return newestFileName;
}
QUrl defaultScriptsLocation() {
// return "http://s3.amazonaws.com/hifi-public";
#ifdef Q_OS_WIN
QString path = QCoreApplication::applicationDirPath() + "/scripts";
#elif defined(Q_OS_OSX)
QString path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
#else
QString path = QCoreApplication::applicationDirPath() + "/scripts";
#endif
QUrl PathUtils::defaultScriptsLocation(const QString& newDefaultPath) {
static QString overriddenDefaultScriptsLocation = "";
QString path;
// set overriddenDefaultScriptLocation if it was passed in
if (!newDefaultPath.isEmpty()) {
overriddenDefaultScriptsLocation = newDefaultPath;
}
// use the overridden location if it is set
if (!overriddenDefaultScriptsLocation.isEmpty()) {
path = overriddenDefaultScriptsLocation;
} else {
#ifdef Q_OS_WIN
path = QCoreApplication::applicationDirPath() + "/scripts";
#elif defined(Q_OS_OSX)
path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
#else
path = QCoreApplication::applicationDirPath() + "/scripts";
#endif
}
// turn the string into a legit QUrl
QFileInfo fileInfo(path);
return QUrl::fromLocalFile(fileInfo.canonicalFilePath());
}
QString PathUtils::stripFilename(const QUrl& url) {
// Guard against meaningless query and fragment parts.
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())

View file

@ -13,7 +13,6 @@
#define hifi_PathUtils_h
#include <QtCore/QObject>
#include "DependencyManager.h"
/**jsdoc
@ -38,11 +37,11 @@ public:
static QString stripFilename(const QUrl& url);
// note: this is FS-case-sensitive version of parentURL.isParentOf(childURL)
static bool isDescendantOf(const QUrl& childURL, const QUrl& parentURL);
static QUrl defaultScriptsLocation(const QString& newDefault = "");
};
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions);
QString findMostRecentFileExtension(const QString& originalFileName, QVector<QString> possibleExtensions);
QUrl defaultScriptsLocation();
#endif // hifi_PathUtils_h

View file

@ -1,3 +1,8 @@
set(TARGET_NAME ui)
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns)
link_hifi_libraries(shared networking gl script-engine)
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns)
link_hifi_libraries(shared networking gl audio)
if (NOT ANDROID)
# Required for some low level GL interaction in the OffscreenQMLSurface
target_glew()
endif ()

View file

@ -20,7 +20,8 @@
#include <AbstractUriHandler.h>
#include <AccountManager.h>
#include <DependencyManager.h>
#include <TabletScriptingInterface.h>
#include "ui/TabletScriptingInterface.h"
#include "FileDialogHelper.h"
#include "VrMenu.h"

View file

@ -21,9 +21,9 @@
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QInputDialog>
#include <gl/OffscreenQmlSurface.h>
#include <DependencyManager.h>
#include "ui/OffscreenQmlSurface.h"
#include "OffscreenQmlElement.h"
class VrMenu;

View file

@ -6,7 +6,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OffscreenQmlSurface.h"
#include "Config.h"
// Has to come before Qt GL includes
#include <gl/Config.h>
#include <unordered_set>
#include <unordered_map>
@ -33,14 +35,17 @@
#include <NetworkAccessManager.h>
#include <GLMHelpers.h>
#include <shared/GlobalAppProperties.h>
#include <FileTypeProfile.h>
#include <HFWebEngineProfile.h>
#include <HFTabletWebEngineProfile.h>
#include "OffscreenGLCanvas.h"
#include "GLHelpers.h"
#include "GLLogging.h"
#include "Context.h"
#include <gl/OffscreenGLCanvas.h>
#include <gl/GLHelpers.h>
#include <gl/Context.h>
#include "types/FileTypeProfile.h"
#include "types/HFWebEngineProfile.h"
#include "types/HFTabletWebEngineProfile.h"
#include "types/SoundEffect.h"
#include "Logging.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
@ -272,7 +277,7 @@ QString getEventBridgeJavascript() {
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
} else {
qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
qCWarning(uiLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
}
return javaScriptToInject;
}
@ -297,6 +302,14 @@ private:
QQmlEngine* acquireEngine(QQuickWindow* window) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (QThread::currentThread() != qApp->thread()) {
qCWarning(uiLogging) << "Cannot acquire QML engine on any thread but the main thread";
}
static std::once_flag once;
std::call_once(once, [] {
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
});
if (!globalEngine) {
Q_ASSERT(0 == globalEngineRefCount);
globalEngine = new QQmlEngine();
@ -325,8 +338,6 @@ QQmlEngine* acquireEngine(QQuickWindow* window) {
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext));
}
++globalEngineRefCount;
@ -457,7 +468,7 @@ void OffscreenQmlSurface::onAboutToQuit() {
}
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
qCDebug(glLogging) << "Building QML surface";
qCDebug(uiLogging) << "Building QML surface";
_renderControl = new QMyQuickRenderControl();
connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; });
@ -548,7 +559,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
return;
}
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height();
qCDebug(uiLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height();
gl::withSavedContext([&] {
_canvas->makeCurrent();
@ -595,6 +606,9 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
}
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) {
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Called load on a non-surface thread";
}
// Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
@ -636,7 +650,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlCon
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
qCWarning(glLogging) << error.url() << error.line() << error;
qCWarning(uiLogging) << error.url() << error.line() << error;
}
qmlComponent->deleteLater();
return nullptr;
@ -646,7 +660,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlCon
QObject* newObject = qmlComponent->beginCreate(qmlContext);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
qCWarning(glLogging) << error.url() << error.line() << error;
qCWarning(uiLogging) << error.url() << error.line() << error;
}
if (!_rootItem) {
qFatal("Unable to finish loading QML root");

View file

@ -0,0 +1,59 @@
//
// Created by Bradley Austin Davis on 2017/06/22
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "QmlWrapper.h"
#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
QmlWrapper::QmlWrapper(QObject* qmlObject, QObject* parent)
: QObject(parent), _qmlObject(qmlObject) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
}
void QmlWrapper::writeProperty(QString propertyName, QVariant propertyValue) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "writeProperty", Q_ARG(QString, propertyName), Q_ARG(QVariant, propertyValue));
}
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
}
void QmlWrapper::writeProperties(QVariant propertyMap) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "writeProperties", Q_ARG(QVariant, propertyMap));
}
QVariantMap map = propertyMap.toMap();
for (const QString& key : map.keys()) {
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
}
}
QVariant QmlWrapper::readProperty(const QString& propertyName) {
if (QThread::currentThread() != thread()) {
QVariant result;
QMetaObject::invokeMethod(this, "readProperty", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, result), Q_ARG(QString, propertyName));
return result;
}
return _qmlObject->property(propertyName.toStdString().c_str());
}
QVariant QmlWrapper::readProperties(const QVariant& propertyList) {
if (QThread::currentThread() != thread()) {
QVariant result;
QMetaObject::invokeMethod(this, "readProperties", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, propertyList));
return result;
}
QVariantMap result;
for (const QVariant& property : propertyList.toList()) {
QString propertyString = property.toString();
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
}
return result;
}

View file

@ -0,0 +1,44 @@
//
// Created by Anthony J. Thibault on 2016-12-12
// Copyright 2013-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_QmlWrapper_h
#define hifi_QmlWrapper_h
#include <QtCore/QObject>
#include <QtCore/QVariant>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
class QmlWrapper : public QObject {
Q_OBJECT
public:
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr);
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue);
Q_INVOKABLE void writeProperties(QVariant propertyMap);
Q_INVOKABLE QVariant readProperty(const QString& propertyName);
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList);
protected:
QObject* _qmlObject{ nullptr };
};
template <typename T>
QScriptValue wrapperToScriptValue(QScriptEngine* engine, T* const &in) {
if (!in) {
return engine->undefinedValue();
}
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
template <typename T>
void wrapperFromScriptValue(const QScriptValue& value, T* &out) {
out = qobject_cast<T*>(value.toQObject());
}
#endif

View file

@ -9,66 +9,51 @@
#include "TabletScriptingInterface.h"
#include <QtCore/QThread>
#include <QtQml/QQmlProperty>
#include <AccountManager.h>
#include "DependencyManager.h"
#include <PathUtils.h>
#include <QmlWindowClass.h>
#include <QQmlProperty>
#include <DependencyManager.h>
#include <AccountManager.h>
#include <RegisteredMetaTypes.h>
#include "ScriptEngineLogging.h"
#include <OffscreenUi.h>
#include <InfoView.h>
#include "SoundEffect.h"
#include "../QmlWindowClass.h"
#include "../OffscreenUi.h"
#include "../InfoView.h"
#include "ToolbarScriptingInterface.h"
#include "Logging.h"
// FIXME move to global app properties
const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
QScriptValue tabletToScriptValue(QScriptEngine* engine, TabletProxy* const &in) {
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
void tabletFromScriptValue(const QScriptValue& value, TabletProxy* &out) {
out = qobject_cast<TabletProxy*>(value.toQObject());
}
TabletScriptingInterface::TabletScriptingInterface() {
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
qCDebug(uiLogging) << "Building tablet scripting interface";
}
QObject* TabletScriptingInterface::getSystemToolbarProxy() {
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != _toolbarScriptingInterface->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
TabletScriptingInterface::~TabletScriptingInterface() {
qCDebug(uiLogging) << "Destroying tablet scripting interface";
}
QObject* toolbarProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR));
if (hasResult) {
return toolbarProxy;
} else {
qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result";
return nullptr;
}
ToolbarProxy* TabletScriptingInterface::getSystemToolbarProxy() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
return _toolbarScriptingInterface->getToolbar(SYSTEM_TOOLBAR);
}
TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) {
TabletProxy* tabletProxy = nullptr;
{
// the only thing guarded should be map mutation
// this avoids a deadlock with the Main thread
// from Qt::BlockingQueuedEvent invocations later in the call-tree
std::lock_guard<std::mutex> guard(_mapMutex);
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "getTablet", Qt::BlockingQueuedConnection, Q_RETURN_ARG(TabletProxy*, tabletProxy), Q_ARG(QString, tabletId));
return tabletProxy;
}
auto iter = _tabletProxies.find(tabletId);
if (iter != _tabletProxies.end()) {
// tablet already exists
return iter->second;
} else {
// tablet must be created
tabletProxy = new TabletProxy(this, tabletId);
_tabletProxies[tabletId] = tabletProxy;
}
auto iter = _tabletProxies.find(tabletId);
if (iter != _tabletProxies.end()) {
// tablet already exists
return iter->second;
} else {
// tablet must be created
tabletProxy = new TabletProxy(this, tabletId);
_tabletProxies[tabletId] = tabletProxy;
}
assert(tabletProxy);
@ -78,42 +63,40 @@ TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) {
}
void TabletScriptingInterface::setToolbarMode(bool toolbarMode) {
{
// the only thing guarded should be _toolbarMode
// this avoids a deadlock with the Main thread
// from Qt::BlockingQueuedEvent invocations later in the call-tree
std::lock_guard<std::mutex> guard(_mapMutex);
_toolbarMode = toolbarMode;
}
Q_ASSERT(QThread::currentThread() == qApp->thread());
_toolbarMode = toolbarMode;
for (auto& iter : _tabletProxies) {
iter.second->setToolbarMode(toolbarMode);
}
}
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, OffscreenQmlSurface* qmlOffscreenSurface) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(tabletId));
if (tablet) {
tablet->setQmlTabletRoot(qmlTabletRoot, qmlOffscreenSurface);
tablet->setQmlTabletRoot(qmlOffscreenSurface);
} else {
qCWarning(scriptengine) << "TabletScriptingInterface::setupTablet() bad tablet object";
qCWarning(uiLogging) << "TabletScriptingInterface::setupTablet() bad tablet object";
}
}
QQuickWindow* TabletScriptingInterface::getTabletWindow() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(SYSTEM_TABLET));
QObject* qmlSurface = tablet->getTabletSurface();
OffscreenQmlSurface* surface = dynamic_cast<OffscreenQmlSurface*>(qmlSurface);
if (!surface) {
if (!tablet) {
return nullptr;
}
QQuickWindow* window = surface->getWindow();
return window;
auto* qmlSurface = tablet->getTabletSurface();
if (!qmlSurface) {
return nullptr;
}
return qmlSurface->getWindow();
}
void TabletScriptingInterface::processMenuEvents(QObject* object, const QKeyEvent* event) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
switch (event->key()) {
case Qt::Key_Down:
QMetaObject::invokeMethod(object, "nextItem");
@ -141,6 +124,7 @@ void TabletScriptingInterface::processMenuEvents(QObject* object, const QKeyEven
}
void TabletScriptingInterface::processTabletEvents(QObject* object, const QKeyEvent* event) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
switch (event->key()) {
case Qt::Key_Down:
QMetaObject::invokeMethod(object, "downItem");
@ -167,8 +151,8 @@ void TabletScriptingInterface::processTabletEvents(QObject* object, const QKeyEv
}
}
void TabletScriptingInterface::processEvent(const QKeyEvent* event) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(SYSTEM_TABLET));
QObject* qmlTablet = tablet->getQmlTablet();
QObject* qmlMenu = tablet->getQmlMenu();
@ -180,8 +164,8 @@ void TabletScriptingInterface::processEvent(const QKeyEvent* event) {
}
}
QObject* TabletScriptingInterface::getFlags()
{
QObject* TabletScriptingInterface::getFlags() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->getFlags();
}
@ -198,11 +182,24 @@ class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
};
TabletProxy::TabletProxy(QObject* parent, QString name) : QObject(parent), _name(name) {
TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) {
if (QThread::currentThread() != qApp->thread()) {
qCWarning(uiLogging) << "Creating tablet proxy on wrong thread " << _name;
}
}
TabletProxy::~TabletProxy() {
qCDebug(uiLogging) << "Destroying tablet proxy " << _name;
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Destroying tablet proxy on wrong thread" << _name;
}
}
void TabletProxy::setToolbarMode(bool toolbarMode) {
std::lock_guard<std::mutex> guard(_tabletMutex);
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode));
return;
}
if (toolbarMode == _toolbarMode) {
return;
@ -245,25 +242,23 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
}
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
QVariant resultVar;
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != qmlTablet->thread()) {
connectionType = Qt::BlockingQueuedConnection;
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (buttonProxy == NULL){
qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
return;
}
if (buttonProxy == NULL){
qCCritical(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
return;
}
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", connectionType,
QVariant resultVar;
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties()));
if (!hasResult) {
qCWarning(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
return;
}
QObject* qmlButton = qvariant_cast<QObject *>(resultVar);
if (!qmlButton) {
qCWarning(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
return;
}
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
@ -281,6 +276,11 @@ static QString getUsername() {
}
void TabletProxy::initialScreen(const QVariant& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "initialScreen", Q_ARG(QVariant, url));
return;
}
if (_qmlTabletRoot) {
pushOntoStack(url);
} else {
@ -290,34 +290,49 @@ void TabletProxy::initialScreen(const QVariant& url) {
}
bool TabletProxy::isMessageDialogOpen() {
if (_qmlTabletRoot) {
QVariant result;
QMetaObject::invokeMethod(_qmlTabletRoot, "isDialogOpen",Qt::DirectConnection,
Q_RETURN_ARG(QVariant, result));
return result.toBool();
if (QThread::currentThread() != thread()) {
bool result = false;
QMetaObject::invokeMethod(this, "isMessageDialogOpen", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return false;
if (!_qmlTabletRoot) {
return false;
}
QVariant result;
QMetaObject::invokeMethod(_qmlTabletRoot, "isDialogOpen",Qt::DirectConnection,
Q_RETURN_ARG(QVariant, result));
return result.toBool();
}
void TabletProxy::emitWebEvent(QVariant msg) {
void TabletProxy::emitWebEvent(const QVariant& msg) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg));
return;
}
emit webEventReceived(msg);
}
bool TabletProxy::isPathLoaded(QVariant path) {
bool TabletProxy::isPathLoaded(const QVariant& path) {
if (QThread::currentThread() != thread()) {
bool result = false;
QMetaObject::invokeMethod(this, "isPathLoaded", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path));
return result;
}
return path.toString() == _currentPathLoaded.toString();
}
void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
std::lock_guard<std::mutex> guard(_tabletMutex);
_qmlOffscreenSurface = qmlOffscreenSurface;
_qmlTabletRoot = qmlTabletRoot;
if (_qmlTabletRoot && _qmlOffscreenSurface) {
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
_qmlOffscreenSurface = qmlOffscreenSurface;
_qmlTabletRoot = qmlOffscreenSurface ? qmlOffscreenSurface->getRootItem() : nullptr;
if (_qmlTabletRoot && _qmlOffscreenSurface) {
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)));
// forward qml surface events to interface js
connect(dynamic_cast<OffscreenQmlSurface*>(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) {
connect(_qmlOffscreenSurface, &OffscreenQmlSurface::fromQml, [this](QVariant message) {
if (message.canConvert<QJSValue>()) {
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
} else if (message.canConvert<QString>()) {
@ -330,7 +345,7 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
if (_toolbarMode) {
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
}
@ -360,9 +375,18 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
}
void TabletProxy::gotoHomeScreen() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "gotoHomeScreen");
return;
}
loadHomeScreen(false);
}
void TabletProxy::gotoMenuScreen(const QString& submenu) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "gotoMenuScreen", Q_ARG(QString, submenu));
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
@ -385,6 +409,11 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
}
void TabletProxy::loadQMLOnTop(const QVariant& path) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadQMLOnTop", Q_ARG(QVariant, path));
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
@ -396,11 +425,16 @@ void TabletProxy::loadQMLOnTop(const QVariant& path) {
QMetaObject::invokeMethod(root, "loadQMLOnTop", Q_ARG(const QVariant&, path));
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
} else {
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
}
}
void TabletProxy::returnToPreviousApp() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "returnToPreviousApp");
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
@ -412,11 +446,15 @@ void TabletProxy::returnToPreviousApp() {
QMetaObject::invokeMethod(root, "returnToPreviousApp");
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
} else {
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
}
}
void TabletProxy::loadQMLSource(const QVariant& path) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadQMLSource", Q_ARG(QVariant, path));
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
@ -435,11 +473,17 @@ void TabletProxy::loadQMLSource(const QVariant& path) {
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
} else {
qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
}
}
bool TabletProxy::pushOntoStack(const QVariant& path) {
if (QThread::currentThread() != thread()) {
bool result = false;
QMetaObject::invokeMethod(this, "pushOntoStack", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path));
return result;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
@ -455,13 +499,18 @@ bool TabletProxy::pushOntoStack(const QVariant& path) {
loadQMLSource(path);
}
} else {
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
qCDebug(uiLogging) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
}
return root;
}
void TabletProxy::popFromStack() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "popFromStack");
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
@ -473,15 +522,20 @@ void TabletProxy::popFromStack() {
auto stack = root->findChild<QQuickItem*>("stack");
QMetaObject::invokeMethod(stack, "popSource");
} else {
qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
qCDebug(uiLogging) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
}
}
void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadHomeScreen", Q_ARG(bool, forceOntoHomeScreen));
return;
}
if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) {
if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
} else if (_toolbarMode && _desktopWindow) {
@ -505,6 +559,11 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url) {
}
void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJavaScriptUrl) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadWebScreenOnTop", Q_ARG(QVariant, url), Q_ARG(QString, injectJavaScriptUrl));
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
@ -521,6 +580,10 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJ
}
void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "gotoWebScreen", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl));
return;
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
@ -540,57 +603,58 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
_currentPathLoaded = QVariant(url);
}
QObject* TabletProxy::addButton(const QVariant& properties) {
TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
if (QThread::currentThread() != thread()) {
TabletButtonProxy* result = nullptr;
QMetaObject::invokeMethod(this, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(TabletButtonProxy*, result), Q_ARG(QVariant, properties));
return result;
}
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
std::unique_lock<std::mutex> guard(_tabletMutex);
_tabletButtonProxies.push_back(tabletButtonProxy);
if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet();
if (tablet) {
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
} else {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != toolbarProxy->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
guard.unlock();
// copy properties from tablet button proxy to toolbar button proxy.
QObject* toolbarButtonProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
if (hasResult) {
tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy);
} else {
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
if (toolbarProxy) {
// copy properties from tablet button proxy to toolbar button proxy.
toolbarProxy->addButton(tabletButtonProxy->getProperties());
}
}
return tabletButtonProxy.data();
}
bool TabletProxy::onHomeScreen() {
if (QThread::currentThread() != thread()) {
bool result = false;
QMetaObject::invokeMethod(this, "onHomeScreen", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return _state == State::Home;
}
void TabletProxy::removeButton(QObject* tabletButtonProxy) {
std::unique_lock<std::mutex> guard(_tabletMutex);
void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "removeButton", Q_ARG(TabletButtonProxy*, tabletButtonProxy));
return;
}
auto tablet = getQmlTablet();
if (!tablet) {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
QSharedPointer<TabletButtonProxy> buttonProxy;
{
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
if (iter == _tabletButtonProxies.end()) {
qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
return;
}
buttonProxy = *iter;
@ -600,21 +664,24 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
if (!_toolbarMode && _qmlTabletRoot) {
buttonProxy->setQmlButton(nullptr);
if (tablet) {
guard.unlock();
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
// remove button from toolbarProxy
guard.unlock();
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
buttonProxy->setToolbarButtonProxy(nullptr);
if (toolbarProxy) {
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
buttonProxy->setToolbarButtonProxy(nullptr);
}
}
}
void TabletProxy::emitScriptEvent(QVariant msg) {
void TabletProxy::emitScriptEvent(const QVariant& msg) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitScriptEvent", Q_ARG(QVariant, msg));
return;
}
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
} else if (_toolbarMode && _desktopWindow) {
@ -622,7 +689,12 @@ void TabletProxy::emitScriptEvent(QVariant msg) {
}
}
void TabletProxy::sendToQml(QVariant msg) {
void TabletProxy::sendToQml(const QVariant& msg) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "sendToQml", Q_ARG(QVariant, msg));
return;
}
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
} else if (_toolbarMode && _desktopWindow) {
@ -635,8 +707,6 @@ void TabletProxy::addButtonsToHomeScreen() {
if (!tablet || _toolbarMode) {
return;
}
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
for (auto& buttonProxy : _tabletButtonProxies) {
addButtonProxyToQmlTablet(tablet, buttonProxy.data());
}
@ -644,7 +714,7 @@ void TabletProxy::addButtonsToHomeScreen() {
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
}
QObject* TabletProxy::getTabletSurface() {
OffscreenQmlSurface* TabletProxy::getTabletSurface() {
return _qmlOffscreenSurface;
}
@ -663,35 +733,23 @@ void TabletProxy::desktopWindowClosed() {
}
void TabletProxy::addButtonsToToolbar() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != toolbarProxy->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
Q_ASSERT(QThread::currentThread() == thread());
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
for (auto& buttonProxy : _tabletButtonProxies) {
// copy properties from tablet button proxy to toolbar button proxy.
QObject* toolbarButtonProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties()));
if (hasResult) {
buttonProxy->setToolbarButtonProxy(toolbarButtonProxy);
} else {
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
}
buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties()));
}
// make the toolbar visible
QMetaObject::invokeMethod(toolbarProxy, "writeProperty", Qt::AutoConnection, Q_ARG(QString, "visible"), Q_ARG(QVariant, QVariant(true)));
toolbarProxy->writeProperty("visible", QVariant(true));
}
void TabletProxy::removeButtonsFromToolbar() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
Q_ASSERT(QThread::currentThread() == thread());
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
for (auto& buttonProxy : _tabletButtonProxies) {
// remove button from toolbarProxy
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
buttonProxy->setToolbarButtonProxy(nullptr);
}
}
@ -753,34 +811,56 @@ TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) :
_properties[UUID_KEY] = _uuid;
_properties[OBJECT_NAME_KEY] = _uuid.toString();
_properties[STABLE_ORDER_KEY] = _stableOrder;
if (QThread::currentThread() != qApp->thread()) {
qCWarning(uiLogging) << "Creating tablet button proxy on wrong thread";
}
}
TabletButtonProxy::~TabletButtonProxy() {
qCDebug(uiLogging) << "Destroying tablet button proxy " ;
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Destroying tablet button proxy on wrong thread";
}
}
void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) {
std::lock_guard<std::mutex> guard(_buttonMutex);
Q_ASSERT(QThread::currentThread() == qApp->thread());
_qmlButton = qmlButton;
}
void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) {
std::lock_guard<std::mutex> guard(_buttonMutex);
Q_ASSERT(QThread::currentThread() == thread());
_toolbarButtonProxy = toolbarButtonProxy;
if (_toolbarButtonProxy) {
QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot()));
}
}
QVariantMap TabletButtonProxy::getProperties() const {
std::lock_guard<std::mutex> guard(_buttonMutex);
QVariantMap TabletButtonProxy::getProperties() {
if (QThread::currentThread() != thread()) {
QVariantMap result;
QMetaObject::invokeMethod(this, "getProperties", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariantMap, result));
return result;
}
return _properties;
}
void TabletButtonProxy::editProperties(QVariantMap properties) {
std::lock_guard<std::mutex> guard(_buttonMutex);
void TabletButtonProxy::editProperties(const QVariantMap& properties) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "editProperties", Q_ARG(QVariantMap, properties));
return;
}
QVariantMap::const_iterator iter = properties.constBegin();
while (iter != properties.constEnd()) {
_properties[iter.key()] = iter.value();
if (_qmlButton) {
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
const auto& key = iter.key();
const auto& value = iter.value();
if (!_properties.contains(key) || _properties[key] != value) {
_properties[iter.key()] = iter.value();
if (_qmlButton) {
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
}
}
++iter;
}
@ -789,5 +869,3 @@ void TabletButtonProxy::editProperties(QVariantMap properties) {
QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties));
}
}
#include "TabletScriptingInterface.moc"

View file

@ -26,9 +26,13 @@
#include <DependencyManager.h>
class ToolbarProxy;
class ToolbarScriptingInterface;
class TabletProxy;
class TabletButtonProxy;
class QmlWindowClass;
class OffscreenQmlSurface;
/**jsdoc
* @namespace Tablet
@ -37,9 +41,9 @@ class TabletScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
TabletScriptingInterface();
~TabletScriptingInterface();
void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
QObject* getSystemToolbarProxy();
void setToolbarScriptingInterface(ToolbarScriptingInterface* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
/**jsdoc
* Creates or retruns a new TabletProxy and returns it.
@ -51,7 +55,7 @@ public:
void setToolbarMode(bool toolbarMode);
void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
void setQmlTabletRoot(QString tabletId, OffscreenQmlSurface* offscreenQmlSurface);
void processEvent(const QKeyEvent* event);
@ -67,13 +71,14 @@ signals:
void tabletNotification();
private:
friend class TabletProxy;
void processMenuEvents(QObject* object, const QKeyEvent* event);
void processTabletEvents(QObject* object, const QKeyEvent* event);
ToolbarProxy* getSystemToolbarProxy();
protected:
std::mutex _mapMutex;
std::map<QString, TabletProxy*> _tabletProxies;
QObject* _toolbarScriptingInterface { nullptr };
ToolbarScriptingInterface* _toolbarScriptingInterface { nullptr };
bool _toolbarMode { false };
};
@ -90,19 +95,20 @@ class TabletProxy : public QObject {
Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape)
Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged)
public:
TabletProxy(QObject* parent, QString name);
TabletProxy(QObject* parent, const QString& name);
~TabletProxy();
void setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
QString getName() const { return _name; }
void setQmlTabletRoot(OffscreenQmlSurface* offscreenQmlSurface);
const QString getName() const { return _name; }
bool getToolbarMode() const { return _toolbarMode; }
void setToolbarMode(bool toolbarMode);
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
Q_INVOKABLE void initialScreen(const QVariant& url);
/**jsdoc
* transition to the home screen
* @function TabletProxy#gotoHomeScreen
@ -143,28 +149,28 @@ public:
* @param properties {Object} button properties UI_TABLET_HACK: enumerate these when we figure out what they should be!
* @returns {TabletButtonProxy}
*/
Q_INVOKABLE QObject* addButton(const QVariant& properties);
Q_INVOKABLE TabletButtonProxy* addButton(const QVariant& properties);
/**jsdoc
* removes button from the tablet
* @function TabletProxy.removeButton
* @param tabletButtonProxy {TabletButtonProxy} button to be removed
*/
Q_INVOKABLE void removeButton(QObject* tabletButtonProxy);
Q_INVOKABLE void removeButton(TabletButtonProxy* tabletButtonProxy);
/**jsdoc
* Used to send an event to the html/js embedded in the tablet
* @function TabletProxy#emitScriptEvent
* @param msg {object|string}
*/
Q_INVOKABLE void emitScriptEvent(QVariant msg);
Q_INVOKABLE void emitScriptEvent(const QVariant& msg);
/**jsdoc
* Used to send an event to the qml embedded in the tablet
* @function TabletProxy#sendToQml
* @param msg {object|string}
*/
Q_INVOKABLE void sendToQml(QVariant msg);
Q_INVOKABLE void sendToQml(const QVariant& msg);
/**jsdoc
* Check if the tablet is on the homescreen
@ -180,11 +186,11 @@ public:
Q_INVOKABLE void setLandscape(bool landscape) { _landscape = landscape; }
Q_INVOKABLE bool getLandscape() { return _landscape; }
Q_INVOKABLE bool isPathLoaded(QVariant path);
Q_INVOKABLE bool isPathLoaded(const QVariant& path);
QQuickItem* getTabletRoot() const { return _qmlTabletRoot; }
QObject* getTabletSurface();
OffscreenQmlSurface* getTabletSurface();
QQuickItem* getQmlTablet() const;
@ -225,7 +231,7 @@ signals:
protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
void emitWebEvent(QVariant msg);
void emitWebEvent(const QVariant& msg);
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);
@ -236,10 +242,9 @@ protected:
QVariant _initialPath { "" };
QVariant _currentPathLoaded { "" };
QString _name;
std::mutex _tabletMutex;
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
QQuickItem* _qmlTabletRoot { nullptr };
QObject* _qmlOffscreenSurface { nullptr };
OffscreenQmlSurface* _qmlOffscreenSurface { nullptr };
QmlWindowClass* _desktopWindow { nullptr };
bool _toolbarMode { false };
bool _tabletShown { false };
@ -251,9 +256,6 @@ protected:
Q_DECLARE_METATYPE(TabletProxy*);
QScriptValue tabletToScriptValue(QScriptEngine* engine, TabletProxy* const &in);
void tabletFromScriptValue(const QScriptValue& value, TabletProxy* &out);
/**jsdoc
* @class TabletButtonProxy
* @property uuid {QUuid} READ_ONLY: uniquely identifies this button
@ -263,6 +265,7 @@ class TabletButtonProxy : public QObject {
Q_PROPERTY(QUuid uuid READ getUuid)
public:
TabletButtonProxy(const QVariantMap& properties);
~TabletButtonProxy();
void setQmlButton(QQuickItem* qmlButton);
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
@ -274,14 +277,14 @@ public:
* @function TabletButtonProxy#getProperties
* @returns {ButtonProperties}
*/
Q_INVOKABLE QVariantMap getProperties() const;
Q_INVOKABLE QVariantMap getProperties();
/**jsdoc
* Replace the values of some of this button's properties
* @function TabletButtonProxy#editProperties
* @param {ButtonProperties} properties - set of properties to change
*/
Q_INVOKABLE void editProperties(QVariantMap properties);
Q_INVOKABLE void editProperties(const QVariantMap& properties);
public slots:
void clickedSlot() { emit clicked(); }
@ -297,12 +300,13 @@ signals:
protected:
QUuid _uuid;
int _stableOrder;
mutable std::mutex _buttonMutex;
QQuickItem* _qmlButton { nullptr };
QObject* _toolbarButtonProxy { nullptr };
QVariantMap _properties;
};
Q_DECLARE_METATYPE(TabletButtonProxy*);
/**jsdoc
* @typedef TabletButtonProxy.ButtonProperties
* @property {string} icon - url to button icon. (50 x 50)

View file

@ -0,0 +1,124 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-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 "ToolbarScriptingInterface.h"
#include <QtCore/QThread>
#include <QtQuick/QQuickItem>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
#include "../OffscreenUi.h"
QScriptValue toolbarToScriptValue(QScriptEngine* engine, ToolbarProxy* const &in) {
if (!in) {
return engine->undefinedValue();
}
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
void toolbarFromScriptValue(const QScriptValue& value, ToolbarProxy* &out) {
out = qobject_cast<ToolbarProxy*>(value.toQObject());
}
QScriptValue toolbarButtonToScriptValue(QScriptEngine* engine, ToolbarButtonProxy* const &in) {
if (!in) {
return engine->undefinedValue();
}
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
void toolbarButtonFromScriptValue(const QScriptValue& value, ToolbarButtonProxy* &out) {
out = qobject_cast<ToolbarButtonProxy*>(value.toQObject());
}
ToolbarButtonProxy::ToolbarButtonProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
_qmlButton = qobject_cast<QQuickItem*>(qmlObject);
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
}
void ToolbarButtonProxy::editProperties(const QVariantMap& properties) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "editProperties", Q_ARG(QVariantMap, properties));
return;
}
QVariantMap::const_iterator iter = properties.constBegin();
while (iter != properties.constEnd()) {
_properties[iter.key()] = iter.value();
if (_qmlButton) {
// [01/25 14:26:20] [WARNING] [default] QMetaObject::invokeMethod: No such method ToolbarButton_QMLTYPE_195::changeProperty(QVariant,QVariant)
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection,
Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
}
++iter;
}
}
ToolbarProxy::ToolbarProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
}
ToolbarButtonProxy* ToolbarProxy::addButton(const QVariant& properties) {
if (QThread::currentThread() != thread()) {
ToolbarButtonProxy* result = nullptr;
QMetaObject::invokeMethod(this, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ToolbarButtonProxy*, result), Q_ARG(QVariant, properties));
return result;
}
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
if (!invokeResult) {
return nullptr;
}
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
if (!rawButton) {
return nullptr;
}
return new ToolbarButtonProxy(rawButton, this);
}
void ToolbarProxy::removeButton(const QVariant& name) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "removeButton", Q_ARG(QVariant, name));
return;
}
QMetaObject::invokeMethod(_qmlObject, "removeButton", Q_ARG(QVariant, name));
}
ToolbarProxy* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
if (QThread::currentThread() != thread()) {
ToolbarProxy* result = nullptr;
QMetaObject::invokeMethod(this, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ToolbarProxy*, result), Q_ARG(QString, toolbarId));
return result;
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != desktop->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
if (!invokeResult) {
return nullptr;
}
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
if (!rawToolbar) {
return nullptr;
}
return new ToolbarProxy(rawToolbar);
}

View file

@ -0,0 +1,56 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-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_ToolbarScriptingInterface_h
#define hifi_ToolbarScriptingInterface_h
#include <mutex>
#include <QtCore/QObject>
#include <QtScript/QScriptValue>
#include <DependencyManager.h>
#include "QmlWrapper.h"
class QQuickItem;
class ToolbarButtonProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr);
Q_INVOKABLE void editProperties(const QVariantMap& properties);
signals:
void clicked();
protected:
QQuickItem* _qmlButton { nullptr };
QVariantMap _properties;
};
Q_DECLARE_METATYPE(ToolbarButtonProxy*);
class ToolbarProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr);
Q_INVOKABLE ToolbarButtonProxy* addButton(const QVariant& properties);
Q_INVOKABLE void removeButton(const QVariant& name);
};
Q_DECLARE_METATYPE(ToolbarProxy*);
class ToolbarScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE ToolbarProxy* getToolbar(const QString& toolbarId);
};
#endif // hifi_ToolbarScriptingInterface_h

View file

@ -41,5 +41,3 @@ void SoundEffect::play(QVariant position) {
_injector = AudioInjector::playSound(samples, options);
}
}
#include "SoundEffect.moc"

View file

@ -8,7 +8,7 @@
set(TARGET_NAME hifiCodec)
setup_hifi_client_server_plugin()
link_hifi_libraries(audio plugins input-plugins display-plugins)
link_hifi_libraries(audio plugins)
add_dependency_external_projects(hifiAudioCodec)
target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES})

View file

@ -10,7 +10,7 @@ if (APPLE OR WIN32)
set(TARGET_NAME hifiNeuron)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins)
link_hifi_libraries(shared controllers ui plugins input-plugins)
target_neuron()
endif()

View file

@ -8,5 +8,5 @@
set(TARGET_NAME hifiSdl2)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins input-plugins script-engine display-plugins)
link_hifi_libraries(shared controllers ui plugins input-plugins script-engine)
target_sdl2()

View file

@ -9,6 +9,6 @@
if (NOT ANDROID)
set(TARGET_NAME hifiSixense)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins display-plugins)
link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins)
target_sixense()
endif ()

View file

@ -8,6 +8,6 @@
set(TARGET_NAME pcmCodec)
setup_hifi_client_server_plugin()
link_hifi_libraries(shared plugins input-plugins display-plugins)
link_hifi_libraries(shared plugins)
install_beside_console()