Whitelist functionality for tablet apps

This commit is contained in:
Brad Davis 2017-10-25 16:49:23 -07:00
parent ff36cbf45f
commit d162e1cff6
28 changed files with 284 additions and 218 deletions

View file

@ -0,0 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Rectangle {
width: 100
height: 100
color: "white"
Rectangle {
width: 10
height: 10
color: "red"
}
Label {
text: OverlayWindowTestString
anchors.centerIn: parent
}
}

View file

@ -22,7 +22,6 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property var source;
property var component;
property var dynamicContent;
// Keyboard control properties in case needed by QML content.
@ -35,28 +34,9 @@ Windows.Window {
dynamicContent.destroy();
dynamicContent = null;
}
component = Qt.createComponent(source);
console.log("Created component " + component + " from source " + source);
}
onComponentChanged: {
console.log("Component changed to " + component)
populate();
}
function populate() {
console.log("Populate called: dynamicContent " + dynamicContent + " component " + component);
if (!dynamicContent && component) {
if (component.status == Component.Error) {
console.log("Error loading component:", component.errorString());
} else if (component.status == Component.Ready) {
console.log("Building dynamic content");
dynamicContent = component.createObject(contentHolder);
} else {
console.log("Component not yet ready, connecting to status change");
component.statusChanged.connect(populate);
}
}
QmlSurface.load(source, contentHolder, function(newObject) {
dynamicContent = newObject;
});
}
// Handle message traffic from the script that launched us to the loaded QML

View file

