Browse Source

Initial code for supporting an alternative QtQuick based UI for OpenVR

pull/45/head
dsc 4 months ago
parent
commit
96034902d1
  1. 6
      src/CMakeLists.txt
  2. 19
      src/appcontext.cpp
  3. 33
      src/appcontext.h
  4. 7
      src/libwalletqt/WalletManager.h
  5. 7
      src/main.cpp
  6. 2
      src/mainwindow.cpp
  7. 46
      src/model/TransactionHistoryModel.cpp
  8. 8
      src/model/TransactionHistoryModel.h
  9. 5
      src/utils/keysfiles.cpp
  10. 11
      src/utils/keysfiles.h
  11. 9
      src/utils/wsserver.cpp
  12. 107
      src/vr/main.cpp
  13. 38
      src/vr/main.h
  14. 74
      src/vr/openvr_init.cpp
  15. 16
      src/vr/openvr_init.h
  16. 484
      src/vr/overlaycontroller.cpp
  17. 134
      src/vr/overlaycontroller.h
  18. 58
      src/vr/utils/paths.cpp
  19. 16
      src/vr/utils/paths.h

6
src/CMakeLists.txt

@ -49,12 +49,10 @@ if(OPENVR)
"vr/utils/*.cpp"
)
list(APPEND SOURCE_FILES ${SOURCE_FILES_QML})
if(MINGW)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-deprecated-declarations") # @TODO: removeme
endif()
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-deprecated-declarations") # @TODO: removeme
add_subdirectory(libwalletqt)
add_subdirectory(model)
add_subdirectory(utils)

19
src/appcontext.cpp

