From 96034902d1ff75baa6f5ab5053efb22dfc3ad580 Mon Sep 17 00:00:00 2001 From: dsc Date: Fri, 2 Apr 2021 14:22:37 +0200 Subject: [PATCH] Initial code for supporting an alternative QtQuick based UI for OpenVR --- src/CMakeLists.txt | 6 +- src/appcontext.cpp | 19 + src/appcontext.h | 33 +- src/libwalletqt/WalletManager.h | 7 +- src/main.cpp | 7 +- src/mainwindow.cpp | 2 - src/model/TransactionHistoryModel.cpp | 46 ++- src/model/TransactionHistoryModel.h | 8 +- src/utils/keysfiles.cpp | 5 + src/utils/keysfiles.h | 11 + src/utils/wsserver.cpp | 9 +- src/vr/main.cpp | 107 ++++++ src/vr/main.h | 38 ++ src/vr/openvr_init.cpp | 74 ++++ src/vr/openvr_init.h | 16 + src/vr/overlaycontroller.cpp | 484 ++++++++++++++++++++++++++ src/vr/overlaycontroller.h | 134 +++++++ src/vr/utils/paths.cpp | 58 +++ src/vr/utils/paths.h | 16 + 19 files changed, 1034 insertions(+), 46 deletions(-) create mode 100644 src/vr/main.cpp create mode 100644 src/vr/main.h create mode 100755 src/vr/openvr_init.cpp create mode 100755 src/vr/openvr_init.h create mode 100755 src/vr/overlaycontroller.cpp create mode 100755 src/vr/overlaycontroller.h create mode 100755 src/vr/utils/paths.cpp create mode 100755 src/vr/utils/paths.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69cb3d9..39f148a 100644 --- a/src/CMakeLists.txt +++ b/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) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 1b0640c..387b2a9 100644 --- a/src/appcontext.cpp +++ b/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) { diff --git a/src/appcontext.h b/src/appcontext.h index 4748878..efc3021 100644 --- a/src/appcontext.h +++ b/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 txCache; static TxFiatHistory *txFiatHistory; - QList 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 &addresses, const QVector &amounts, const QString &description); void onCancelTransaction(PendingTransaction *tx, const QVector &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 &address, double amount); void createTransactionSuccess(PendingTransaction *tx, const QVector &address); + void wsConnected(); + void wsDisconnected(); void redditUpdated(QList> &posts); void nodesUpdated(QList> &nodes); void ccsUpdated(QList> &entries); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 35df670..778bee1 100644 --- a/src/libwalletqt/WalletManager.h +++ b/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; diff --git a/src/main.cpp b/src/main.cpp index 87e3fef..501149b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,12 +114,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { qRegisterMetaType>(); +#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; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 46d027e..389e66d 100644 --- a/src/mainwindow.cpp +++ b/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) { diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index 0c96526..8158d1f 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/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(); diff --git a/src/model/TransactionHistoryModel.h b/src/model/TransactionHistoryModel.h index 3225447..bdc0f3d 100644 --- a/src/model/TransactionHistoryModel.h +++ b/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); diff --git a/src/utils/keysfiles.cpp b/src/utils/keysfiles.cpp index d61f417..c61defa 100644 --- a/src/utils/keysfiles.cpp +++ b/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(*this); +} + QJsonObject WalletKeysFiles::toJsonObject() const { auto item = QJsonObject(); item["fileName"] = m_fileName; diff --git a/src/utils/keysfiles.h b/src/utils/keysfiles.h index 45112b5..5f665b9 100644 --- a/src/utils/keysfiles.h +++ b/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 { diff --git a/src/utils/wsserver.cpp b/src/utils/wsserver.cpp index 32b6d70..077432f 100644 --- a/src/utils/wsserver.cpp +++ b/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().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) { diff --git a/src/vr/main.cpp b/src/vr/main.cpp new file mode 100644 index 0000000..cd8cdfe --- /dev/null +++ b/src/vr/main.cpp @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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("moneroComponents.Clipboard", 1, 0, "Clipboard"); + qRegisterMetaType(); + qmlRegisterType("wowlet.NetworkType", 1, 0, "NetworkType"); + + qmlRegisterUncreatableType("wowlet.WalletKeysFiles", 1, 0, "WalletKeysFiles", "lol"); + qmlRegisterUncreatableType("wowlet.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); + qmlRegisterType("wowlet.WalletManager", 1, 0, "WalletManager"); + + qmlRegisterUncreatableType("wowlet.TransactionHistoryProxyModel", 1, 0, "TransactionHistoryProxyModel", "TransactionHistoryProxyModel can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionHistoryModel", 1, 0, "TransactionHistoryModel", "TransactionHistoryModel can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionInfo", 1, 0, "TransactionInfo", "TransactionHistory can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionHistory", 1, 0, "TransactionHistory", "TransactionHistory can't be instantiated directly"); + + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + // @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(quickObj); + + auto displayName = application_strings::applicationDisplayName; + auto appKey = application_strings::applicationKey; + + if(desktopMode) { + auto m_pWindow = new QQuickWindow(); + qobject_cast(quickObj)->setParentItem(m_pWindow->contentItem()); + m_pWindow->setGeometry(0, 0, + static_cast(qobject_cast(quickObj)->width()), + static_cast(qobject_cast(quickObj)->height())); + m_pWindow->show(); + return; + } + + m_controller->SetWidget(quickObjItem, displayName, appKey); + } + + WowletVR::~WowletVR() { + int weoignwieog = 1; + }; + +} \ No newline at end of file diff --git a/src/vr/main.h b/src/vr/main.h new file mode 100644 index 0000000..4c94492 --- /dev/null +++ b/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 +#include +#include +#include + +#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 errors; + + private: + AppContext *ctx; + QCommandLineParser *m_parser; + QQmlEngine m_engine; + QQmlComponent *m_component; + bool desktopMode = false; + wowletvr::OverlayController *m_controller; + }; + +} + +#endif //WOWLET_MAIN_H diff --git a/src/vr/openvr_init.cpp b/src/vr/openvr_init.cpp new file mode 100755 index 0000000..2596e04 --- /dev/null +++ b/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 +#include +#include +#include +#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 diff --git a/src/vr/openvr_init.h b/src/vr/openvr_init.h new file mode 100755 index 0000000..3473be1 --- /dev/null +++ b/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 diff --git a/src/vr/overlaycontroller.cpp b/src/vr/overlaycontroller.cpp new file mode 100755 index 0000000..d09ff5b --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "overlaycontroller.h" +#include + +// 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( + 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( quickItem->width() ), + static_cast( quickItem->height() ), + fboFormat ) ); + + m_window.setRenderTarget( m_pFbo.get() ); + quickItem->setParentItem( m_window.contentItem() ); + m_window.setGeometry( 0, + 0, + static_cast( quickItem->width() ), + static_cast( quickItem->height() ) ); + m_renderControl.initialize( &m_openGLContext ); + + vr::HmdVector2_t vecWindowSize + = { static_cast( quickItem->width() ), + static_cast( 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( + static_cast( unTexture ) ), + vr::TextureType_OpenGL, + vr::ColorSpace_Auto }; +#else + vr::Texture_t texture = { reinterpret_cast( 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( m_window.height() ); + y = h - y; +#endif + return QPoint( static_cast( mouse.x ), static_cast( 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( vrEvent.data.scroll.xdelta + * ( 360.0f * 8.0f ) ), + static_cast( 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( + 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 diff --git a/src/vr/overlaycontroller.h b/src/vr/overlaycontroller.h new file mode 100755 index 0000000..4cb7cde --- /dev/null +++ b/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 +#include +// because of incompatibilities with QtOpenGL and GLEW we need to cherry pick includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 m_pFbo; + QOpenGLContext m_openGLContext; + QOffscreenSurface m_offscreenSurface; + + std::unique_ptr 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 diff --git a/src/vr/utils/paths.cpp b/src/vr/utils/paths.cpp new file mode 100755 index 0000000..45d3553 --- /dev/null +++ b/src/vr/utils/paths.cpp @@ -0,0 +1,58 @@ +#include "paths.h" +#include +#include +#include +#include +#include + +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 diff --git a/src/vr/utils/paths.h b/src/vr/utils/paths.h new file mode 100755 index 0000000..aeb274f --- /dev/null +++ b/src/vr/utils/paths.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include +#include +#include + +namespace paths +{ +using std::string; + +string binaryDirectory(); +string binaryDirectoryFindFile( const string& fileName ); +string settingsDirectory(); +string verifyIconFilePath( const string& filename ); + +} // namespace paths