@ -2,6 +2,7 @@ import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import "."
import "../../styles-uit"
import "../audio" as HifiAudio
@ -12,6 +13,31 @@ Item {
property int columnIndex: 0
property int count: (flowMain.children.length - 1)
Component {
id: buttonComponent
TabletButton { }
}
Component.onCompleted: {
tablet.populateButtons();
}
function createClickedHandler(proxy) {
return function() { proxy.clicked(); }
}
function populateButtons() {
var tabletProxy = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var buttons = tabletProxy.getButtons();
for (var i = 0; i < buttons.length; i++) {
var proxy = buttons[i];
var button = tablet.addButtonProxy(proxy.getProperties());
button.clicked.connect(createClickedHandler(proxy));
proxy.setQmlButton(button);
}
sortButtons();
}
// used to look up a button by its uuid
function findButtonIndex(uuid) {
if (!uuid) {
@ -47,9 +73,7 @@ Item {
// called by C++ code when a button should be added to the tablet
function addButtonProxy(properties) {
var component = Qt.createComponent("TabletButton.qml");
var button = component.createObject(flowMain);
var button = buttonComponent.createObject(flowMain);
// copy all properites to button
var keys = Object.keys(properties).forEach(function (key) {
button[key] = properties[key];
@ -62,8 +86,6 @@ Item {
button.tabletRoot = parent.parent;
}
sortButtons();
return button;
}
@ -83,11 +105,8 @@ Item {
anchors {
top: parent.top
topMargin: 0
left: parent.left
leftMargin: 0
right: parent.right
rightMargin: 0
}
gradient: Gradient {

View file

@ -68,18 +68,17 @@ Item {
function loadSource(url) {
tabletApps.clear();
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.load(url)
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
}
function loadQMLOnTop(url) {
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.source = "";
loader.source = tabletApps.get(currentApp).appUrl;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
loader.load(tabletApps.get(currentApp).appUrl, function(){
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
})
}
function loadWebOnTop(url, injectJavaScriptUrl) {
@ -92,13 +91,11 @@ Item {
}
function loadWebBase() {
loader.source = "";
loader.source = "TabletWebView.qml";
loader.load("hifi/tablet/TabletWebView.qml");
}
function loadTabletWebBase() {
loader.source = "";
loader.source = "./BlocksWebView.qml";
loader.load("hifi/tablet/BlocksWebView.qml");
}
function returnToPreviousApp() {
@ -110,7 +107,7 @@ Item {
loadSource("TabletWebView.qml");
loadWebUrl(webUrl, scriptUrl);
} else {
loader.source = tabletApps.get(currentApp).appUrl;
loader.load(tabletApps.get(currentApp).appUrl);
}
}
@ -173,47 +170,72 @@ Item {
}
}
Loader {
id: loader
objectName: "loader"
asynchronous: false
width: parent.width
height: parent.height
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
onLoaded: {
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
Item {
id: loader
objectName: "loader";
anchors.fill: parent;
property string source: "";
property var item: null;
signal loaded;
onWidthChanged: {
if (loader.item) {
loader.item.width = loader.width;
}
}
onHeightChanged: {
if (loader.item) {
loader.item.height = loader.height;
}
}
function load(newSource, callback) {
loader.source = newSource;
loader.item = null;
QmlSurface.load(newSource, loader, function(newItem) {
loader.item = newItem;
loader.item.width = loader.width;
loader.item.height = loader.height;
loader.loaded();
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
}
if (callback) {
callback();
}
});
console.log("QQQ done calling QmlSurface.load")
}
}
width: 480
height: 706

View file

@ -2214,6 +2214,16 @@ extern void setupPreferences();
void Application::initializeUi() {
// Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "qrc:///qml/OverlayWindowTest.qml" },
[](QQmlContext* context) {
qDebug() << "Whitelist OverlayWindow worked";
context->setContextProperty("OverlayWindowTestString", "TestWorked");
});
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "qrc:///qml/hifi/audio/Audio.qml" },
[](QQmlContext* context) {
qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
});
AddressBarDialog::registerType();
ErrorDialog::registerType();
@ -2230,10 +2240,9 @@ void Application::initializeUi() {
auto surfaceContext = offscreenUi->getSurfaceContext();
offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use
offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml"));
offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
// FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus
@ -7194,13 +7203,17 @@ void Application::updateDisplayMode() {
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
// Make the switch atomic from the perspective of other threads
{
std::unique_lock<std::mutex> lock(_displayPluginLock);
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
bool wasRepositionLocked = false;
if (desktop) {
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
}
if (_displayPlugin) {
disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
@ -7246,7 +7259,6 @@ void Application::updateDisplayMode() {
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
auto desktop = offscreenUi->getDesktop();
if (desktop) {
desktop->setProperty("repositionLocked", wasRepositionLocked);
}

View file

@ -101,7 +101,7 @@ Menu::Menu() {
auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J);
connect(action, &QAction::triggered, [] {
static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml");
static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml");
static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml");
static const QString name("RunningScripts");
qApp->showDialog(widgetUrl, tabletUrl, name);
});
@ -338,7 +338,7 @@ Menu::Menu() {
connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
tablet->loadQMLSource("ControllerSettings.qml");
tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml");
if (!hmd->getShouldShowTablet()) {
hmd->toggleShouldShowTablet();

View file

@ -1,4 +1,4 @@
//
//
// WalletScriptingInterface.cpp
// interface/src/scripting
//
@ -23,7 +23,7 @@ void WalletScriptingInterface::refreshWalletStatus() {
wallet->getWalletStatus();
}
static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml";
static const QString CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href));

View file

@ -31,7 +31,7 @@
#include "scripting/HMDScriptingInterface.h"
static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml";
static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml";
template<typename T>
void DialogsManager::maybeCreateDialog(QPointer<T>& member) {
if (!member) {
@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
ConnectionFailureDialog::hide();
}
} else {
static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml");
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (visible) {
tablet->initialScreen(url);

View file

@ -46,7 +46,7 @@ void LoginDialog::showWithSelection()
if (tablet->getToolbarMode()) {
LoginDialog::show();
} else {
static const QUrl url("../../dialogs/TabletLoginDialog.qml");
static const QUrl url("dialogs/TabletLoginDialog.qml");
tablet->initialScreen(url);
if (!hmd->getShouldShowTablet()) {
hmd->openTablet();

View file

@ -264,7 +264,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI
}
}
static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
void ContextOverlayInterface::openInspectionCertificate() {
// lets open the tablet to the inspection certificate QML
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {

View file

@ -30,6 +30,8 @@
using namespace render;
using namespace render::entities;
static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml";
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount{ 0 };
// Don't allow more than 100 concurrent web views
@ -218,6 +220,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
};
{
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
_webSurface->create();
}
@ -289,7 +292,6 @@ void WebEntityRenderer::loadSourceURL() {
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
_lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) {
_contentType = htmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) {
@ -298,12 +300,11 @@ void WebEntityRenderer::loadSourceURL() {
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
}
_webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
item->setProperty("url", _lastSourceUrl);
});
} else {
_contentType = qmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
_webSurface->load(_lastSourceUrl);
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();

View file

@ -62,7 +62,7 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
QUrl url { properties[SOURCE_PROPERTY].toString() };
if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" &&
url.scheme() != "atp") {
url.scheme() != "atp" && url.scheme() != "qrc") {
properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString();
}

View file

@ -47,6 +47,7 @@
#include "types/HFWebEngineProfile.h"
#include "types/SoundEffect.h"
#include "TabletScriptingInterface.h"
#include "Logging.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
@ -98,7 +99,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list
}
QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {};
QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
struct TextureSet {
// The number of surfaces with this size
@ -586,10 +587,11 @@ void OffscreenQmlSurface::create() {
auto qmlEngine = acquireEngine(_quickWindow);
_qmlContext = new QQmlContext(qmlEngine->rootContext());
_qmlContext->setBaseUrl(QUrl{ "qrc:///qml/" });
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this);
_qmlContext->setContextProperty("QmlSurface", this);
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
@ -684,55 +686,69 @@ QQuickItem* OffscreenQmlSurface::getRootItem() {
return _rootItem;
}
void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl);
QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, bool forceNewContext) {
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
forceNewContext |= !callbacks.empty();
QQmlContext* targetContext = _qmlContext;
if (_rootItem && forceNewContext) {
targetContext = new QQmlContext(targetContext);
}
for (const auto& callback : callbacks) {
callback(targetContext);
}
return targetContext;
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) {
loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) {
QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem));
});
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) {
loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) {
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << qmlSource;
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);
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
createNewContext |= !callbacks.empty();
callbacks.push_back(onQmlLoadedCallback);
QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) {
targetContext = new QQmlContext(targetContext);
}
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << "resolved to " << finalQmlSource;
}
auto targetContext = contextForUrl(finalQmlSource, createNewContext);
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, callbacks);
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
});
return;
}
finishQmlLoad(qmlComponent, targetContext, callbacks);
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, false, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
@ -740,7 +756,8 @@ void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache();
}
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks) {
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
@ -762,6 +779,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
return;
}
if (!newObject) {
if (!_rootItem) {
qFatal("Could not load object as root item");
return;
}
qCWarning(uiLogging) << "Unable to load QML item";
return;
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
// All quick items should be focusable
@ -772,37 +805,26 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
}
// Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) {
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
}
// If we already have a root, just set a couple of flags and the ancestry
if (_rootItem) {
callback(qmlContext, newItem);
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
if (!parent) {
parent = _rootItem;
}
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(parent);
newItem->setParentItem(parent);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
// If we already have a root, just set a couple of flags and the ancestry
if (newItem && _rootItem) {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(_rootItem);
if (newItem) {
newItem->setParentItem(_rootItem);
}
return;
}
if (!newItem) {
qFatal("Could not load object as root item");
if (_rootItem) {
return;
}
@ -813,10 +835,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
if (_rootItem->objectName() == "tabletRoot") {
_qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem));
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this);
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
_qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
}
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newItem);
}
void OffscreenQmlSurface::updateQuick() {

View file

@ -30,12 +30,14 @@ class QQmlContext;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
class QJSValue;
// GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>;
using QmlContextCallback = std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenQmlSurface : public QObject {
Q_OBJECT
@ -43,7 +45,7 @@ class OffscreenQmlSurface : public QObject {
public:
static void setSharedContext(QOpenGLContext* context);
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
static QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
@ -56,10 +58,15 @@ public:
void resize(const QSize& size, bool forceResize = false);
QSize size() const;
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
// Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... })
Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
// For C++ use
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling
@ -73,7 +80,6 @@ public:
void resume();
bool isPaused() const;
void setBaseUrl(const QUrl& baseUrl);
QQuickItem* getRootItem();
QQuickWindow* getWindow();
QObject* getEventHandler();
@ -124,13 +130,13 @@ protected:
private:
static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks);
QQmlContext* contextForUrl(const QUrl& url, bool forceNewContext = false);
void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps);
void render();
void cleanup();
QJsonObject getGLContextData();
private slots:
void updateQuick();

View file

@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP
QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) {
auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface());
surface->create();
surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
surface->load(rootSource);
surface->resize(QSize(100, 100));
return surface;