@ -107,6 +107,8 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// Tor & socks proxy
this->ws = new WSClient(this, m_wsUrl);
connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage);
connect(this->ws, &WSClient::connectionEstablished, this, &AppContext::wsConnected);
connect(this->ws, &WSClient::closed, this, &AppContext::wsDisconnected);
// Store the wallet every 2 minutes
m_storeTimer.start(2 * 60 * 1000);
@ -330,6 +332,8 @@ void AppContext::onWalletOpened(Wallet *wallet) {
this->refreshed = false;
this->currentWallet = wallet;
this->walletPath = this->currentWallet->path() + ".keys";
QFileInfo fileInfo(this->currentWallet->path());
this->walletName = fileInfo.fileName();
this->walletViewOnly = this->currentWallet->viewOnly();
config()->set(Config::walletPath, this->walletPath);
@ -536,6 +540,12 @@ void AppContext::createConfigDirectory(const QString &dir) {
}
}
void AppContext::createWalletWithoutSpecifyingSeed(const QString &name, const QString &password) {
WowletSeed seed = WowletSeed(this->restoreHeights[this->networkType], this->coinName, this->seedLanguage);
auto path = QDir(this->defaultWalletDir).filePath(name);
this->createWallet(seed, path, password);
}
void AppContext::createWallet(WowletSeed seed, const QString &path, const QString &password) {
if(Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
@ -605,6 +615,9 @@ void AppContext::createWalletFinish(const QString &password) {
this->currentWallet->store();
this->walletPassword = password;
emit walletCreated(this->currentWallet);
// emit signal on behalf of walletManager, open wallet
this->walletManager->walletOpened(this->currentWallet);
}
void AppContext::initRestoreHeights() {
@ -835,7 +848,13 @@ void AppContext::updateBalance() {
AppContext::balance = balance_u / globals::cdiv;
double spendable = this->currentWallet->unlockedBalance();
// formatted
QString fmt_str = QString("Balance: %1 WOW").arg(Utils::balanceFormat(spendable));
if (balance > spendable)
fmt_str += QString(" (+%1 WOW unconfirmed)").arg(Utils::balanceFormat(balance - spendable));
emit balanceUpdated(balance_u, spendable);
emit balanceUpdatedFormatted(fmt_str);
}
void AppContext::syncStatusUpdated(quint64 height, quint64 target) {

33
src/appcontext.h

@ -60,9 +60,12 @@ public:
QString walletPath;
QString walletPassword = "";
QString walletName;
bool walletViewOnly = false;
NetworkType::Type networkType;
Q_PROPERTY(QString walletName MEMBER walletName);
QString applicationPath;
static void createConfigDirectory(const QString &dir) ;
@ -89,24 +92,20 @@ public:
static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory;
QList<WalletKeysFiles> listWallets() {
// return listing of wallet .keys items
m_walletKeysFilesModel->refresh();
return m_walletKeysFilesModel->listWallets();
}
// libwalletqt
bool refreshed = false;
WalletManager *walletManager;
Wallet *currentWallet = nullptr;
void createWallet(WowletSeed seed, const QString &path, const QString &password);
Q_INVOKABLE void createWalletWithoutSpecifyingSeed(const QString &name, const QString &password);
void createWalletViewOnly(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight);
void createWalletFinish(const QString &password);
void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance();
void initTor();
Q_INVOKABLE void initTor();
Q_INVOKABLE void initWS();
void initRestoreHeights();
void initWS();
void donateBeg();
void refreshModels();
void setWindowTitle(bool mining = false);
@ -115,8 +114,21 @@ public:
void closeWallet(bool emitClosedSignal = true, bool storeWallet = false);
void storeWallet();
Q_INVOKABLE QVariantList listWallets() {
m_walletKeysFilesModel->refresh();
QVariantList list;
for(const WalletKeysFiles &wallet: m_walletKeysFilesModel->listWallets())
list << wallet.toVariant();
return list;
}
Q_INVOKABLE QString displayAmount(quint64 amount) {
return Utils::balanceFormat(amount);
}
public slots:
void onOpenWallet(const QString& path, const QString &password);
Q_INVOKABLE void onOpenWallet(const QString& path, const QString &password);
void onCreateTransaction(QString address, quint64 amount, const QString description, bool all);
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
@ -151,6 +163,7 @@ signals:
void walletClosed();
void balanceUpdated(quint64 balance, quint64 spendable);
void balanceUpdatedFormatted(QString fmt);
void blockchainSync(int height, int target);
void refreshSync(int height, int target);
void synchronized();
@ -165,6 +178,8 @@ signals:
void createTransactionError(QString message);
void createTransactionCancelled(const QVector<QString> &address, double amount);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void wsConnected();
void wsDisconnected();
void redditUpdated(QList<QSharedPointer<RedditPost>> &posts);
void nodesUpdated(QList<QSharedPointer<WowletNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);

7
src/libwalletqt/WalletManager.h

@ -36,8 +36,10 @@ public:
LogLevel_Min = Monero::WalletManagerFactory::LogLevel_Min,
LogLevel_Max = Monero::WalletManagerFactory::LogLevel_Max,
};
explicit WalletManager(QObject *parent = nullptr);
static WalletManager * instance();
~WalletManager();
// wizard: createWallet path;
Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password,
const QString &language, NetworkType::Type nettype = NetworkType::MAINNET, quint64 kdfRounds = 1);
@ -187,9 +189,6 @@ public slots:
private:
friend class WalletPassphraseListenerImpl;
explicit WalletManager(QObject *parent = 0);
~WalletManager();
bool isMining() const;
static WalletManager * m_instance;

7
src/main.cpp

@ -114,12 +114,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
qRegisterMetaType<QVector<QString>>();
#ifdef QML
qputenv("QML_DISABLE_DISK_CACHE", "1");
#endif
if(openVREnabled) {
#ifdef HAS_OPENVR
QApplication vr_app(argc, argv);
auto *ctx = new AppContext(&parser);
auto *vr = new wowletVR::WowletVR(ctx, &parser, &vr_app);
qDebug() << "Context: " << qgetenv("QMLSCENE_DEVICE");
auto *vr = new wowletvr::WowletVR(ctx, &parser, &vr_app);
if(vr->errors.length() > 0)
return 1;

2
src/mainwindow.cpp

@ -574,8 +574,6 @@ void MainWindow::onWalletOpenedError(const QString &err) {
void MainWindow::onWalletCreated(Wallet *wallet) {
qDebug() << Q_FUNC_INFO;
// emit signal on behalf of walletManager
m_ctx->walletManager->walletOpened(wallet);
}
void MainWindow::onWalletOpened(Wallet *wallet) {

46
src/model/TransactionHistoryModel.cpp

@ -51,7 +51,7 @@ int TransactionHistoryModel::columnCount(const QModelIndex &parent) const {
return 0;
}
return Column::COUNT;
return TransactionInfoRole::COUNT;
}
QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const {
@ -70,14 +70,14 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
}
else if (role == Qt::TextAlignmentRole) {
switch (index.column()) {
case Column::Amount:
case Column::FiatAmount:
case TransactionInfoRole::Amount:
case TransactionInfoRole::FiatAmount:
result = Qt::AlignRight;
}
}
else if (role == Qt::DecorationRole) {
switch (index.column()) {
case Column::Date:
case TransactionInfoRole::Date:
{
if (tInfo.isFailed())
result = QVariant(m_warning);
@ -100,7 +100,7 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
}
else if (role == Qt::ToolTipRole) {
switch(index.column()) {
case Column::Date:
case TransactionInfoRole::Date:
{
if (tInfo.isFailed())
result = "Transaction failed";
@ -113,8 +113,8 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
}
else if (role == Qt::ForegroundRole) {
switch(index.column()) {
case Column::FiatAmount:
case Column::Amount:
case TransactionInfoRole::FiatAmount:
case TransactionInfoRole::Amount:
{
if (tInfo.direction() == TransactionInfo::Direction_Out) {
result = QVariant(QColor("#BC1E1E"));
@ -134,9 +134,19 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
{
switch (column)
{
case Column::Date:
case TransactionInfoRole::TransactionFailedRole:
return tInfo.isFailed();
case TransactionInfoRole::TransactionPendingRole:
return tInfo.isPending();
case TransactionInfoRole::TransactionConfirmationsRole:
return tInfo.confirmations();
case TransactionInfoRole::TransactionConfirmationsRequiredRole:
return tInfo.confirmationsRequired();
case TransactionInfoRole::Date:
return tInfo.timestamp().toString("yyyy-MM-dd HH:mm");
case Column::Description: {
case TransactionInfoRole::TransactionIsOutRole:
return tInfo.direction() == TransactionInfo::Direction_Out;
case TransactionInfoRole::Description: {
// if this tx is still in the pool, then we wont get the
// description. We've cached it inside `AppContext::txDescriptionCache`
// for the time being.
@ -147,15 +157,15 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
}
return tInfo.description();
}
case Column::Amount:
case TransactionInfoRole::Amount:
{
QString amount = QString::number(tInfo.balanceDelta() / globals::cdiv, 'f', 4);
amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount;
return amount;
}
case Column::TxID:
case TransactionInfoRole::TxID:
return tInfo.hash();
case Column::FiatAmount:
case TransactionInfoRole::FiatAmount:
{
double usd_price = AppContext::txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd"));
if (usd_price == 0.0)
@ -183,15 +193,15 @@ QVariant TransactionHistoryModel::headerData(int section, Qt::Orientation orient
}
if (orientation == Qt::Horizontal) {
switch(section) {
case Column::Date:
case TransactionInfoRole::Date:
return QString("Date");
case Column::Description:
case TransactionInfoRole::Description:
return QString("Description");
case Column::Amount:
case TransactionInfoRole::Amount:
return QString("Amount");
case Column::TxID:
case TransactionInfoRole::TxID:
return QString("Txid");
case Column::FiatAmount:
case TransactionInfoRole::FiatAmount:
return QString("Fiat");
default:
return QVariant();
@ -205,7 +215,7 @@ bool TransactionHistoryModel::setData(const QModelIndex &index, const QVariant &
QString hash;
switch (index.column()) {
case Column::Description:
case TransactionInfoRole::Description:
{
m_transactionHistory->transaction(index.row(), [this, &hash, &value](const TransactionInfo &tInfo){
hash = tInfo.hash();

8
src/model/TransactionHistoryModel.h

@ -21,15 +21,21 @@ class TransactionHistoryModel : public QAbstractTableModel
Q_PROPERTY(TransactionHistory * transactionHistory READ transactionHistory WRITE setTransactionHistory NOTIFY transactionHistoryChanged)
public:
enum Column
enum TransactionInfoRole
{
Date = 0,
Description,
Amount,
TxID,
FiatAmount,
TransactionIsOutRole,
TransactionFailedRole,
TransactionPendingRole,
TransactionConfirmationsRole,
TransactionConfirmationsRequiredRole,
COUNT
};
Q_ENUM(TransactionInfoRole)
explicit TransactionHistoryModel(QObject * parent = nullptr);
void setTransactionHistory(TransactionHistory * th);

5
src/utils/keysfiles.cpp

@ -13,6 +13,7 @@
using namespace std::chrono;
WalletKeysFiles::WalletKeysFiles() = default; // to please Q_DECLARE_METATYPE
WalletKeysFiles::WalletKeysFiles(const QFileInfo &info, int networkType, QString address) :
m_fileName(info.fileName()),
m_modified(info.lastModified().toSecsSinceEpoch()),
@ -47,6 +48,10 @@ int WalletKeysFiles::networkType() const {
return m_networkType;
}
QVariant WalletKeysFiles::toVariant() const {
return QVariant::fromValue<WalletKeysFiles>(*this);
}
QJsonObject WalletKeysFiles::toJsonObject() const {
auto item = QJsonObject();
item["fileName"] = m_fileName;

11
src/utils/keysfiles.h

@ -10,7 +10,9 @@
class WalletKeysFiles
{
Q_GADGET
public:
WalletKeysFiles();
WalletKeysFiles(const QFileInfo &info, int networkType, QString address);
QString fileName() const;
@ -20,6 +22,13 @@ public:
QString address() const;
QJsonObject toJsonObject() const;
QVariant toVariant() const;
Q_PROPERTY(qint64 modified READ modified)
Q_PROPERTY(QString fileName READ fileName)
Q_PROPERTY(QString path READ path)
Q_PROPERTY(QString address READ address)
Q_PROPERTY(int networkType READ networkType)
private:
QString m_fileName;
@ -28,6 +37,8 @@ private:
int m_networkType;
QString m_address;
};
Q_DECLARE_METATYPE(WalletKeysFiles)
class WalletKeysFilesModel : public QAbstractTableModel
{

9
src/utils/wsserver.cpp

@ -80,8 +80,8 @@ void WSServer::onNewConnection() {
// blast wallet listing on connect
QJsonArray arr;
for(const WalletKeysFiles &wallet: m_ctx->listWallets())
arr << wallet.toJsonObject();
for(const QVariant &wallet: m_ctx->listWallets())
arr << wallet.value<WalletKeysFiles>().toJsonObject();
auto welcomeWalletMessage = WSServer::createWSMessage("walletList", arr);
pSocket->sendBinaryMessage(welcomeWalletMessage);
@ -336,9 +336,6 @@ void WSServer::onWalletCreatedError(const QString &err) {
void WSServer::onWalletCreated(Wallet *wallet) {
auto obj = wallet->toJsonObject();
sendAll("walletCreated", obj);
// emit signal on behalf of walletManager
m_ctx->walletManager->walletOpened(wallet);
}
void WSServer::onSynchronized() {
@ -350,7 +347,7 @@ void WSServer::onWalletOpenPasswordRequired(bool invalidPassword, const QString
QJsonObject obj;
obj["invalidPassword"] = invalidPassword;
obj["path"] = path;
sendAll("synchronized", obj);
sendAll("walletOpenPasswordRequired", obj);
}
void WSServer::onConnectionStatusChanged(int status) {

107
src/vr/main.cpp

@ -0,0 +1,107 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include <iostream>
#include <QResource>
#include <QApplication>
#include <QCoreApplication>
#include <QQmlComponent>
#include <QtCore>
#include <QtGui>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QQuickView>
#include <QQuickItem>
#include "openvr.h"
#include "vr/openvr_init.h"
#include "vr/main.h"
#include "libwalletqt/TransactionInfo.h"
#include "libwalletqt/TransactionHistory.h"
#include "model/TransactionHistoryModel.h"
#include "model/TransactionHistoryProxyModel.h"
#include "libwalletqt/WalletManager.h"
#include "utils/keysfiles.h"
namespace wowletvr {
void check_error(int line, vr::EVRInitError error) { if (error != 0) printf("%d: error %s\n", line, VR_GetVRInitErrorAsSymbol(error)); }
WowletVR::WowletVR(AppContext *ctx, QCommandLineParser *parser, QObject *parent) :
QObject(parent), ctx(ctx), m_parser(parser) {
desktopMode = m_parser->isSet("openvr-debug");
#ifdef Q_OS_WIN
if(desktopMode)
qputenv("QMLSCENE_DEVICE", "softwarecontext");
#endif
qDebug() << "QMLSCENE_DEVICE: " << qgetenv("QMLSCENE_DEVICE");
m_engine.rootContext()->setContextProperty("homePath", QDir::homePath());
m_engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath());
m_engine.rootContext()->setContextProperty("idealThreadCount", QThread::idealThreadCount());
m_engine.rootContext()->setContextProperty("qtRuntimeVersion", qVersion());
m_engine.rootContext()->setContextProperty("ctx", ctx);
// qmlRegisterType<clipboardAdapter>("moneroComponents.Clipboard", 1, 0, "Clipboard");
qRegisterMetaType<NetworkType::Type>();
qmlRegisterType<NetworkType>("wowlet.NetworkType", 1, 0, "NetworkType");
qmlRegisterUncreatableType<WalletKeysFiles>("wowlet.WalletKeysFiles", 1, 0, "WalletKeysFiles", "lol");
qmlRegisterUncreatableType<Wallet>("wowlet.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly");
qmlRegisterType<WalletManager>("wowlet.WalletManager", 1, 0, "WalletManager");
qmlRegisterUncreatableType<TransactionHistoryProxyModel>("wowlet.TransactionHistoryProxyModel", 1, 0, "TransactionHistoryProxyModel", "TransactionHistoryProxyModel can't be instantiated directly");
qmlRegisterUncreatableType<TransactionHistoryModel>("wowlet.TransactionHistoryModel", 1, 0, "TransactionHistoryModel", "TransactionHistoryModel can't be instantiated directly");
qmlRegisterUncreatableType<TransactionInfo>("wowlet.TransactionInfo", 1, 0, "TransactionInfo", "TransactionHistory can't be instantiated directly");
qmlRegisterUncreatableType<TransactionHistory>("wowlet.TransactionHistory", 1, 0, "TransactionHistory", "TransactionHistory can't be instantiated directly");
qRegisterMetaType<PendingTransaction::Priority>();
qRegisterMetaType<TransactionInfo::Direction>();
qRegisterMetaType<TransactionHistoryModel::TransactionInfoRole>();
// @TODO: custom DPI / AA
// QCoreApplication::setAttribute( Qt::AA_UseDesktopOpenGL );
// QCoreApplication::setAttribute( Qt::AA_Use96Dpi );
auto widgetUrl = QUrl(QStringLiteral("qrc:///main"));
m_component = new QQmlComponent(&m_engine, widgetUrl);
this->errors = m_component->errors();
for (auto &e : this->errors)
qCritical() << "QML Error: " << e.toString().toStdString().c_str();
if(!desktopMode) {
openvr_init::initializeOpenVR(openvr_init::OpenVrInitializationType::Overlay);
m_controller = new wowletvr::OverlayController(desktopMode, m_engine);
}
}
void WowletVR::render() {
auto quickObj = m_component->create();
QQuickItem *quickObjItem = qobject_cast<QQuickItem*>(quickObj);
auto displayName = application_strings::applicationDisplayName;
auto appKey = application_strings::applicationKey;
if(desktopMode) {
auto m_pWindow = new QQuickWindow();
qobject_cast<QQuickItem *>(quickObj)->setParentItem(m_pWindow->contentItem());
m_pWindow->setGeometry(0, 0,
static_cast<int>(qobject_cast<QQuickItem *>(quickObj)->width()),
static_cast<int>(qobject_cast<QQuickItem *>(quickObj)->height()));
m_pWindow->show();
return;
}
m_controller->SetWidget(quickObjItem, displayName, appKey);
}
WowletVR::~WowletVR() {
int weoignwieog = 1;
};
}

38
src/vr/main.h

@ -0,0 +1,38 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef WOWLET_MAIN_H
#define WOWLET_MAIN_H
#include <QtCore>
#include <QQmlError>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "overlaycontroller.h"
#include "appcontext.h"
namespace wowletvr {
class WowletVR : public QObject {
Q_OBJECT
public:
explicit WowletVR(AppContext *ctx, QCommandLineParser *cmdargs, QObject *parent = nullptr);
~WowletVR() override;
void render();
QList<QQmlError> errors;
private:
AppContext *ctx;
QCommandLineParser *m_parser;
QQmlEngine m_engine;
QQmlComponent *m_component;
bool desktopMode = false;
wowletvr::OverlayController *m_controller;
};
}
#endif //WOWLET_MAIN_H

74
src/vr/openvr_init.cpp

@ -0,0 +1,74 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
// Copyright (c) OpenVR Advanced Settings
#include <string>
#include <openvr.h>
#include <QDebug>
#include <QMessageBox>
#include "openvr_init.h"
namespace openvr_init
{
bool initializeProperly(const OpenVrInitializationType initType) {
auto initializationType = vr::EVRApplicationType::VRApplication_Other;
if (initType == OpenVrInitializationType::Overlay) {
initializationType = vr::EVRApplicationType::VRApplication_Overlay;
} else if (initType == OpenVrInitializationType::Utility) {
initializationType = vr::EVRApplicationType::VRApplication_Utility;
}
auto initError = vr::VRInitError_None;
vr::VR_Init(&initError, initializationType);
if (initError != vr::VRInitError_None) {
if (initError == vr::VRInitError_Init_HmdNotFound || initError == vr::VRInitError_Init_HmdNotFoundPresenceFailed) {
QMessageBox::critical(nullptr, "Wowlet VR", "Could not find HMD!");
}
qCritical() << "Failed to initialize OpenVR: " << std::string(vr::VR_GetVRInitErrorAsEnglishDescription(initError)).c_str();
return false;
}
qInfo() << "OpenVR initialized successfully.";
return true;
}
bool initializeOpenVR(const OpenVrInitializationType initType)
{
bool res = initializeProperly(initType);
if(!res)
return false;
// Check whether OpenVR is too outdated
if (!vr::VR_IsInterfaceVersionValid(vr::IVRSystem_Version)) {
return reportVersionError(vr::IVRSystem_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRSettings_Version)) {
return reportVersionError(vr::IVRSettings_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVROverlay_Version)) {
return reportVersionError(vr::IVROverlay_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRApplications_Version)) {
return reportVersionError(vr::IVRApplications_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRChaperone_Version)) {
return reportVersionError(vr::IVRChaperone_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRChaperoneSetup_Version)) {
return reportVersionError(vr::IVRChaperoneSetup_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRCompositor_Version)) {
return reportVersionError(vr::IVRCompositor_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRNotifications_Version)) {
return reportVersionError(vr::IVRNotifications_Version);
} else if (!vr::VR_IsInterfaceVersionValid(vr::IVRInput_Version)) {
return reportVersionError(vr::IVRInput_Version);
}
return true;
}
bool reportVersionError(const char* const interfaceAndVersion) {
// The function call and error message was the same for all version checks.
// Specific error messages are unlikely to be necessary since both the type
// and version are in the string and will be output.
auto msg = "OpenVR version is too outdated. Please update OpenVR: " + QString(interfaceAndVersion);
QMessageBox::critical(nullptr, "Wowlet VR", msg);
return false;
}
} // namespace openvr_init

16
src/vr/openvr_init.h

@ -0,0 +1,16 @@
#pragma once
namespace openvr_init
{
enum class OpenVrInitializationType
{
Overlay,
Utility,
};
bool initializeProperly(OpenVrInitializationType initType);
bool initializeOpenVR(OpenVrInitializationType initType );
bool reportVersionError(const char* interfaceAndVersion);
} // namespace openvr_init

484
src/vr/overlaycontroller.cpp

@ -0,0 +1,484 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
// Copyright (c) OpenVR Advanced Settings
#include <QOpenGLFramebufferObjectFormat>
#include <QOpenGLPaintDevice>
#include <QPainter>
#include <QQuickView>
#include <QApplication>
#include <QQmlEngine>
#include <QQmlContext>
#include <QtWidgets/QWidget>
#include <QMouseEvent>
#include <QtWidgets/QGraphicsSceneMouseEvent>
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsEllipseItem>
#include <QOpenGLExtraFunctions>
#include <QCursor>
#include <QProcess>
#include <QMessageBox>
#include <iostream>
#include <cmath>
#include "overlaycontroller.h"
#include <openvr.h>
// application namespace
namespace wowletvr
{
OverlayController::OverlayController(bool desktopMode, QQmlEngine& qmlEngine) :
QObject(),
m_desktopMode(desktopMode)
{
// Arbitrarily chosen Max Length of Directory path, should be sufficient for
// Any set-up
const uint32_t maxLength = 16192;
uint32_t requiredLength;
char tempRuntimePath[maxLength];
bool pathIsGood = vr::VR_GetRuntimePath( tempRuntimePath, maxLength, &requiredLength );
// Throw Error If over 16k characters in path string
if ( !pathIsGood )
{
qCritical() << "Error Finding VR Runtime Path, Attempting Recovery: ";
uint32_t maxLengthRe = requiredLength;
qInfo() << "Open VR reporting Required path length of: "
<< maxLengthRe;
}
m_runtimePathUrl = QUrl::fromLocalFile( tempRuntimePath );
qInfo() << "VR Runtime Path: " << m_runtimePathUrl.toLocalFile();
QSurfaceFormat format;
// Qt's QOpenGLPaintDevice is not compatible with OpenGL versions >= 3.0
// NVIDIA does not care, but unfortunately AMD does
// Are subtle changes to the semantics of OpenGL functions actually covered
// by the compatibility profile, and this is an AMD bug?
format.setVersion( 2, 1 );
// format.setProfile( QSurfaceFormat::CompatibilityProfile );
format.setDepthBufferSize( 16 );
format.setStencilBufferSize( 8 );
format.setSamples( 16 );
m_openGLContext.setFormat( format );
if ( !m_openGLContext.create() ) {
throw std::runtime_error( "Could not create OpenGL context" );
}
// create an offscreen surface to attach the context and FBO to
m_offscreenSurface.setFormat( m_openGLContext.format() );
m_offscreenSurface.create();
m_openGLContext.makeCurrent( &m_offscreenSurface );
if (!vr::VROverlay()){
QMessageBox::critical(nullptr, "Wowlet VR Overlay", "Is OpenVR running?");
throw std::runtime_error( std::string( "No Overlay interface" ) );
}
// Set qml context
qmlEngine.rootContext()->setContextProperty("applicationVersion", "1337");
qmlEngine.rootContext()->setContextProperty("vrRuntimePath", getVRRuntimePathUrl());
// Pretty disgusting trick to allow qmlRegisterSingletonType to continue
// working with the lambdas that were already there. The callback function
// in qmlRegisterSingletonType won't work with any lambdas that capture the
// environment. The alternative to making a static pointer to this was
// rewriting all QML to not be singletons, which should probably be done
// whenever possible.
static OverlayController* const objectAddress = this;
constexpr auto qmlSingletonImportName = "ovrwow.wowletvr";
qmlRegisterSingletonType<OverlayController>(
qmlSingletonImportName,
1,
0,
"OverlayController",
[]( QQmlEngine*, QJSEngine* ) {
QObject* obj = objectAddress;
QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership );
return obj;
});
qInfo() << "OPENSSL VERSION: " << QSslSocket::sslLibraryBuildVersionString();
}
OverlayController::~OverlayController() {
Shutdown();
}
void OverlayController::exitApp() {
Shutdown();
QApplication::exit();
qInfo() << "All systems exited.";
exit( EXIT_SUCCESS );
// Does not fallthrough
}
void OverlayController::Shutdown() {
if (m_pRenderTimer)
{
disconnect( &m_renderControl,
SIGNAL( renderRequested() ),
this,
SLOT( OnRenderRequest() ) );
disconnect( &m_renderControl,
SIGNAL( sceneChanged() ),
this,
SLOT( OnRenderRequest() ) );
disconnect( m_pRenderTimer.get(),
SIGNAL( timeout() ),
this,
SLOT( renderOverlay() ) );
m_pRenderTimer->stop();
m_pRenderTimer.reset();
}
m_pFbo.reset();
}
void OverlayController::SetWidget( QQuickItem* quickItem,
const std::string& name,
const std::string& key )
{
if ( !m_desktopMode )
{
vr::VROverlayError overlayError
= vr::VROverlay()->CreateDashboardOverlay(
key.c_str(),
name.c_str(),
&m_ulOverlayHandle,
&m_ulOverlayThumbnailHandle );
if ( overlayError != vr::VROverlayError_None )
{
if ( overlayError == vr::VROverlayError_KeyInUse )
{
QMessageBox::critical( nullptr,
"Wowlet VR Overlay",
"Another instance is already running." );
}
throw std::runtime_error( std::string(
"Failed to create Overlay: "
+ std::string( vr::VROverlay()->GetOverlayErrorNameFromEnum(
overlayError ) ) ) );
}
vr::VROverlay()->SetOverlayWidthInMeters( m_ulOverlayHandle, 2.5f );
vr::VROverlay()->SetOverlayInputMethod(
m_ulOverlayHandle, vr::VROverlayInputMethod_Mouse );
vr::VROverlay()->SetOverlayFlag(
m_ulOverlayHandle,
vr::VROverlayFlags_SendVRSmoothScrollEvents,
true );
constexpr auto thumbiconFilename = "img/icons/thumbicon.png";
const auto thumbIconPath = paths::binaryDirectoryFindFile( thumbiconFilename );
if ( !thumbIconPath.empty() ) {
vr::VROverlay()->SetOverlayFromFile( m_ulOverlayThumbnailHandle, thumbIconPath.c_str() );
}
else {
qCritical() << "Could not find thumbnail icon \"" << thumbiconFilename << "\"";
}
// Too many render calls in too short time overwhelm Qt and an
// assertion gets thrown. Therefore we use an timer to delay render
// calls
m_pRenderTimer.reset(new QTimer());
m_pRenderTimer->setSingleShot( false );
m_pRenderTimer->setInterval( 5 );
connect( m_pRenderTimer.get(),
SIGNAL( timeout() ),
this,
SLOT( renderOverlay() ) );
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setAttachment(
QOpenGLFramebufferObject::CombinedDepthStencil );
fboFormat.setTextureTarget( GL_TEXTURE_2D );
m_pFbo.reset( new QOpenGLFramebufferObject(
static_cast<int>( quickItem->width() ),
static_cast<int>( quickItem->height() ),
fboFormat ) );
m_window.setRenderTarget( m_pFbo.get() );
quickItem->setParentItem( m_window.contentItem() );
m_window.setGeometry( 0,
0,
static_cast<int>( quickItem->width() ),
static_cast<int>( quickItem->height() ) );
m_renderControl.initialize( &m_openGLContext );
vr::HmdVector2_t vecWindowSize
= { static_cast<float>( quickItem->width() ),
static_cast<float>( quickItem->height() ) };
vr::VROverlay()->SetOverlayMouseScale( m_ulOverlayHandle,
&vecWindowSize );
connect( &m_renderControl,
SIGNAL( renderRequested() ),
this,
SLOT( OnRenderRequest() ) );
connect( &m_renderControl,
SIGNAL( sceneChanged() ),
this,
SLOT( OnRenderRequest() ) );
m_pRenderTimer->start();
}
}
void OverlayController::OnRenderRequest() {
if ( m_pRenderTimer && !m_pRenderTimer->isActive() )
{
m_pRenderTimer->start();
}
}
void OverlayController::renderOverlay() {
if ( !m_desktopMode )
{
// skip rendering if the overlay isn't visible
if ( !vr::VROverlay()
|| ( !vr::VROverlay()->IsOverlayVisible( m_ulOverlayHandle )
&& !vr::VROverlay()->IsOverlayVisible(
m_ulOverlayThumbnailHandle ) ) )
return;
m_renderControl.polishItems();
m_renderControl.sync();
m_renderControl.render();
GLuint unTexture = m_pFbo->texture();
if ( unTexture != 0 )
{
#if defined _WIN64 || defined _LP64
// To avoid any compiler warning because of cast to a larger
// pointer type (warning C4312 on VC)
vr::Texture_t texture = { reinterpret_cast<void*>(
static_cast<uint64_t>( unTexture ) ),
vr::TextureType_OpenGL,
vr::ColorSpace_Auto };
#else
vr::Texture_t texture = { reinterpret_cast<void*>( unTexture ),
vr::TextureType_OpenGL,
vr::ColorSpace_Auto };
#endif
vr::VROverlay()->SetOverlayTexture( m_ulOverlayHandle, &texture );
}
m_openGLContext.functions()->glFlush(); // We need to flush otherwise
// the texture may be empty.*/
if(m_customTickRateCounter % k_nonVsyncTickRate == 0) {
mainEventLoop();
m_customTickRateCounter = 0;
} else {
m_customTickRateCounter += 1;
}
}
}
bool OverlayController::pollNextEvent( vr::VROverlayHandle_t ulOverlayHandle,
vr::VREvent_t* pEvent ) {
if ( isDesktopMode() )
{
return vr::VRSystem()->PollNextEvent( pEvent, sizeof( vr::VREvent_t ) );
}
else
{
return vr::VROverlay()->PollNextOverlayEvent(
ulOverlayHandle, pEvent, sizeof( vr::VREvent_t ) );
}
}
QPoint OverlayController::getMousePositionForEvent( vr::VREvent_Mouse_t mouse ) {
float y = mouse.y;
#ifdef __linux__
float h = static_cast<float>( m_window.height() );
y = h - y;
#endif
return QPoint( static_cast<int>( mouse.x ), static_cast<int>( y ) );
}
void OverlayController::mainEventLoop() {
if ( !vr::VRSystem() )
return;
vr::VREvent_t vrEvent;
while ( pollNextEvent( m_ulOverlayHandle, &vrEvent ) ) {
switch ( vrEvent.eventType )
{
case vr::VREvent_MouseMove:
{
QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse );
if ( ptNewMouse != m_ptLastMouse )
{
QMouseEvent mouseEvent( QEvent::MouseMove,
ptNewMouse,
m_window.mapToGlobal( ptNewMouse ),
Qt::NoButton,
m_lastMouseButtons,
nullptr );
m_ptLastMouse = ptNewMouse;
QCoreApplication::sendEvent( &m_window, &mouseEvent );
OnRenderRequest();
}
}
break;
case vr::VREvent_MouseButtonDown:
{
QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse );
Qt::MouseButton button
= vrEvent.data.mouse.button == vr::VRMouseButton_Right
? Qt::RightButton
: Qt::LeftButton;
m_lastMouseButtons |= button;
QMouseEvent mouseEvent( QEvent::MouseButtonPress,
ptNewMouse,
m_window.mapToGlobal( ptNewMouse ),
button,
m_lastMouseButtons,
nullptr );
QCoreApplication::sendEvent( &m_window, &mouseEvent );
}
break;
case vr::VREvent_MouseButtonUp:
{
QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse );
Qt::MouseButton button
= vrEvent.data.mouse.button == vr::VRMouseButton_Right
? Qt::RightButton
: Qt::LeftButton;
m_lastMouseButtons &= ~button;
QMouseEvent mouseEvent( QEvent::MouseButtonRelease,
ptNewMouse,
m_window.mapToGlobal( ptNewMouse ),
button,
m_lastMouseButtons,
nullptr );
QCoreApplication::sendEvent( &m_window, &mouseEvent );
}
break;
case vr::VREvent_ScrollSmooth:
{
// Wheel speed is defined as 1/8 of a degree
QWheelEvent wheelEvent(
m_ptLastMouse,
m_window.mapToGlobal( m_ptLastMouse ),
QPoint(),
QPoint( static_cast<int>( vrEvent.data.scroll.xdelta
* ( 360.0f * 8.0f ) ),
static_cast<int>( vrEvent.data.scroll.ydelta
* ( 360.0f * 8.0f ) ) ),
0,
Qt::Vertical,
m_lastMouseButtons,
nullptr );
QCoreApplication::sendEvent( &m_window, &wheelEvent );
}
break;
case vr::VREvent_OverlayShown:
{
m_window.update();
}
break;
case vr::VREvent_Quit:
{
qInfo() << "Received quit request.";
vr::VRSystem()->AcknowledgeQuit_Exiting(); // Let us buy some
// time just in case
exitApp();
// Won't fallthrough, but also exitApp() wont, but QT won't
// acknowledge
exit( EXIT_SUCCESS );
}
case vr::VREvent_DashboardActivated:
{
qDebug() << "Dashboard activated";
m_dashboardVisible = true;
}
break;
case vr::VREvent_DashboardDeactivated:
{
qDebug() << "Dashboard deactivated";
m_dashboardVisible = false;
}
break;
case vr::VREvent_KeyboardDone:
{
char keyboardBuffer[1024];
vr::VROverlay()->GetKeyboardText( keyboardBuffer, 1024 );
emit keyBoardInputSignal( QString( keyboardBuffer ),
static_cast<unsigned long>(
vrEvent.data.keyboard.uUserValue ) );
}
break;
}
}
if ( m_ulOverlayThumbnailHandle != vr::k_ulOverlayHandleInvalid ) {
while ( vr::VROverlay()->PollNextOverlayEvent(
m_ulOverlayThumbnailHandle, &vrEvent, sizeof( vrEvent ) ) )
{
switch ( vrEvent.eventType )
{
case vr::VREvent_OverlayShown:
{
m_window.update();
}
break;
}
}
}
}
void OverlayController::showKeyboard(QString existingText, unsigned long userValue)
{
vr::VROverlay()->ShowKeyboardForOverlay(
m_ulOverlayHandle,
vr::k_EGamepadTextInputModeNormal,
vr::k_EGamepadTextInputLineModeSingleLine,
0,
"Advanced Settings Overlay",
1024,
existingText.toStdString().c_str(),
userValue);
setKeyboardPos();
}
void OverlayController::setKeyboardPos()
{
vr::HmdVector2_t emptyvec;
emptyvec.v[0] = 0;
emptyvec.v[1] = 0;
vr::HmdRect2_t empty;
empty.vTopLeft = emptyvec;
empty.vBottomRight = emptyvec;
vr::VROverlay()->SetKeyboardPositionForOverlay( m_ulOverlayHandle, empty );
}
QUrl OverlayController::getVRRuntimePathUrl() {
return m_runtimePathUrl;
}
bool OverlayController::soundDisabled() {
return true;
}
const vr::VROverlayHandle_t& OverlayController::overlayHandle() {
return m_ulOverlayHandle;
}
const vr::VROverlayHandle_t& OverlayController::overlayThumbnailHandle() {
return m_ulOverlayThumbnailHandle;
}
} // namespace wowletvr

134
src/vr/overlaycontroller.h

@ -0,0 +1,134 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
// Copyright (c) OpenVR Advanced Settings
#pragma once
#include <openvr.h>
#include <QtCore/QtCore>
// because of incompatibilities with QtOpenGL and GLEW we need to cherry pick includes
#include <QVector2D>
#include <QMatrix4x4>
#include <QVector>
#include <QVector2D>
#include <QVector3D>
#include <QObject>
#include <QQmlEngine>
#include <QtGui/QOpenGLContext>
#include <QtWidgets/QGraphicsScene>
#include <QOffscreenSurface>
#include <QOpenGLFramebufferObject>
#include <QQuickWindow>
#include <QQuickItem>
#include <QQuickRenderControl>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <memory>
#include "openvr_init.h"
#include "vr/utils/paths.h"
#include "appcontext.h"
namespace application_strings
{
constexpr auto applicationOrganizationName = "Wownero";
constexpr auto applicationName = "Wowlet VR";
constexpr const char* applicationKey = "steam.overlay.1001337";
constexpr const char* applicationDisplayName = "Wowlet VR";
constexpr const char* applicationVersionString = "1337";
} // namespace application_strings
constexpr int k_nonVsyncTickRate = 20;
// application namespace
namespace wowletvr
{
class OverlayController : public QObject
{
Q_OBJECT
public:
OverlayController(bool desktopMode, QQmlEngine& qmlEngine);
virtual ~OverlayController();
void Shutdown();
Q_INVOKABLE void exitApp();
bool isDashboardVisible()
{
return m_dashboardVisible;
}
void SetWidget( QQuickItem* quickItem,
const std::string& name,
const std::string& key = "" );
bool isDesktopMode()
{
return m_desktopMode;
}
Q_INVOKABLE QUrl getVRRuntimePathUrl();
Q_INVOKABLE bool soundDisabled();
const vr::VROverlayHandle_t& overlayHandle();
const vr::VROverlayHandle_t& overlayThumbnailHandle();
bool pollNextEvent(vr::VROverlayHandle_t ulOverlayHandle, vr::VREvent_t* pEvent );
void mainEventLoop();
private:
vr::VROverlayHandle_t m_ulOverlayHandle = vr::k_ulOverlayHandleInvalid;
vr::VROverlayHandle_t m_ulOverlayThumbnailHandle
= vr::k_ulOverlayHandleInvalid;
QQuickRenderControl m_renderControl;
QQuickWindow m_window{ &m_renderControl };
std::unique_ptr<QOpenGLFramebufferObject> m_pFbo;
QOpenGLContext m_openGLContext;
QOffscreenSurface m_offscreenSurface;
std::unique_ptr<QTimer> m_pRenderTimer;
bool m_dashboardVisible = false;
QPoint m_ptLastMouse;
Qt::MouseButtons m_lastMouseButtons = nullptr;
bool m_desktopMode;
QUrl m_runtimePathUrl;
uint64_t m_customTickRateCounter = 0;
uint64_t m_currentFrame = 0;
uint64_t m_lastFrame = 0;
QNetworkAccessManager* netManager = new QNetworkAccessManager( this );
QJsonDocument m_remoteVersionJsonDocument = QJsonDocument();
QJsonObject m_remoteVersionJsonObject;
private:
QPoint getMousePositionForEvent( vr::VREvent_Mouse_t mouse );
bool m_exclusiveState = false;
bool m_keyPressOneState = false;
bool m_keyPressTwoState = false;
AppContext *m_ctx;
public slots:
void renderOverlay();
void OnRenderRequest();
void showKeyboard( QString existingText, unsigned long userValue = 0 );
void setKeyboardPos();
signals:
void keyBoardInputSignal( QString input, unsigned long userValue = 0 );
};
} // namespace wowletvr

58
src/vr/utils/paths.cpp

@ -0,0 +1,58 @@
#include "paths.h"
#include <QStandardPaths>
#include <QCoreApplication>
#include <QString>
#include <QFileInfo>
#include <QDir>
namespace paths
{
string binaryDirectory()
{
const auto path = QCoreApplication::applicationDirPath();
if ( path == "" ) {
qCritical() << "Could not find binary directory.";
return "";
}
return path.toStdString() + "/../"; // @ TODO: removeme
}
string binaryDirectoryFindFile( const string& fileName ) {
const auto path = binaryDirectory();
if (path.empty()) {
return "";
}
const auto filePath = QDir( QString::fromStdString( path ) + '/'
+ QString::fromStdString( fileName ) );
QFileInfo file( filePath.path() );
if (!file.exists())
{
qCritical() << "Could not find file '" << fileName.c_str()
<< "' in binary directory.";
return "";
}
return QDir::toNativeSeparators( file.filePath() ).toStdString();
}
string settingsDirectory() {
const auto path = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation );
if (path == "") {
qCritical() << "Could not find settings directory.";
return "";
}
return path.toStdString();
}
string verifyIconFilePath( const string& filename ) {
const string notifIconPath = paths::binaryDirectoryFindFile( filename );
if (notifIconPath.empty()) {
qCritical() << "Could not find icon " << filename.c_str() << "\"";
}
return notifIconPath;
}
} // namespace paths

16
src/vr/utils/paths.h

@ -0,0 +1,16 @@
#pragma once
#include <QDebug>
#include <string>
#include <optional>
#include <experimental/optional>
namespace paths
{
using std::string;
string binaryDirectory();
string binaryDirectoryFindFile( const string& fileName );
string settingsDirectory();
string verifyIconFilePath( const string& filename );
} // namespace paths
Loading…
Cancel
Save