View file

@ -210,9 +210,9 @@ QObject* TabletScriptingInterface::getFlags() {
// TabletProxy
//
static const char* TABLET_SOURCE_URL = "Tablet.qml";
static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "TabletMenu.qml";
static const char* TABLET_SOURCE_URL = "hifi/tablet/Tablet.qml";
static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml";
class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
@ -232,6 +232,15 @@ TabletProxy::~TabletProxy() {
disconnect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown);
}
QVariant TabletProxy::getButtons() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
QVariantList result;
for (const auto& button : _tabletButtonProxies) {
result.push_back(QVariant::fromValue(button.data()));
}
return result;
}
void TabletProxy::setToolbarMode(bool toolbarMode) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode));
@ -247,7 +256,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (toolbarMode) {
removeButtonsFromHomeScreen();
addButtonsToToolbar();
// create new desktop window
@ -267,7 +275,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
removeButtonsFromToolbar();
if (_currentPathLoaded == TABLET_SOURCE_URL) {
addButtonsToHomeScreen();
// Tablet QML now pulls buttons from Tablet proxy
} else {
loadHomeScreen(true);
}
@ -284,18 +292,20 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
}
}
#if 0
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (buttonProxy == NULL){
qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
qCCritical(uiLogging) << __FUNCTION__ << "buttonProxy is NULL";
return;
}
QVariant resultVar;
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << "adding button " << buttonProxy;
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties()));
if (!hasResult) {
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
qCWarning(uiLogging) << __FUNCTION__ << " has no result";
return;
}
@ -307,6 +317,8 @@ static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy*
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton));
}
#endif
static QString getUsername() {
QString username = "Unknown user";
@ -362,7 +374,7 @@ void TabletProxy::onTabletShown() {
static_cast<TabletScriptingInterface*>(parent())->playSound(TabletScriptingInterface::TabletOpen);
if (_showRunningScripts) {
_showRunningScripts = false;
pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml");
pushOntoStack("hifi/dialogs/TabletRunningScripts.qml");
}
}
}
@ -396,9 +408,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
});
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()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
}
@ -427,7 +436,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
} else {
removeButtonsFromHomeScreen();
_state = State::Uninitialized;
emit screenChanged(QVariant("Closed"), QVariant(""));
_currentPathLoaded = "";
@ -456,7 +464,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
}
if (root) {
removeButtonsFromHomeScreen();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
@ -530,7 +537,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) {
}
if (root) {
removeButtonsFromHomeScreen(); //works only in Tablet
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
_state = State::QML;
if (path != _currentPathLoaded) {
@ -612,8 +618,6 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
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()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
} else if (_toolbarMode && _desktopWindow) {
@ -674,7 +678,6 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
}
if (root) {
removeButtonsFromHomeScreen();
if (loadOtherBase) {
QMetaObject::invokeMethod(root, "loadTabletWebBase");
} else {
@ -701,12 +704,8 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
_tabletButtonProxies.push_back(tabletButtonProxy);
if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet();
if (tablet) {
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
} else {
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
// Tablet now pulls buttons from the tablet proxy
// FIXME emit a signal so that the tablet can refresh buttons if they change
} else if (_toolbarMode) {
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
if (toolbarProxy) {
@ -791,31 +790,11 @@ void TabletProxy::sendToQml(const QVariant& msg) {
}
}
void TabletProxy::addButtonsToHomeScreen() {
auto tablet = getQmlTablet();
if (!tablet || _toolbarMode) {
return;
}
for (auto& buttonProxy : _tabletButtonProxies) {
addButtonProxyToQmlTablet(tablet, buttonProxy.data());
}
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
}
OffscreenQmlSurface* TabletProxy::getTabletSurface() {
return _qmlOffscreenSurface;
}
void TabletProxy::removeButtonsFromHomeScreen() {
auto tablet = getQmlTablet();
for (auto& buttonProxy : _tabletButtonProxies) {
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
buttonProxy->setQmlButton(nullptr);
}
}
void TabletProxy::desktopWindowClosed() {
gotoHomeScreen();

View file

@ -196,6 +196,8 @@ public:
Q_INVOKABLE bool isPathLoaded(const QVariant& path);
Q_INVOKABLE QVariant getButtons();
QQuickItem* getTabletRoot() const { return _qmlTabletRoot; }
OffscreenQmlSurface* getTabletSurface();
@ -237,12 +239,10 @@ signals:
void tabletShownChanged();
protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
void emitWebEvent(const QVariant& msg);
void onTabletShown();
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);
void addButtonsToToolbar();
void removeButtonsFromToolbar();
@ -277,7 +277,9 @@ public:
TabletButtonProxy(const QVariantMap& properties);
~TabletButtonProxy();
void setQmlButton(QQuickItem* qmlButton);
Q_INVOKABLE void setQmlButton(QQuickItem* qmlButton);
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
QUuid getUuid() const { return _uuid; }

View file

@ -19,7 +19,7 @@
tablet.gotoHomeScreen();
onRecordingScreen = false;
} else {
tablet.loadQMLSource("InputRecorder.qml");
tablet.loadQMLSource("hifi/tablet/InputRecorder.qml");
onRecordingScreen = true;
}
}

View file

@ -1,7 +1,7 @@
print("Launching web window");
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
source: "qrc:///qml/OverlayWindowTest.qml",
height: 240,
width: 320,
toolWindow: false,

View file

@ -15,7 +15,7 @@
var TABLET_BUTTON_NAME = "AUDIO";
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
var AUDIO_QML_SOURCE = "../audio/Audio.qml";
var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml";
var MUTE_ICONS = {
icon: "icons/tablet-icons/mic-mute-i.svg",

View file

@ -26,8 +26,8 @@
// Relevant Variables:
// -WALLET_QML_SOURCE: The path to the Wallet QML
// -onWalletScreen: true/false depending on whether we're looking at the app.
var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml";
var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var onWalletScreen = false;
function onButtonClicked() {
if (!tablet) {

View file

@ -656,7 +656,7 @@ var toolBar = (function () {
selectionDisplay.triggerMapping.disable();
tablet.landscape = false;
} else {
tablet.loadQMLSource("Edit.qml", true);
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
UserActivityLogger.enabledEdit();
entityListTool.setVisible(true);
gridTool.setVisible(true);

View file

@ -18,7 +18,7 @@
var buttonName = "Settings";
var toolBar = null;
var tablet = null;
var settings = "TabletGeneralPreferences.qml"
var settings = "hifi/tablet/TabletGeneralPreferences.qml"
function onClicked(){
if (tablet) {
tablet.loadQMLSource(settings);

View file

@ -346,7 +346,7 @@
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
case 'purchases_openGoTo':
tablet.loadQMLSource("TabletAddressDialog.qml");
tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml");
break;
case 'purchases_itemCertificateClicked':
setCertificateInfo("", message.itemCertificateId);

View file

@ -40,7 +40,7 @@ var HOVER_TEXTURES = {
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
var PAL_QML_SOURCE = "../Pal.qml";
var PAL_QML_SOURCE = "hifi/Pal.qml";
var conserveResources = true;
Script.include("/~/system/libraries/controllers.js");

View file

@ -24,7 +24,7 @@
print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
}
var gotoQmlSource = "TabletAddressDialog.qml";
var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml";
var buttonName = "GOTO";
var onGotoScreen = false;
var shouldActivateButton = false;

View file

@ -24,7 +24,7 @@
if (onSkyboxChangerScreen) {
tablet.gotoHomeScreen();
} else {
tablet.loadQMLSource("../SkyboxChanger.qml");
tablet.loadQMLSource("hifi/SkyboxChanger.qml");
}
}

View file

@ -390,7 +390,7 @@
// Relevant Variables:
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml";
var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml";
var onSpectatorCameraScreen = false;
function onTabletButtonClicked() {
if (!tablet) {