diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d4b781..ac1470d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ project(CLIFp # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("v0.3.7.1") +fetch_ob_cmake("19d33b5bb1752b50767f78ca3e17796868354ac3") # Initialize project according to standard rules include(OB/Project) @@ -101,9 +101,18 @@ ob_fetch_quazip( include(OB/FetchMagicEnum) ob_fetch_magicenum("v0.9.6") +# Bring in frontend framework module +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/frontend_framework/cmake") + # Process Targets -set(APP_TARGET_NAME ${PROJECT_NAMESPACE_LC}_${PROJECT_NAMESPACE_LC}) -set(APP_ALIAS_NAME ${PROJECT_NAMESPACE}) +set(BACKEND_TARGET_NAME ${PROJECT_NAMESPACE_LC}_backend) +set(BACKEND_ALIAS_NAME Backend) +string(TOLOWER "${BACKEND_ALIAS_NAME}" BACKEND_ALIAS_NAME_LC) +set(FRONTEND_FRAMEWORK_TARGET_NAME ${PROJECT_NAMESPACE_LC}_frontend_framework) +set(FRONTEND_FRAMEWORK_ALIAS_NAME FrontendFramework) +add_subdirectory(lib) +set(APP_GUI_TARGET_NAME ${PROJECT_NAMESPACE_LC}_${PROJECT_NAMESPACE_LC}) +set(APP_GUI_ALIAS_NAME ${PROJECT_NAMESPACE}) add_subdirectory(app) #--------------------Package Config----------------------- @@ -112,7 +121,7 @@ ob_standard_project_package_config( COMPATIBILITY "SameMinorVersion" CONFIG STANDARD TARGET_CONFIGS - TARGET "${PROJECT_NAMESPACE}::${APP_ALIAS_NAME}" COMPONENT "${APP_ALIAS_NAME}" DEFAULT + TARGET "${PROJECT_NAMESPACE}::${APP_GUI_ALIAS_NAME}" COMPONENT "${APP_GUI_ALIAS_NAME}" DEFAULT ) #================= Install ========================== diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 902de0c..fdf5e92 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,176 +1,2 @@ -#================= Common Build ========================= - -# Pre-configure target -set(CLIFP_SOURCE - kernel/buildinfo.h - kernel/core.h - kernel/core.cpp - kernel/directive.h - kernel/director.h - kernel/director.cpp - kernel/directorate.h - kernel/directorate.cpp - kernel/driver.h - kernel/driver.cpp - kernel/errorstatus.h - kernel/errorstatus.cpp - command/command.h - command/command.cpp - command/c-download.h - command/c-download.cpp - command/c-link.h - command/c-link.cpp - command/c-play.h - command/c-play.cpp - command/c-prepare.h - command/c-prepare.cpp - command/c-run.h - command/c-run.cpp - command/c-share.h - command/c-share.cpp - command/c-show.cpp - command/c-show.h - command/c-update.h - command/c-update.cpp - command/title-command.h - command/title-command.cpp - task/task.h - task/task.cpp - task/t-download.h - task/t-download.cpp - task/t-exec.h - task/t-exec.cpp - task/t-extra.h - task/t-extra.cpp - task/t-extract.h - task/t-extract.cpp - task/t-generic.h - task/t-generic.cpp - task/t-message.h - task/t-message.cpp - task/t-mount.h - task/t-mount.cpp - task/t-sleep.h - task/t-sleep.cpp - tools/blockingprocessmanager.h - tools/blockingprocessmanager.cpp - tools/deferredprocessmanager.h - tools/deferredprocessmanager.cpp - tools/mounter_game_server.h - tools/mounter_game_server.cpp - tools/mounter_qmp.h - tools/mounter_qmp.cpp - tools/mounter_router.h - tools/mounter_router.cpp - frontend/statusrelay.h - frontend/statusrelay.cpp - controller.h - controller.cpp - utility.h - utility.cpp - main.cpp -) - -set(CLIFP_LINKS - PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets - Qt6::Sql - Qt6::Network - Qx::Core - Qx::Io - Qx::Network - Qx::Widgets - Fp::Fp - QuaZip::QuaZip - magic_enum::magic_enum - QI-QMP::Qmpi -) - -if(CMAKE_SYSTEM_NAME STREQUAL Windows) - list(APPEND CLIFP_SOURCE - command/c-link_win.cpp - task/t-exec_win.cpp - task/t-bideprocess.h - task/t-bideprocess.cpp - ) - - list(APPEND CLIFP_LINKS - PRIVATE - Qx::Windows - ) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL Linux) - list(APPEND CLIFP_SOURCE - command/c-link_linux.cpp - task/t-awaitdocker.h - task/t-awaitdocker.cpp - task/t-exec_linux.cpp - ) - list(APPEND CLIFP_LINKS - PRIVATE - Qx::Linux - ) -endif() - -# Add via ob standard executable -include(OB/Executable) -ob_add_standard_executable(${APP_TARGET_NAME} - NAMESPACE "${PROJECT_NAMESPACE}" - ALIAS "${APP_ALIAS_NAME}" - SOURCE ${CLIFP_SOURCE} - RESOURCE "resources.qrc" - LINKS ${CLIFP_LINKS} - CONFIG STANDARD - WIN32 -) - -## Forward select project variables to C++ code -include(OB/CppVars) -ob_add_cpp_vars(${APP_TARGET_NAME} - NAME "project_vars" - PREFIX "PROJECT_" - VARS - VERSION_STR "\"${PROJECT_VERSION_VERBOSE}\"" - SHORT_NAME "\"${APP_ALIAS_NAME}\"" - TARGET_FP_VER_PFX_STR "\"${TARGET_FP_VERSION_PREFIX}\"" - APP_NAME "\"${PROJECT_FORMAL_NAME}\"" -) - -## Add build info -if(BUILD_SHARED_LIBS) - set(link_str "Shared") -else() - set(link_str "Static") -endif() - -ob_add_cpp_vars(${APP_TARGET_NAME} - NAME "_buildinfo" - PREFIX "BUILDINFO_" - VARS - SYSTEM "\"${CMAKE_SYSTEM_NAME}\"" - LINKAGE "\"${link_str}\"" - COMPILER "u\"${CMAKE_CXX_COMPILER_ID}\"_s" - COMPILER_VER_STR "u\"${CMAKE_CXX_COMPILER_VERSION}\"_s" -) - -## Add exe details on Windows -if(CMAKE_SYSTEM_NAME STREQUAL Windows) - # Set target exe details - include(OB/WinExecutableDetails) - ob_set_win_executable_details(${APP_TARGET_NAME} - ICON "${CMAKE_CURRENT_SOURCE_DIR}/res/app/CLIFp.ico" - FILE_VER ${PROJECT_VERSION} - PRODUCT_VER ${TARGET_FP_VERSION_PREFIX} - COMPANY_NAME "oblivioncth" - FILE_DESC "CLI for Flashpoint Archive" - INTERNAL_NAME "CLIFp" - COPYRIGHT "Open Source @ 2022 oblivioncth" - TRADEMARKS_ONE "All Rights Reserved" - TRADEMARKS_TWO "GNU AGPL V3" - ORIG_FILENAME "CLIFp.exe" - PRODUCT_NAME "${PROJECT_FORMAL_NAME}" - ) -endif() +add_subdirectory(gui) +add_subdirectory(console) diff --git a/app/cmake/Frontend.cmake b/app/cmake/Frontend.cmake new file mode 100644 index 0000000..e69de29 diff --git a/app/res/app/128x128/CLIFp.png b/app/cmake/res/app/128x128/CLIFp.png similarity index 100% rename from app/res/app/128x128/CLIFp.png rename to app/cmake/res/app/128x128/CLIFp.png diff --git a/app/res/app/16x16/CLIFp.png b/app/cmake/res/app/16x16/CLIFp.png similarity index 100% rename from app/res/app/16x16/CLIFp.png rename to app/cmake/res/app/16x16/CLIFp.png diff --git a/app/res/app/256x256/CLIFp.png b/app/cmake/res/app/256x256/CLIFp.png similarity index 100% rename from app/res/app/256x256/CLIFp.png rename to app/cmake/res/app/256x256/CLIFp.png diff --git a/app/res/app/32x32/CLIFp.png b/app/cmake/res/app/32x32/CLIFp.png similarity index 100% rename from app/res/app/32x32/CLIFp.png rename to app/cmake/res/app/32x32/CLIFp.png diff --git a/app/res/app/48x48/CLIFp.png b/app/cmake/res/app/48x48/CLIFp.png similarity index 100% rename from app/res/app/48x48/CLIFp.png rename to app/cmake/res/app/48x48/CLIFp.png diff --git a/app/res/app/64x64/CLIFp.png b/app/cmake/res/app/64x64/CLIFp.png similarity index 100% rename from app/res/app/64x64/CLIFp.png rename to app/cmake/res/app/64x64/CLIFp.png diff --git a/app/cmake/res/app/CLIFp.ico b/app/cmake/res/app/CLIFp.ico new file mode 100644 index 0000000..43a609b Binary files /dev/null and b/app/cmake/res/app/CLIFp.ico differ diff --git a/app/res/resources.qrc b/app/cmake/res/resources.qrc similarity index 100% rename from app/res/resources.qrc rename to app/cmake/res/resources.qrc diff --git a/app/res/tray/Exit.png b/app/cmake/res/tray/Exit.png similarity index 100% rename from app/res/tray/Exit.png rename to app/cmake/res/tray/Exit.png diff --git a/app/console/CMakeLists.txt b/app/console/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/app/res/app/CLIFp.ico b/app/console/res/app/CLIFp.ico similarity index 100% rename from app/res/app/CLIFp.ico rename to app/console/res/app/CLIFp.ico diff --git a/app/gui/CMakeLists.txt b/app/gui/CMakeLists.txt new file mode 100644 index 0000000..d420beb --- /dev/null +++ b/app/gui/CMakeLists.txt @@ -0,0 +1,27 @@ +#================= Common Build ========================= + +# Add via ob standard executable +include(OB/Executable) +ob_add_standard_executable(${APP_GUI_TARGET_NAME} + NAMESPACE "${PROJECT_NAMESPACE}" + ALIAS "${APP_GUI_ALIAS_NAME}" + SOURCE + frontend/gui.h + frontend/gui.cpp + main.cpp + RESOURCE "resources.qrc" + LINKS + PRIVATE + CLIFp::FrontendFramework + ${Qt}::Widgets + Qx::Widgets + magic_enum::magic_enum + CONFIG STANDARD + WIN32 +) + +# Add exe details on Windows +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + include(FrontendFramework) + set_clip_exe_details(${APP_GUI_TARGET_NAME} ${APP_GUI_ALIAS_NAME}) +endif() diff --git a/app/gui/res/resources.qrc b/app/gui/res/resources.qrc new file mode 100644 index 0000000..199cad7 --- /dev/null +++ b/app/gui/res/resources.qrc @@ -0,0 +1,5 @@ + + + tray/Exit.png + + diff --git a/app/gui/res/tray/Exit.png b/app/gui/res/tray/Exit.png new file mode 100644 index 0000000..490b084 Binary files /dev/null and b/app/gui/res/tray/Exit.png differ diff --git a/app/gui/src/frontend/gui.cpp b/app/gui/src/frontend/gui.cpp new file mode 100644 index 0000000..c641f4a --- /dev/null +++ b/app/gui/src/frontend/gui.cpp @@ -0,0 +1,223 @@ +// Unit Include +#include "gui.h" + +// Qt Includes +#include +#include +#include +#include +#include + +// Qx Includes +#include + +// Magic enum +#include "magic_enum_utility.hpp" + +//=============================================================================================================== +// FrontendGui +//=============================================================================================================== + +//-Constructor------------------------------------------------------------------------------------------------------- +//Public: +FrontendGui::FrontendGui(QApplication* app) : + FrontendFramework(app), + mSystemClipboard(QGuiApplication::clipboard()) +{ + app->setQuitOnLastWindowClosed(false); +#ifdef __linux__ + // Set application icon + app->setWindowIcon(appIconFromResources()); +#endif + setupTrayIcon(); + setupProgressDialog(); +} + +//-Class Functions-------------------------------------------------------------------------------------------------------- +//Private: +QMessageBox::StandardButtons FrontendGui::choicesToButtons(DBlockingError::Choices cs) +{ + QMessageBox::StandardButtons bs; + + magic_enum::enum_for_each([&] (auto val) { + constexpr DBlockingError::Choice c = val; + if(cs.testFlag(c)) + bs.setFlag(smChoiceButtonMap.toRight(c)); + }); + + return bs; +} + +template + requires Qx::any_of +QMessageBox* FrontendGui::prepareMessageBox(const MessageT& dMsg) +{ + QMessageBox* msg = new QMessageBox(); + msg->setIcon(QMessageBox::Information); + msg->setWindowTitle(QCoreApplication::applicationName()); // This should be the default, but hey being explicit never hurt + msg->setText(dMsg.text); + + if(dMsg.selectable) + msg->setTextInteractionFlags(Qt::TextSelectableByMouse); + + return msg; +} + +bool FrontendGui::windowsAreOpen() +{ + // Based on Qt's own check here: https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qwindow.cpp?h=5.15.2#n2710 + // and here: https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qguiapplication.cpp?h=5.15.2#n3629 + QWindowList topWindows = QApplication::topLevelWindows(); + for(const QWindow* window : qAsConst(topWindows)) + { + if (window->isVisible() && !window->transientParent() && window->type() != Qt::ToolTip) + return true; + } + return false; +} + +QIcon& FrontendGui::trayExitIconFromResources() { static QIcon ico(u":/frontend/tray/Exit.png"_s); return ico; } + +//-Instance Functions------------------------------------------------------------------------------------------------------ +//Private: +void FrontendGui::handleMessage(const DMessage& d) +{ + auto mb = prepareMessageBox(d); + mb->setAttribute(Qt::WA_DeleteOnClose); + mb->show(); +} + +void FrontendGui::handleError(const DError& d) { Qx::postError(d.error); } + +void FrontendGui::handleProcedureStart(const DProcedureStart& d) +{ + // Set label + mProgressDialog.setLabelText(d.label); + + // Show right away + mProgressDialog.setValue(0); +} + +void FrontendGui::handleProcedureStop(const DProcedureStop& d) +{ + Q_UNUSED(d); + /* Always reset the dialog regardless of whether it is visible or not as it may not be currently visible, + * but queued to be visible on the next event loop iteration and therefore still needs to be hidden + * immediately after. + */ + mProgressDialog.reset(); +} + +void FrontendGui::handleProcedureProgress(const DProcedureProgress& d) { mProgressDialog.setValue(d.current); } +void FrontendGui::handleProcedureScale(const DProcedureScale& d) { mProgressDialog.setMaximum(d.max); } +void FrontendGui::handleClipboardUpdate(const DClipboardUpdate& d) { mSystemClipboard->setText(d.text); } +void FrontendGui::handleStatusUpdate(const DStatusUpdate& d) { mStatusHeading = d.heading; mStatusMessage = d.message; } + +// Sync directive handlers +void FrontendGui::handleBlockingMessage(const DBlockingMessage& d) +{ + auto mb = prepareMessageBox(d); + mb->exec(); + delete mb; +} + +void FrontendGui::handleBlockingError(const DBlockingError& d) +{ + Q_ASSERT(d.response); + auto btns = choicesToButtons(d.choices); + auto def = smChoiceButtonMap.toRight(d.defaultChoice); + int rawRes = Qx::postBlockingError(d.error, btns, def); + *d.response = smChoiceButtonMap.toLeft(static_cast(rawRes)); +} + +void FrontendGui::handleSaveFilename(const DSaveFilename& d) +{ + Q_ASSERT(d.response); + *d.response = QFileDialog::getSaveFileName(nullptr, d.caption, d.dir, d.filter, d.selectedFilter); +} + +void FrontendGui::handleExistingDir(const DExistingDir& d) +{ + Q_ASSERT(d.response); + *d.response = QFileDialog::getExistingDirectory(nullptr, d.caption, d.startingDir); +} + +void FrontendGui::handleItemSelection(const DItemSelection& d) +{ + Q_ASSERT(d.response); + *d.response = QInputDialog::getItem(nullptr, d.caption, d.label, d.items, 0, false); +} + +void FrontendGui::handleYesOrNo(const DYesOrNo& d) +{ + Q_ASSERT(d.response); + *d.response = QMessageBox::question(nullptr, QString(), d.question) == QMessageBox::Yes; +} + +bool FrontendGui::aboutToExit() +{ + // Quit once no windows remain + if(windowsAreOpen()) + { + connect(qApp, &QGuiApplication::lastWindowClosed, this, &FrontendGui::exit); + return false; + } + else + return true; +} + +void FrontendGui::setupTrayIcon() +{ + // Set Icon + mTrayIcon.setIcon(appIconFromResources()); + + // Set ToolTip Action + mTrayIcon.setToolTip(SYS_TRAY_STATUS); + connect(&mTrayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason){ + if(reason != QSystemTrayIcon::Context) + mTrayIcon.showMessage(mStatusHeading, mStatusMessage); + }); + + // Set Context Menu + QAction* quit = new QAction(&mTrayIconContextMenu); + quit->setIcon(trayExitIconFromResources()); + quit->setText(u"Quit"_s); + connect(quit, &QAction::triggered, this, [this]{ + // Notify driver to quit if it still exists + shutdownDriver(); + + // Close all top-level windows + qApp->closeAllWindows(); + }); + mTrayIconContextMenu.addAction(quit); + mTrayIcon.setContextMenu(&mTrayIconContextMenu); + + // Display Icon + mTrayIcon.show(); +} + +void FrontendGui::setupProgressDialog() +{ + // Initialize dialog + mProgressDialog.setCancelButtonText(u"Cancel"_s); + mProgressDialog.setWindowModality(Qt::NonModal); + mProgressDialog.setMinimumDuration(0); + mProgressDialog.setAutoClose(true); + mProgressDialog.setAutoReset(false); + mProgressDialog.reset(); // Stops the auto-show timer that is started by QProgressDialog's ctor + connect(&mProgressDialog, &QProgressDialog::canceled, this, [this]{ + /* A bit of a bodge. Pressing the Cancel button on a progress dialog + * doesn't count as closing it (it doesn't fire a close event) by the strict definition of + * QWidget, so here when the progress bar is closed we manually check to see if it was + * the last window if the application is ready to exit. + * + * Normally the progress bar should never still be open by that point, but this is here as + * a fail-safe as otherwise the application would deadlock when the progress bar is closed + * via the Cancel button. + */ + if(readyToExit() && !windowsAreOpen()) + exit(); + else + cancelDriverTask(); + }); +} diff --git a/app/gui/src/frontend/gui.h b/app/gui/src/frontend/gui.h new file mode 100644 index 0000000..dffca64 --- /dev/null +++ b/app/gui/src/frontend/gui.h @@ -0,0 +1,83 @@ +#ifndef GUI_H +#define GUI_H + +// Qt Includes +#include +#include +#include +#include + +// Qx Includes +#include +#include + +// Project Includes +#include "frontend/framework.h" + +class FrontendGui final : public FrontendFramework +{ +//-Class Variables-------------------------------------------------------------------------------------------------------- +private: + static inline const Qx::Bimap smChoiceButtonMap{ + {DBlockingError::Choice::Ok, QMessageBox::Ok}, + {DBlockingError::Choice::Yes, QMessageBox::Yes}, + {DBlockingError::Choice::No, QMessageBox::No}, + }; + + static inline const QString SYS_TRAY_STATUS = u"CLIFp is running"_s; + +//-Instance Variables---------------------------------------------------------------------------------------------- +private: + QProgressDialog mProgressDialog; + QString mStatusHeading; + QString mStatusMessage; + + QSystemTrayIcon mTrayIcon; + QMenu mTrayIconContextMenu; + QClipboard* mSystemClipboard; + +//-Constructor------------------------------------------------------------------------------------------------------- +public: + explicit FrontendGui(QApplication* app); + +//-Class Functions-------------------------------------------------------------------------------------------------------- +//Private: +static QMessageBox::StandardButtons choicesToButtons(DBlockingError::Choices cs); + +template + requires Qx::any_of +static QMessageBox* prepareMessageBox(const MessageT& dMsg); + +static bool windowsAreOpen(); + +static QIcon& trayExitIconFromResources(); + +//-Instance Functions------------------------------------------------------------------------------------------------------ +private: + // Async directive handlers + void handleMessage(const DMessage& d) override; + void handleError(const DError& d) override; + void handleProcedureStart(const DProcedureStart& d) override; + void handleProcedureStop(const DProcedureStop& d) override; + void handleProcedureProgress(const DProcedureProgress& d) override; + void handleProcedureScale(const DProcedureScale& d) override; + void handleClipboardUpdate(const DClipboardUpdate& d) override; + void handleStatusUpdate(const DStatusUpdate& d) override; + + // Sync directive handlers + void handleBlockingMessage(const DBlockingMessage& d) override; + void handleBlockingError(const DBlockingError& d) override; + void handleSaveFilename(const DSaveFilename& d) override; + void handleExistingDir(const DExistingDir& d) override; + void handleItemSelection(const DItemSelection& d) override; + void handleYesOrNo(const DYesOrNo& d) override; + + // Control + bool aboutToExit() override; + + // Derived + void setupTrayIcon(); + void setupProgressDialog(); +}; + +#endif // GUI_H diff --git a/app/gui/src/main.cpp b/app/gui/src/main.cpp new file mode 100644 index 0000000..ec510f0 --- /dev/null +++ b/app/gui/src/main.cpp @@ -0,0 +1,13 @@ +// Qt Includes +#include + +// Project Includes +#include "frontend/gui.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + FrontendGui frontend(&app); + return frontend.exec(); +} + diff --git a/app/src/controller.cpp b/app/src/controller.cpp deleted file mode 100644 index 4c41f81..0000000 --- a/app/src/controller.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Unit Include -#include "controller.h" - -// Qt Include -#include -#include - -// Project Includes -#include "kernel/driver.h" - -//=============================================================================================================== -// CONTROLLER -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -Controller::Controller(QObject* parent) : - QObject(parent), - mStatusRelay(this), - mExitCode(0), - mReadyToExit(false) -{ - // Create driver - Driver* driver = new Driver(QApplication::arguments()); - driver->moveToThread(&mWorkerThread); - - // Connect driver - Operation - connect(&mWorkerThread, &QThread::started, driver, &Driver::drive); // Thread start causes driver start - connect(driver, &Driver::finished, this, &Controller::driverFinishedHandler); // Result handling - connect(driver, &Driver::finished, driver, &QObject::deleteLater); // Have driver clean up itself - connect(driver, &Driver::finished, &mWorkerThread, &QThread::quit); // Have driver finish cause thread finish - connect(&mWorkerThread, &QThread::finished, this, &Controller::finisher); // Finish execution when thread quits - - // Connect driver - Directives - connect(driver, &Driver::asyncDirectiveAccounced, &mStatusRelay, &StatusRelay::asyncDirectiveHandler); - connect(driver, &Driver::syncDirectiveAccounced, &mStatusRelay, &StatusRelay::syncDirectiveHandler, Qt::BlockingQueuedConnection); - - // Connect driver - Task Cancellation - connect(&mStatusRelay, &StatusRelay::longTaskCanceled, driver, &Driver::cancelActiveLongTask); - connect(&mStatusRelay, &StatusRelay::longTaskCanceled, this, &Controller::longTaskCanceledHandler); - - // Connect quit handler - connect(&mStatusRelay, &StatusRelay::quitRequested, this, &Controller::quitRequestHandler); - connect(this, &Controller::quit, driver, &Driver::quitNow); -} - -//-Destructor------------------------------------------------------------- -Controller::~Controller() -{ - // Just to be safe, but never should be the case - if(mWorkerThread.isRunning()) - { - mWorkerThread.quit(); - mWorkerThread.wait(); - } -} - -//-Instance Functions------------------------------------------------------------- -//Private: -bool Controller::windowsAreOpen() -{ - // Based on Qt's own check here: https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qwindow.cpp?h=5.15.2#n2710 - // and here: https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qguiapplication.cpp?h=5.15.2#n3629 - QWindowList topWindows = QApplication::topLevelWindows(); - for(const QWindow* window : qAsConst(topWindows)) - { - if (window->isVisible() && !window->transientParent() && window->type() != Qt::ToolTip) - return true; - } - return false; -} - - -//Public: -void Controller::run() { mWorkerThread.start(); } - -//-Slots-------------------------------------------------------------------------------- -//Private: -void Controller::driverFinishedHandler(ErrorCode code) { mExitCode = code; } - -void Controller::quitRequestHandler() -{ - // Notify driver to quit if it still exists - emit quit(); - - // Close all top-level windows - qApp->closeAllWindows(); -} - -void Controller::longTaskCanceledHandler() -{ - /* A bit of a bodge. Pressing the Cancel button on a progress dialog - * doesn't count as closing it (it doesn't fire a close event) by the strict definition of - * QWidget, so here when the progress bar is closed we manually check to see if it was - * the last window if the application is ready to exit. - * - * Normally the progress bar should never still be open by that point, but this is here as - * a fail-safe as otherwise the application would deadlock when the progress bar is closed - * via the Cancel button. - */ - if(mReadyToExit && !windowsAreOpen()) - exit(); -} - -void Controller::finisher() -{ - // Quit once no windows remain - if(windowsAreOpen()) - { - mReadyToExit = true; - connect(qApp, &QGuiApplication::lastWindowClosed, this, &Controller::exit); - } - else - exit(); -} - -void Controller::exit() { QApplication::exit(mExitCode); } diff --git a/app/src/controller.h b/app/src/controller.h deleted file mode 100644 index 86c3bd8..0000000 --- a/app/src/controller.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef CONTROLLER_H -#define CONTROLLER_H - -// Qt Includes -#include -#include - -// Project Includes -#include "frontend/statusrelay.h" -#include "kernel/director.h" - -class Controller : public QObject -{ - Q_OBJECT -//-Instance Variables------------------------------------------------------------------------------------------------------------ -private: - QThread mWorkerThread; - StatusRelay mStatusRelay; - ErrorCode mExitCode; - bool mReadyToExit; - -//-Constructor------------------------------------------------------------------------------------------------- -public: - explicit Controller(QObject* parent = nullptr); - -//-Destructor---------------------------------------------------------------------------------------------------------- -public: - ~Controller(); - -//-Instance Functions------------------------------------------------------------------------------------------------------ -private: - bool windowsAreOpen(); - -public: - void run(); - -//-Signals & Slots------------------------------------------------------------------------------------------------------------ -private slots: - void driverFinishedHandler(ErrorCode code); - void quitRequestHandler(); - void longTaskCanceledHandler(); - void finisher(); - void exit(); - -signals: - void quit(); -}; - -#endif // CONTROLLER_H diff --git a/app/src/frontend/statusrelay.cpp b/app/src/frontend/statusrelay.cpp deleted file mode 100644 index 1f1d1ed..0000000 --- a/app/src/frontend/statusrelay.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Unit Include -#include "statusrelay.h" - -// Qt Includes -#include -#include -#include -#include - -// Qx Includes -#include -#include - -// Magic enum -#include "magic_enum_utility.hpp" - -//=============================================================================================================== -// STATUS RELAY -//=============================================================================================================== - -//-Constructor-------------------------------------------------------------------- -StatusRelay::StatusRelay(QObject* parent) : - QObject(parent), - mSystemClipboard(QGuiApplication::clipboard()) -{ - setupTrayIcon(); - setupProgressDialog(); -} - -//-Class Functions---------------------------------------------------------------------------------------------------- -//Private: -QMessageBox::StandardButtons StatusRelay::choicesToButtons(DBlockingError::Choices cs) -{ - QMessageBox::StandardButtons bs; - - magic_enum::enum_for_each([&] (auto val) { - constexpr DBlockingError::Choice c = val; - if(cs.testFlag(c)) - bs.setFlag(smChoiceButtonMap.toRight(c)); - }); - - return bs; -} - -template - requires Qx::any_of -QMessageBox* StatusRelay::prepareMessageBox(const MessageT& dMsg) -{ - QMessageBox* msg = new QMessageBox(); - msg->setIcon(QMessageBox::Information); - msg->setWindowTitle(QApplication::applicationName()); // This should be the default, but hey being explicit never hurt - msg->setText(dMsg.text); - - if(dMsg.selectable) - msg->setTextInteractionFlags(Qt::TextSelectableByMouse); - - return msg; -} - -//-Instance Functions-------------------------------------------------------------------------------------------------- -//Private: -void StatusRelay::setupTrayIcon() -{ - // Set Icon - mTrayIcon.setIcon(QIcon(u":/app/CLIFp.ico"_s)); - - // Set ToolTip Action - mTrayIcon.setToolTip(SYS_TRAY_STATUS); - connect(&mTrayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason){ - if(reason != QSystemTrayIcon::Context) - mTrayIcon.showMessage(mStatusHeading, mStatusMessage); - }); - - // Set Context Menu - QAction* quit = new QAction(&mTrayIconContextMenu); - quit->setIcon(QIcon(u":/tray/Exit.png"_s)); - quit->setText(u"Quit"_s); - connect(quit, &QAction::triggered, this, &StatusRelay::quitRequested); - mTrayIconContextMenu.addAction(quit); - mTrayIcon.setContextMenu(&mTrayIconContextMenu); - - // Display Icon - mTrayIcon.show(); -} - -void StatusRelay::setupProgressDialog() -{ - // Initialize dialog - mLongTaskProgressDialog.setCancelButtonText(u"Cancel"_s); - mLongTaskProgressDialog.setWindowModality(Qt::NonModal); - mLongTaskProgressDialog.setMinimumDuration(0); - mLongTaskProgressDialog.setAutoClose(true); - mLongTaskProgressDialog.setAutoReset(false); - mLongTaskProgressDialog.reset(); // Stops the auto-show timer that is started by QProgressDialog's ctor - connect(&mLongTaskProgressDialog, &QProgressDialog::canceled, this, &StatusRelay::longTaskCanceled); -} - -void StatusRelay::handleMessage(const DMessage& d) -{ - auto mb = prepareMessageBox(d); - mb->setAttribute(Qt::WA_DeleteOnClose); - mb->show(); -} - -void StatusRelay::handleError(const DError& d) { Qx::postError(d.error); } - -void StatusRelay::handleProcedureStart(const DProcedureStart& d) -{ - // Set label - mLongTaskProgressDialog.setLabelText(d.label); - - // Show right away - mLongTaskProgressDialog.setValue(0); -} - -void StatusRelay::handleProcedureStop(const DProcedureStop& d) -{ - Q_UNUSED(d); - /* Always reset the dialog regardless of whether it is visible or not as it may not be currently visible, - * but queued to be visible on the next event loop iteration and therefore still needs to be hidden - * immediately after. - */ - mLongTaskProgressDialog.reset(); -} - -void StatusRelay::handleProcedureProgress(const DProcedureProgress& d) { mLongTaskProgressDialog.setValue(d.current); } -void StatusRelay::handleProcedureScale(const DProcedureScale& d) { mLongTaskProgressDialog.setMaximum(d.max); } -void StatusRelay::handleClipboardUpdate(const DClipboardUpdate& d) { mSystemClipboard->setText(d.text); } -void StatusRelay::handleStatusUpdate(const DStatusUpdate& d) { mStatusHeading = d.heading; mStatusMessage = d.message; } - -// Sync directive handlers -void StatusRelay::handleBlockingMessage(const DBlockingMessage& d) -{ - auto mb = prepareMessageBox(d); - mb->exec(); - delete mb; -} - -void StatusRelay::handleBlockingError(const DBlockingError& d) -{ - Q_ASSERT(d.response); - auto btns = choicesToButtons(d.choices); - auto def = smChoiceButtonMap.toRight(d.defaultChoice); - int rawRes = Qx::postBlockingError(d.error, btns, def); - *d.response = smChoiceButtonMap.toLeft(static_cast(rawRes)); -} - -void StatusRelay::handleSaveFilename(const DSaveFilename& d) -{ - Q_ASSERT(d.response); - *d.response = QFileDialog::getSaveFileName(nullptr, d.caption, d.dir, d.filter, d.selectedFilter); -} - -void StatusRelay::handleExistingDir(const DExistingDir& d) -{ - Q_ASSERT(d.response); - *d.response = QFileDialog::getExistingDirectory(nullptr, d.caption, d.startingDir); -} - -void StatusRelay::handleItemSelection(const DItemSelection& d) -{ - Q_ASSERT(d.response); - *d.response = QInputDialog::getItem(nullptr, d.caption, d.label, d.items, 0, false); -} - -void StatusRelay::handleYesOrNo(const DYesOrNo& d) -{ - Q_ASSERT(d.response); - *d.response = QMessageBox::question(nullptr, QString(), d.question) == QMessageBox::Yes; -} - -//-Signals & Slots------------------------------------------------------------- -//Public Slots: -void StatusRelay::asyncDirectiveHandler(const AsyncDirective& aDirective) -{ - std::visit(qxFuncAggregate{ - [this](DMessage d){ handleMessage(d); }, - [this](DError d) { handleError(d); }, - [this](DProcedureStart d) { handleProcedureStart(d); }, - [this](DProcedureStop d) { handleProcedureStop(d); }, - [this](DProcedureProgress d) { handleProcedureProgress(d); }, - [this](DProcedureScale d) { handleProcedureScale(d); }, - [this](DClipboardUpdate d) { handleClipboardUpdate(d); }, - [this](DStatusUpdate d) { handleStatusUpdate(d); }, - }, aDirective); -} - -void StatusRelay::syncDirectiveHandler(const SyncDirective& sDirective) -{ - std::visit(qxFuncAggregate{ - [this](DBlockingMessage d){ handleBlockingMessage(d); }, - [this](DBlockingError d){ handleBlockingError(d); }, - [this](DSaveFilename d) { handleSaveFilename(d); }, - [this](DExistingDir d) { handleExistingDir(d); }, - [this](DItemSelection d) { handleItemSelection(d); }, - [this](DYesOrNo d) { handleYesOrNo(d); } - }, sDirective); -} diff --git a/app/src/frontend/statusrelay.h b/app/src/frontend/statusrelay.h deleted file mode 100644 index 242ecfb..0000000 --- a/app/src/frontend/statusrelay.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef STATUSRELAY_H -#define STATUSRELAY_H - -// Qt Includes -#include -#include -#include -#include -#include -#include - -// Qx Includes -#include -#include -#include - -// Project Includes -#include "kernel/directive.h" - -class StatusRelay : public QObject -{ - Q_OBJECT -//-Class Variables------------------------------------------------------------------------------------------------------ -private: - // System Messages - static inline const QString SYS_TRAY_STATUS = u"CLIFp is running"_s; - -//-Class Variables-------------------------------------------------------------------------------------------------------- -private: - Qx::Bimap smChoiceButtonMap{ - {DBlockingError::Choice::Ok, QMessageBox::Ok}, - {DBlockingError::Choice::Yes, QMessageBox::Yes}, - {DBlockingError::Choice::No, QMessageBox::No}, - }; - -//-Instance Variables------------------------------------------------------------------------------------------------------ -public: - QProgressDialog mLongTaskProgressDialog; - QString mStatusHeading; - QString mStatusMessage; - - QSystemTrayIcon mTrayIcon; - QMenu mTrayIconContextMenu; - QClipboard* mSystemClipboard; - -//-Constructor---------------------------------------------------------------------------------------------------------- -public: - explicit StatusRelay(QObject* parent = nullptr); - -//-Instance Functions-------------------------------------------------------------------------------------------------- -private: - QMessageBox::StandardButtons choicesToButtons(DBlockingError::Choices cs); - - template - requires Qx::any_of - QMessageBox* prepareMessageBox(const MessageT& dMsg); - -//-Instance Functions-------------------------------------------------------------------------------------------------- -private: - void setupTrayIcon(); - void setupProgressDialog(); - - // Async directive handlers - void handleMessage(const DMessage& d); - void handleError(const DError& d); - void handleProcedureStart(const DProcedureStart& d); - void handleProcedureStop(const DProcedureStop& d); - void handleProcedureProgress(const DProcedureProgress& d); - void handleProcedureScale(const DProcedureScale& d); - void handleClipboardUpdate(const DClipboardUpdate& d); - void handleStatusUpdate(const DStatusUpdate& d); - - // Sync directive handlers - void handleBlockingMessage(const DBlockingMessage& d); - void handleBlockingError(const DBlockingError& d); - void handleSaveFilename(const DSaveFilename& d); - void handleExistingDir(const DExistingDir& d); - void handleItemSelection(const DItemSelection& d); - void handleYesOrNo(const DYesOrNo& d); - -//-Signals & Slots------------------------------------------------------------------------------------------------------ -public slots: - // Directive handlers - void asyncDirectiveHandler(const AsyncDirective& aDirective); - void syncDirectiveHandler(const SyncDirective& sDirective); - -signals: - void longTaskCanceled(); - void quitRequested(); -}; - -#endif // STATUSRELAY_H diff --git a/app/src/main.cpp b/app/src/main.cpp deleted file mode 100644 index 6e7e8d6..0000000 --- a/app/src/main.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Qt Includes -#include - -// Project Includes -#include "kernel/directive.h" -#include "controller.h" -#ifdef __linux__ - #include "utility.h" -#endif -#include "project_vars.h" - -int main(int argc, char *argv[]) -{ - //-Basic Application Setup------------------------------------------------------------- - - // QApplication Object - QApplication app(argc, argv); - app.setQuitOnLastWindowClosed(false); - - // Set application name - app.setApplicationName(PROJECT_APP_NAME); - app.setApplicationVersion(PROJECT_VERSION_STR); - -#ifdef __linux__ - // Set application icon - app.setWindowIcon(Utility::appIconFromResources()); -#endif - - // Register metatypes - qRegisterMetaType(); - qRegisterMetaType(); - - // Create application controller - Controller appController(&app); - - // Start driver - appController.run(); - - // Start event loop - return app.exec(); -} - - - - diff --git a/app/src/utility.cpp b/app/src/utility.cpp deleted file mode 100644 index cc5cdf4..0000000 --- a/app/src/utility.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Unit Include -#include "utility.h" - -// Qt Includes -#include -#include - -// Project Includes -#include "project_vars.h" - -//-Macros---------------------------------------- -#define APP_ICON_RES_BASE_PATH u":/app/"_s - -using namespace Qt::Literals::StringLiterals; - -namespace Utility -{ - -namespace -{ - const QString dimStr(int w, int h) - { - static const QString dimTemplate = u"%1x%2"_s; - return dimTemplate.arg(w).arg(h); - } - - const QString appIconResourceFullPath(int w, int h) - { - return APP_ICON_RES_BASE_PATH + dimStr(w, h) + '/' + PROJECT_SHORT_NAME u".png"_s; - } - - const QList& availableAppIconSizes() - { - static QList sizes; - if(sizes.isEmpty()) - { - QDirIterator itr(APP_ICON_RES_BASE_PATH, QDir::Dirs | QDir::NoDotAndDotDot); - while(itr.hasNext()) - { - itr.next(); - QStringList dims = itr.fileName().split('x'); - int w = dims.first().toInt(); - int h = dims.last().toInt(); - sizes << QSize(w, h); - } - } - return sizes; - } -} - -//-Functions---------------------------------------------------- -const QIcon& appIconFromResources() -{ - static QIcon appIcon; - if(appIcon.isNull()) - { - const QList& sizes = availableAppIconSizes(); - for(const QSize& size : sizes) - appIcon.addFile(appIconResourceFullPath(size.width(), size.height()), size); - } - return appIcon; -} - -bool installAppIconForUser() -{ - static const QDir iconDestBaseDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + u"/icons/hicolor"_s); - - const QList& sizes = availableAppIconSizes(); - for(const QSize& size : sizes) - { - QString resSpecificSubPath = dimStr(size.width(), size.height()) + u"/apps"_s; - - // Ensure path exists - iconDestBaseDir.mkpath(u"./"_s + resSpecificSubPath); - - // Determine paths - QString fullSrcPath = appIconResourceFullPath(size.width(), size.height()); - QString fullDestPath = iconDestBaseDir.absolutePath() + '/' + resSpecificSubPath + '/' + PROJECT_SHORT_NAME + u".png"_s; - - // Remove exiting file if it exists (icon could need to be updated), then copy the new icon - if((QFile::exists(fullDestPath) && !QFile::remove(fullDestPath) ) || !QFile::copy(fullSrcPath, fullDestPath)) - return false; - } - - return true; -} - -} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..17de2f9 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(backend) +add_subdirectory(frontend_framework) diff --git a/lib/backend/CMakeLists.txt b/lib/backend/CMakeLists.txt new file mode 100644 index 0000000..4b64881 --- /dev/null +++ b/lib/backend/CMakeLists.txt @@ -0,0 +1,156 @@ +#================= Common Build ========================= + +# Pre-configure target +set(BACKEND_API + kernel/directive.h + kernel/driver.h + kernel/errorcode.h +) + +set(BACKEND_IMPLEMENTATION + kernel/buildinfo.h + kernel/core.h + kernel/core.cpp + kernel/director.h + kernel/director.cpp + kernel/directorate.h + kernel/directorate.cpp + kernel/driver_p.h + kernel/driver.cpp + kernel/errorstatus.h + kernel/errorstatus.cpp + command/command.h + command/command.cpp + command/c-download.h + command/c-download.cpp + command/c-link.h + command/c-link.cpp + command/c-play.h + command/c-play.cpp + command/c-prepare.h + command/c-prepare.cpp + command/c-run.h + command/c-run.cpp + command/c-share.h + command/c-share.cpp + command/c-show.cpp + command/c-show.h + command/c-update.h + command/c-update.cpp + command/title-command.h + command/title-command.cpp + task/task.h + task/task.cpp + task/t-download.h + task/t-download.cpp + task/t-exec.h + task/t-exec.cpp + task/t-extra.h + task/t-extra.cpp + task/t-extract.h + task/t-extract.cpp + task/t-generic.h + task/t-generic.cpp + task/t-message.h + task/t-message.cpp + task/t-mount.h + task/t-mount.cpp + task/t-sleep.h + task/t-sleep.cpp + tools/blockingprocessmanager.h + tools/blockingprocessmanager.cpp + tools/deferredprocessmanager.h + tools/deferredprocessmanager.cpp + tools/mounter_game_server.h + tools/mounter_game_server.cpp + tools/mounter_qmp.h + tools/mounter_qmp.cpp + tools/mounter_router.h + tools/mounter_router.cpp + utility.h +) + +set(BACKEND_LINKS + PRIVATE + Qt6::Sql + Qt6::Network + Qx::Io + Qx::Network + Fp::Fp + QuaZip::QuaZip + magic_enum::magic_enum + QI-QMP::Qmpi + PUBLIC + Qt6::Core + Qx::Core +) + +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + list(APPEND BACKEND_IMPLEMENTATION + command/c-link_win.cpp + task/t-exec_win.cpp + task/t-bideprocess.h + task/t-bideprocess.cpp + ) + + list(APPEND BACKEND_LINKS + PRIVATE + Qx::Windows + ) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + list(APPEND BACKEND_IMPLEMENTATION + command/c-link_linux.cpp + task/t-awaitdocker.h + task/t-awaitdocker.cpp + task/t-exec_linux.cpp + ) + list(APPEND BACKEND_LINKS + PRIVATE + Qx::Linux + ) +endif() + +# Add via ob standard library +include(OB/Library) +ob_add_standard_library(${BACKEND_TARGET_NAME} + NAMESPACE "${PROJECT_NAMESPACE}" + ALIAS "${BACKEND_ALIAS_NAME}" + EXPORT_HEADER + PATH "${PROJECT_NAMESPACE_LC}_${BACKEND_ALIAS_NAME_LC}_export.h" + HEADERS_API + FILES ${BACKEND_API} + IMPLEMENTATION + ${BACKEND_IMPLEMENTATION} + LINKS + ${BACKEND_LINKS} +) + +## Forward select project variables to C++ code +include(OB/CppVars) +ob_add_cpp_vars(${BACKEND_TARGET_NAME} + NAME "_backend_project_vars" + PREFIX "PROJECT_" + VARS + VERSION_STR "\"${PROJECT_VERSION_VERBOSE}\"" + SHORT_NAME "\"${PROJECT_NAME}\"" + TARGET_FP_VER_PFX_STR "\"${TARGET_FP_VERSION_PREFIX}\"" +) + +## Add build info +if(BUILD_SHARED_LIBS) + set(link_str "Shared") +else() + set(link_str "Static") +endif() + +ob_add_cpp_vars(${BACKEND_TARGET_NAME} + NAME "_buildinfo" + PREFIX "BUILDINFO_" + VARS + SYSTEM "\"${CMAKE_SYSTEM_NAME}\"" + LINKAGE "\"${link_str}\"" + COMPILER "u\"${CMAKE_CXX_COMPILER_ID}\"_s" + COMPILER_VER_STR "u\"${CMAKE_CXX_COMPILER_VERSION}\"_s" +) diff --git a/app/src/kernel/directive.h b/lib/backend/include/kernel/directive.h similarity index 82% rename from app/src/kernel/directive.h rename to lib/backend/include/kernel/directive.h index 74668e8..eaf8079 100644 --- a/app/src/kernel/directive.h +++ b/lib/backend/include/kernel/directive.h @@ -1,12 +1,17 @@ #ifndef DIRECTIVE_H #define DIRECTIVE_H +// Shared Library Support +#include "clifp_backend_export.h" + // Qt Includes #include // Qx Includes #include +// Shared-lib + /* TODO: * * In this file there are some structs with redundant members where one could easily conceive @@ -34,40 +39,40 @@ */ //-Non-blocking Directives----------------------------------------------------------------- -struct DMessage +CLIFP_BACKEND_EXPORT struct DMessage { QString text; bool selectable = false; }; -struct DError +CLIFP_BACKEND_EXPORT struct DError { Qx::Error error; }; -struct DProcedureStart +CLIFP_BACKEND_EXPORT struct DProcedureStart { QString label; }; -struct DProcedureStop {}; +CLIFP_BACKEND_EXPORT struct DProcedureStop {}; -struct DProcedureProgress +CLIFP_BACKEND_EXPORT struct DProcedureProgress { quint64 current; }; -struct DProcedureScale +CLIFP_BACKEND_EXPORT struct DProcedureScale { quint64 max; }; -struct DClipboardUpdate +CLIFP_BACKEND_EXPORT struct DClipboardUpdate { QString text; }; -struct DStatusUpdate +CLIFP_BACKEND_EXPORT struct DStatusUpdate { QString heading; QString message; @@ -88,13 +93,13 @@ template concept AsyncDirectiveT = requires(AsyncDirective ad, T t) { ad = t; }; //-Blocking Directives--------------------------------------------------------------------- -struct DBlockingMessage +CLIFP_BACKEND_EXPORT struct DBlockingMessage { QString text; bool selectable = false; }; -struct DBlockingError +CLIFP_BACKEND_EXPORT struct DBlockingError { enum class Choice {Ok, Yes, No}; Q_DECLARE_FLAGS(Choices, Choice); @@ -106,7 +111,7 @@ struct DBlockingError }; Q_DECLARE_OPERATORS_FOR_FLAGS(DBlockingError::Choices); -struct DSaveFilename +CLIFP_BACKEND_EXPORT struct DSaveFilename { QString caption; QString dir; @@ -115,7 +120,7 @@ struct DSaveFilename QString* response = nullptr; }; -struct DExistingDir +CLIFP_BACKEND_EXPORT struct DExistingDir { QString caption; QString startingDir; @@ -123,7 +128,7 @@ struct DExistingDir // TODO: Make sure to use QFileDialog::ShowDirsOnly on receiving end }; -struct DItemSelection +CLIFP_BACKEND_EXPORT struct DItemSelection { QString caption; QString label; @@ -131,7 +136,7 @@ struct DItemSelection QString* response = nullptr; }; -struct DYesOrNo +CLIFP_BACKEND_EXPORT struct DYesOrNo { QString question; bool* response = nullptr; diff --git a/lib/backend/include/kernel/driver.h b/lib/backend/include/kernel/driver.h new file mode 100644 index 0000000..1e731b8 --- /dev/null +++ b/lib/backend/include/kernel/driver.h @@ -0,0 +1,51 @@ +#ifndef DRIVER_H +#define DRIVER_H + +// Shared Library Support +#include "clifp_backend_export.h" + +// Qt Includes +#include + +// Project Includes +#include "kernel/errorcode.h" +#include "kernel/directive.h" + +class DriverPrivate; + +CLIFP_BACKEND_EXPORT class Driver : public QObject +{ + Q_OBJECT; + Q_DECLARE_PRIVATE(Driver); + +//-Instance Variables------------------------------------------------------------------------------------------------------------ +private: + std::unique_ptr d_ptr; + +//-Constructor------------------------------------------------------------------------------------------------- +public: + Driver(QStringList arguments); + +//-Destructor------------------------------------------------------------------------------------------------- +public: + ~Driver(); // Required for d_ptr destructor to compile + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +public slots: + // Worker main + void drive(); + + // Termination + void cancelActiveLongTask(); + void quitNow(); + +signals: + // Worker status + void finished(ErrorCode errorCode); + + // Director forwarders + void asyncDirectiveAccounced(const AsyncDirective& aDirective); + void syncDirectiveAccounced(const SyncDirective& sDirective); +}; + +#endif // DRIVER_H diff --git a/lib/backend/include/kernel/errorcode.h b/lib/backend/include/kernel/errorcode.h new file mode 100644 index 0000000..92721cf --- /dev/null +++ b/lib/backend/include/kernel/errorcode.h @@ -0,0 +1,9 @@ +#ifndef ERRORCODE_H +#define ERRORCODE_H + +#include + +// General Aliases +using ErrorCode = quint32; + +#endif // ERRORCODE_H diff --git a/app/src/command/c-download.cpp b/lib/backend/src/command/c-download.cpp similarity index 100% rename from app/src/command/c-download.cpp rename to lib/backend/src/command/c-download.cpp diff --git a/app/src/command/c-download.h b/lib/backend/src/command/c-download.h similarity index 73% rename from app/src/command/c-download.h rename to lib/backend/src/command/c-download.h index 5a1f2a5..ce2e56e 100644 --- a/app/src/command/c-download.h +++ b/lib/backend/src/command/c-download.h @@ -10,7 +10,7 @@ class QX_ERROR_TYPE(CDownloadError, "CDownloadError", 1217) { friend class CDownload; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -18,23 +18,23 @@ class QX_ERROR_TYPE(CDownloadError, "CDownloadError", 1217) InvalidPlaylist }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, {InvalidPlaylist, u""_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: CDownloadError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -49,7 +49,7 @@ class QX_ERROR_TYPE(CDownloadError, "CDownloadError", 1217) class CDownload : public Command { - //-Class Variables------------------------------------------------------------------------------------------------------ +//-Class Variables------------------------------------------------------------------------------------------------------ private: // Status static inline const QString STATUS_DOWNLOAD = u"Downloading data packs"_s; @@ -74,17 +74,16 @@ class CDownload : public Command static inline const QString NAME = u"download"_s; static inline const QString DESCRIPTION = u"Download game data packs in bulk"_s; - //-Constructor---------------------------------------------------------------------------------------------------------- +//-Constructor---------------------------------------------------------------------------------------------------------- public: CDownload(Core& coreRef); - //-Instance Functions------------------------------------------------------------------------------------------------------ +//-Instance Functions------------------------------------------------------------------------------------------------------ protected: QList options() const override; QSet requiredOptions() const override; QString name() const override; Qx::Error perform() override; }; -REGISTER_COMMAND(CDownload::NAME, CDownload, CDownload::DESCRIPTION); #endif // CDOWNLOAD_H diff --git a/app/src/command/c-link.cpp b/lib/backend/src/command/c-link.cpp similarity index 100% rename from app/src/command/c-link.cpp rename to lib/backend/src/command/c-link.cpp diff --git a/app/src/command/c-link.h b/lib/backend/src/command/c-link.h similarity index 90% rename from app/src/command/c-link.h rename to lib/backend/src/command/c-link.h index 0f5e338..7bbe85e 100644 --- a/app/src/command/c-link.h +++ b/lib/backend/src/command/c-link.h @@ -10,7 +10,7 @@ class QX_ERROR_TYPE(CLinkError, "CLinkError", 1213) { friend class CLink; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -19,7 +19,7 @@ class QX_ERROR_TYPE(CLinkError, "CLinkError", 1213) IconInstallFailed = 2 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, @@ -27,7 +27,7 @@ class QX_ERROR_TYPE(CLinkError, "CLinkError", 1213) {IconInstallFailed, u"Failed to install icons required for the shortcut."_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; @@ -36,7 +36,7 @@ class QX_ERROR_TYPE(CLinkError, "CLinkError", 1213) private: CLinkError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -101,6 +101,5 @@ class CLink : public TitleCommand QString name() const override; Qx::Error perform() override ; }; -REGISTER_COMMAND(CLink::NAME, CLink, CLink::DESCRIPTION); #endif // CLINK_H diff --git a/app/src/command/c-link_linux.cpp b/lib/backend/src/command/c-link_linux.cpp similarity index 87% rename from app/src/command/c-link_linux.cpp rename to lib/backend/src/command/c-link_linux.cpp index 14e671e..a7e25df 100644 --- a/app/src/command/c-link_linux.cpp +++ b/lib/backend/src/command/c-link_linux.cpp @@ -6,7 +6,7 @@ // Project Includes #include "c-play.h" -#include "project_vars.h" +#include "_backend_project_vars.h" #include "utility.h" //=============================================================================================================== @@ -17,14 +17,6 @@ //Private: Qx::Error CLink::createShortcut(const QString& name, const QDir& dir, QUuid id) { - // Add/update CLIFp icon set - if(!Utility::installAppIconForUser()) - { - CLinkError err(CLinkError::IconInstallFailed); - postDirective(err); - return err; - } - // Setup desktop entry Qx::ApplicationDesktopEntry ade; ade.setName(name); diff --git a/app/src/command/c-link_win.cpp b/lib/backend/src/command/c-link_win.cpp similarity index 100% rename from app/src/command/c-link_win.cpp rename to lib/backend/src/command/c-link_win.cpp diff --git a/app/src/command/c-play.cpp b/lib/backend/src/command/c-play.cpp similarity index 99% rename from app/src/command/c-play.cpp rename to lib/backend/src/command/c-play.cpp index 30e9eef..77b74be 100644 --- a/app/src/command/c-play.cpp +++ b/lib/backend/src/command/c-play.cpp @@ -1,9 +1,6 @@ // Unit Include #include "c-play.h" -// Qt Includes -#include - // Qx Includes #include diff --git a/app/src/command/c-play.h b/lib/backend/src/command/c-play.h similarity index 98% rename from app/src/command/c-play.h rename to lib/backend/src/command/c-play.h index 6b6298c..e43da8d 100644 --- a/app/src/command/c-play.h +++ b/lib/backend/src/command/c-play.h @@ -109,6 +109,5 @@ class CPlay : public TitleCommand public: bool requiresServices() const override; }; -REGISTER_COMMAND(CPlay::NAME, CPlay, CPlay::DESCRIPTION); #endif // CPLAY_H diff --git a/app/src/command/c-prepare.cpp b/lib/backend/src/command/c-prepare.cpp similarity index 100% rename from app/src/command/c-prepare.cpp rename to lib/backend/src/command/c-prepare.cpp diff --git a/app/src/command/c-prepare.h b/lib/backend/src/command/c-prepare.h similarity index 95% rename from app/src/command/c-prepare.h rename to lib/backend/src/command/c-prepare.h index 8c9478d..2693275 100644 --- a/app/src/command/c-prepare.h +++ b/lib/backend/src/command/c-prepare.h @@ -35,6 +35,5 @@ class CPrepare : public TitleCommand public: bool requiresServices() const override; }; -REGISTER_COMMAND(CPrepare::NAME, CPrepare, CPrepare::DESCRIPTION); #endif // CPREPARE_H diff --git a/app/src/command/c-run.cpp b/lib/backend/src/command/c-run.cpp similarity index 100% rename from app/src/command/c-run.cpp rename to lib/backend/src/command/c-run.cpp diff --git a/app/src/command/c-run.h b/lib/backend/src/command/c-run.h similarity index 86% rename from app/src/command/c-run.h rename to lib/backend/src/command/c-run.h index 78a2af6..9cb54cf 100644 --- a/app/src/command/c-run.h +++ b/lib/backend/src/command/c-run.h @@ -10,29 +10,29 @@ class QX_ERROR_TYPE(CRunError, "CRunError", 1215) { friend class CRun; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { NoError = 0 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: CRunError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -86,6 +86,5 @@ class CRun : public Command public: bool requiresServices() const override; }; -REGISTER_COMMAND(CRun::NAME, CRun, CRun::DESCRIPTION); #endif // CRUN_H diff --git a/app/src/command/c-share.cpp b/lib/backend/src/command/c-share.cpp similarity index 100% rename from app/src/command/c-share.cpp rename to lib/backend/src/command/c-share.cpp diff --git a/app/src/command/c-share.h b/lib/backend/src/command/c-share.h similarity index 90% rename from app/src/command/c-share.h rename to lib/backend/src/command/c-share.h index 54b7892..bd35146 100644 --- a/app/src/command/c-share.h +++ b/lib/backend/src/command/c-share.h @@ -7,7 +7,7 @@ class QX_ERROR_TYPE(CShareError, "CShareError", 1216) { friend class CShare; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -16,7 +16,7 @@ class QX_ERROR_TYPE(CShareError, "CShareError", 1216) UnregistrationFailed = 2 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, @@ -24,16 +24,16 @@ class QX_ERROR_TYPE(CShareError, "CShareError", 1216) {UnregistrationFailed, u"Failed to remove CLIFp as the 'flashpoint' scheme handler."_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: CShareError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -104,6 +104,5 @@ class CShare : public TitleCommand QString name() const override; Qx::Error perform() override; }; -REGISTER_COMMAND(CShare::NAME, CShare, CShare::DESCRIPTION); #endif // CSHARE_H diff --git a/app/src/command/c-show.cpp b/lib/backend/src/command/c-show.cpp similarity index 100% rename from app/src/command/c-show.cpp rename to lib/backend/src/command/c-show.cpp diff --git a/app/src/command/c-show.h b/lib/backend/src/command/c-show.h similarity index 86% rename from app/src/command/c-show.h rename to lib/backend/src/command/c-show.h index 257a13f..30ed73d 100644 --- a/app/src/command/c-show.h +++ b/lib/backend/src/command/c-show.h @@ -10,7 +10,7 @@ class QX_ERROR_TYPE(CShowError, "CShowError", 1217) { friend class CShow; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -18,23 +18,23 @@ class QX_ERROR_TYPE(CShowError, "CShowError", 1217) MissingThing = 1 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, {MissingThing, u"No message or extra to show was provided."_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: CShowError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -84,6 +84,5 @@ class CShow : public Command QString name() const override; Qx::Error perform() override; }; -REGISTER_COMMAND(CShow::NAME, CShow, CShow::DESCRIPTION); #endif // CSHOW_H diff --git a/app/src/command/c-update.cpp b/lib/backend/src/command/c-update.cpp similarity index 100% rename from app/src/command/c-update.cpp rename to lib/backend/src/command/c-update.cpp diff --git a/app/src/command/c-update.h b/lib/backend/src/command/c-update.h similarity index 99% rename from app/src/command/c-update.h rename to lib/backend/src/command/c-update.h index 916051d..b82d9cf 100644 --- a/app/src/command/c-update.h +++ b/lib/backend/src/command/c-update.h @@ -205,6 +205,5 @@ class CUpdate : public Command bool requiresFlashpoint() const override; bool autoBlockNewInstances() const override; }; -REGISTER_COMMAND(CUpdate::NAME, CUpdate, CUpdate::DESCRIPTION); #endif // CUPDATE_H diff --git a/app/src/command/command.cpp b/lib/backend/src/command/command.cpp similarity index 73% rename from app/src/command/command.cpp rename to lib/backend/src/command/command.cpp index 754e562..a4f5c74 100644 --- a/app/src/command/command.cpp +++ b/lib/backend/src/command/command.cpp @@ -1,14 +1,19 @@ // Unit Includes #include "command.h" -// Qt Includes -#include - // Qx Includes #include // Project Includes #include "kernel/core.h" +#include "command/c-download.h" +#include "command/c-link.h" +#include "command/c-play.h" +#include "command/c-prepare.h" +#include "command/c-run.h" +#include "command/c-share.h" +#include "command/c-show.h" +#include "command/c-update.h" //=============================================================================================================== // CommandError @@ -35,6 +40,14 @@ QString CommandError::deriveDetails() const { return mDetails; } Qx::Severity CommandError::deriveSeverity() const { return Qx::Critical; } CommandError& CommandError::wDetails(const QString& det) { mDetails = det; return *this; } +template +CommandError CommandError::arged(Args... args) const +{ + CommandError a = *this; + a.mString = a.mString.arg(args...); + return a; +} + //Public: bool CommandError::isValid() const { return mType != NoError; } CommandError::Type CommandError::type() const { return mType; } @@ -53,20 +66,61 @@ Command::Command(Core& coreRef) : //-Class Functions------------------------------------------------------------------ //Private: -QMap& Command::registry() { static QMap registry; return registry; } +template + requires std::derived_from +void Command::registerCommand() +{ + smRegistry.emplace(C::NAME, Entry{ + .producer = [](Core& core)->std::unique_ptr{ return std::make_unique(core); }, + .desc = C::DESCRIPTION + }); +} + +template +void Command::registerCommands() { + (registerCommand(), ...); +} //Public: -void Command::registerCommand(const QString& name, CommandFactory* factory, const QString& desc) { registry()[name] = {factory, desc}; } +void Command::registerAllCommands() +{ + /* This used to be handled independently in each Command's header file via the initialization (ctor) of static objects; + * however, now that this is in a lib, when the lib is static those objects are discarded by the linker since they + * aren't directly used (so the linker thinks they're worthless as it doesn't consider their side effects); therefore, + * we have to have code that is reachable from the final application do the registration instead. So, we explicitly register + * all commands here and then call this function in Driver, which is setup by the application. + * + * NOTE: REGISTER ALL COMMANDS HERE + */ + registerCommands< + CDownload, + CLink, + CPlay, + CPrepare, + CRun, + CShare, + CShow, + CUpdate + >(); +} CommandError Command::isRegistered(const QString &name) { - return registry().contains(name) ? CommandError() : ERR_INVALID_COMMAND.arged(name); + return smRegistry.contains(name) ? CommandError() : ERR_INVALID_COMMAND.arged(name); +} + +QList Command::registered() +{ + QList keys; + keys.resize(smRegistry.size()); + std::transform(smRegistry.cbegin(), smRegistry.cend(), std::back_inserter(keys), + [](const auto& pair) { return pair.first; }); + return keys; } -QList Command::registered() { return registry().keys(); } -bool Command::hasDescription(const QString& name) { return !registry().value(name).description.isEmpty(); } -QString Command::describe(const QString& name) { return registry().value(name).description; } -std::unique_ptr Command::acquire(const QString& name, Core& coreRef) { return registry().value(name).factory->produce(coreRef); } +bool Command::hasDescription(const QString& name) { return smRegistry.contains(name) ? !smRegistry.at(name).desc.isEmpty() : false; } +QString Command::describe(const QString& name) { return smRegistry.contains(name) ? smRegistry.at(name).desc : QString(); } +std::unique_ptr Command::acquire(const QString& name, Core& core) { return smRegistry.contains(name) ? smRegistry.at(name).producer(core) : nullptr; } //-Instance Functions------------------------------------------------------------------------------------------------------ //Private: diff --git a/app/src/command/command.h b/lib/backend/src/command/command.h similarity index 83% rename from app/src/command/command.h rename to lib/backend/src/command/command.h index c4c3d88..466475a 100644 --- a/app/src/command/command.h +++ b/lib/backend/src/command/command.h @@ -13,16 +13,6 @@ class CommandFactory; class Core; -//-Macros------------------------------------------------------------------------------------------------------------------- -#define REGISTER_COMMAND(name, command, desc) \ - class command##Factory : public CommandFactory \ - { \ - public: \ - command##Factory() { Command::registerCommand(name, this, desc); } \ - virtual std::unique_ptr produce(Core& coreRef) { return std::make_unique(coreRef); } \ - }; \ - static command##Factory _##command##Factory; - class QX_ERROR_TYPE(CommandError, "CommandError", 1210) { friend class Command; @@ -61,16 +51,10 @@ class QX_ERROR_TYPE(CommandError, "CommandError", 1210) QString deriveSecondary() const override; QString deriveDetails() const override; Qx::Severity deriveSeverity() const override; + CommandError& wDetails(const QString& det); template - CommandError arged(Args... args) const - { - CommandError a = *this; - a.mString = a.mString.arg(args...); - return a; - } - - CommandError& wDetails(const QString& det); + CommandError arged(Args... args) const; public: bool isValid() const; @@ -79,15 +63,22 @@ class QX_ERROR_TYPE(CommandError, "CommandError", 1210) }; class Command : public Directorate -{ +{ //-Class Structs------------------------------------------------------------------------------------------------------ -protected: +private: struct Entry { - CommandFactory* factory; - QString description; + using Producer = std::unique_ptr (*)(Core& core); + using Description = const QString&; + + Producer producer; + Description desc; }; +//-Class Aliases------------------------------------------------------------------------------------------------------ +private: + using Registry = std::map; + //-Class Variables-------------------------------------------------------------------------------------------------------- private: // Error @@ -123,6 +114,9 @@ class Command : public Directorate // Meta static inline const QString NAME = u"command"_s; + // Registry + static inline Registry smRegistry; + //-Instance Variables------------------------------------------------------------------------------------------------------ private: QString mHelpString; @@ -141,13 +135,18 @@ class Command : public Directorate //-Class Functions---------------------------------------------------------------------------------------------------------- private: - static QMap& registry(); + template + requires std::derived_from + static void registerCommand(); + + template + static void registerCommands(); public: - static void registerCommand(const QString& name, CommandFactory* factory, const QString& desc); + static void registerAllCommands(); static CommandError isRegistered(const QString& name); static QList registered(); - static std::unique_ptr acquire(const QString& name, Core& coreRef); + static std::unique_ptr acquire(const QString& name, Core& core); static bool hasDescription(const QString& name); static QString describe(const QString& name); @@ -172,11 +171,4 @@ class Command : public Directorate Qx::Error process(const QStringList& commandLine); }; -class CommandFactory -{ -//-Instance Functions------------------------------------------------------------------------------------------------------ -public: - virtual std::unique_ptr produce(Core& coreRef) = 0; -}; - #endif // COMMAND_H diff --git a/app/src/command/title-command.cpp b/lib/backend/src/command/title-command.cpp similarity index 100% rename from app/src/command/title-command.cpp rename to lib/backend/src/command/title-command.cpp diff --git a/app/src/command/title-command.h b/lib/backend/src/command/title-command.h similarity index 100% rename from app/src/command/title-command.h rename to lib/backend/src/command/title-command.h diff --git a/app/src/kernel/buildinfo.h b/lib/backend/src/kernel/buildinfo.h similarity index 100% rename from app/src/kernel/buildinfo.h rename to lib/backend/src/kernel/buildinfo.h diff --git a/app/src/kernel/core.cpp b/lib/backend/src/kernel/core.cpp similarity index 99% rename from app/src/kernel/core.cpp rename to lib/backend/src/kernel/core.cpp index 2ca012b..5e81735 100644 --- a/app/src/kernel/core.cpp +++ b/lib/backend/src/kernel/core.cpp @@ -1,9 +1,6 @@ // Unit Include #include "core.h" -// Qt Includes -#include - // Qx Includes #include #include @@ -58,8 +55,7 @@ QString CoreError::deriveSecondary() const { return mSpecific; } //-Constructor------------------------------------------------------------- //Public: -Core::Core(QObject* parent) : - QObject(parent), +Core::Core() : Directorate(&mDirector), mServicesMode(ServicesMode::Standalone), mStatusHeading(u"Initializing"_s), diff --git a/app/src/kernel/core.h b/lib/backend/src/kernel/core.h similarity index 98% rename from app/src/kernel/core.h rename to lib/backend/src/kernel/core.h index a3819e0..5620493 100644 --- a/app/src/kernel/core.h +++ b/lib/backend/src/kernel/core.h @@ -7,15 +7,10 @@ // Qt Includes #include #include -#include -#include #include -#include -#include // Qx Includes #include -#include // libfp Includes #include @@ -24,7 +19,7 @@ #include "kernel/buildinfo.h" #include "kernel/directorate.h" #include "task/task.h" -#include "project_vars.h" +#include "_backend_project_vars.h" class QX_ERROR_TYPE(CoreError, "CoreError", 1200) { @@ -224,7 +219,7 @@ class Core : public QObject, public Directorate //-Constructor---------------------------------------------------------------------------------------------------------- public: - explicit Core(QObject* parent); + explicit Core(); //-Instance Functions------------------------------------------------------------------------------------------------------ private: diff --git a/app/src/kernel/director.cpp b/lib/backend/src/kernel/director.cpp similarity index 98% rename from app/src/kernel/director.cpp rename to lib/backend/src/kernel/director.cpp index 8291e2d..eb0038d 100644 --- a/app/src/kernel/director.cpp +++ b/lib/backend/src/kernel/director.cpp @@ -5,9 +5,9 @@ #include // Project Includes -#include "project_vars.h" #include "utility.h" #include "task/task.h" +#include "_backend_project_vars.h" //=============================================================================================================== // DirectorError @@ -167,8 +167,7 @@ void Director::logEvent(const QString& src, const QString& event) log([&](){ return mLogger.recordGeneralEvent(src, event); }); } - -// TODO: Have task have a toString function/operator instead of "members()" +// TODO: Have task have a toString function/operator instead of "members()" (or make members() private and have toString() use that) void Director::logTask(const QString& src, const Task* task) { logEvent(src, LOG_EVENT_TASK_ENQ.arg(task->name(), task->members().join(u", "_s))); } ErrorCode Director::logFinish(const QString& src, const Qx::Error& errorState) diff --git a/app/src/kernel/director.h b/lib/backend/src/kernel/director.h similarity index 99% rename from app/src/kernel/director.h rename to lib/backend/src/kernel/director.h index b16e6de..f1be958 100644 --- a/app/src/kernel/director.h +++ b/lib/backend/src/kernel/director.h @@ -11,9 +11,7 @@ // Project Includes #include "kernel/directive.h" - -// General Aliases -using ErrorCode = quint32; +#include "kernel/errorcode.h" class Task; diff --git a/app/src/kernel/directorate.cpp b/lib/backend/src/kernel/directorate.cpp similarity index 100% rename from app/src/kernel/directorate.cpp rename to lib/backend/src/kernel/directorate.cpp diff --git a/app/src/kernel/directorate.h b/lib/backend/src/kernel/directorate.h similarity index 100% rename from app/src/kernel/directorate.h rename to lib/backend/src/kernel/directorate.h diff --git a/app/src/kernel/driver.cpp b/lib/backend/src/kernel/driver.cpp similarity index 78% rename from app/src/kernel/driver.cpp rename to lib/backend/src/kernel/driver.cpp index 243ac46..77f55b8 100644 --- a/app/src/kernel/driver.cpp +++ b/lib/backend/src/kernel/driver.cpp @@ -1,14 +1,16 @@ // Unit Include -#include "driver.h" +#include "kernel/driver.h" +#include "driver_p.h" // Qt Includes -#include +#include // Qx Includes #include #include // Project Includes +#include "kernel/core.h" #include "command/command.h" #include "command/c-update.h" #include "task/t-exec.h" @@ -38,38 +40,44 @@ QString DriverError::derivePrimary() const { return ERR_STRINGS.value(mType); } QString DriverError::deriveSecondary() const { return mSpecific; } //=============================================================================================================== -// Driver +// DriverPrivate //=============================================================================================================== //-Constructor-------------------------------------------------------------------- -Driver::Driver(QStringList arguments) : +//Public: +DriverPrivate::DriverPrivate(Driver* q, QStringList arguments) : + q_ptr(q), mArguments(arguments), mCore(nullptr), mErrorStatus(), mCurrentTask(nullptr), mCurrentTaskNumber(-1), mQuitRequested(false) -{} +{ + // Required in order to ensure the commands aren't discarded when this is a static lib + Command::registerAllCommands(); +} //-Instance Functions------------------------------------------------------------- //Private: -QString Driver::name() const { return NAME; } +QString DriverPrivate::name() const { return NAME; } -void Driver::init() +void DriverPrivate::init() { + Q_Q(Driver); // Create core, attach director to self - mCore = new Core(this); + mCore = std::make_unique(); Director* dtor = mCore->director(); setDirector(mCore->director()); //-Setup Core & Director--------------------------- - connect(mCore, &Core::abort, this, [this](CoreError err){ + QObject::connect(mCore.get(), &Core::abort, q, [this](CoreError err){ logEvent(LOG_EVENT_CORE_ABORT); mErrorStatus = err; quit(); }); - connect(dtor, &Director::announceAsyncDirective, this, &Driver::asyncDirectiveAccounced); - connect(dtor, &Director::announceSyncDirective, this, &Driver::syncDirectiveAccounced); + QObject::connect(dtor, &Director::announceAsyncDirective, q, &Driver::asyncDirectiveAccounced); + QObject::connect(dtor, &Director::announceSyncDirective, q, &Driver::syncDirectiveAccounced); //-Setup deferred process manager------ /* NOTE: It looks like the manager should just be a stack member of TExec that is constructed @@ -85,8 +93,10 @@ void Driver::init() TExec::installDeferredProcessManager(dpm); } -void Driver::startNextTask() +void DriverPrivate::startNextTask() { + Q_Q(Driver); + // Ensure tasks exist if(!mCore->hasTasks()) qFatal("Called with no tasks remaining."); @@ -103,33 +113,31 @@ void Driver::startNextTask() // Only execute task after an error/quit if it is a Shutdown task bool isShutdown = mCurrentTask->stage() == Task::Stage::Shutdown; - if(mErrorStatus.isSet() && !isShutdown) - { - logEvent(LOG_EVENT_TASK_SKIP_ERROR); + bool erroring = mErrorStatus.isSet(); + bool qutting = mQuitRequested; + bool skip = (erroring || qutting) && !isShutdown; - // Queue up finished handler directly (executes on next event loop cycle) since task was skipped - // Can't connect directly because newer connect syntax doesn't support default args - QTimer::singleShot(0, this, [this](){ completeTaskHandler(); }); - } - else if(mQuitRequested && !isShutdown) + if(skip) { - logEvent(LOG_EVENT_TASK_SKIP_QUIT); + logEvent(erroring ? LOG_EVENT_TASK_SKIP_ERROR : LOG_EVENT_TASK_SKIP_QUIT); // Queue up finished handler directly (executes on next event loop cycle) since task was skipped // Can't connect directly because newer connect syntax doesn't support default args - QTimer::singleShot(0, this, [this](){ completeTaskHandler(); }); + QTimer::singleShot(0, q, [this](){ completeTaskHandler(); }); } else { // QueuedConnection, allow event processing between tasks - connect(mCurrentTask, &Task::complete, this, &Driver::completeTaskHandler, Qt::QueuedConnection); + QObject::connect(mCurrentTask, &Task::complete, q, [this](const Qx::Error& e){ + completeTaskHandler(e); + }, Qt::QueuedConnection); // Perform task mCurrentTask->perform(); } } -void Driver::cleanup() +void DriverPrivate::cleanup() { logEvent(LOG_EVENT_CLEANUP_START); @@ -140,8 +148,10 @@ void Driver::cleanup() logEvent(LOG_EVENT_CLEANUP_FINISH); } -void Driver::finish() +void DriverPrivate::finish() { + Q_Q(Driver); + logEvent(LOG_EVENT_FINISH); // Clear update cache @@ -153,10 +163,10 @@ void Driver::finish() logEvent(LOG_EVENT_CLEARED_UPDATE_CACHE); } - emit finished(logFinish(mErrorStatus.value())); + emit q->finished(logFinish(mErrorStatus.value())); } -void Driver::quit() +void DriverPrivate::quit() { mQuitRequested = true; @@ -166,7 +176,7 @@ void Driver::quit() } // Helper functions -std::unique_ptr Driver::findFlashpointInstall() +std::unique_ptr DriverPrivate::findFlashpointInstall() { QDir currentDir(CLIFP_DIR_PATH); std::unique_ptr fpInstall; @@ -201,7 +211,7 @@ std::unique_ptr Driver::findFlashpointInstall() //-Slots-------------------------------------------------------------------------------- //Private: -void Driver::completeTaskHandler(Qx::Error e) +void DriverPrivate::completeTaskHandler(const Qx::Error& e) { // Handle errors if(e.isValid()) @@ -226,7 +236,7 @@ void Driver::completeTaskHandler(Qx::Error e) } //Public: -void Driver::drive() +void DriverPrivate::drive() { // Initialize init(); @@ -293,7 +303,7 @@ void Driver::drive() //-Catch early core errors------------------------------------------------------------------- QThread::msleep(100); - QApplication::processEvents(); + QCoreApplication::processEvents(); if(mErrorStatus.isSet()) { finish(); @@ -320,13 +330,13 @@ void Driver::drive() finish(); } -void Driver::cancelActiveLongTask() +void DriverPrivate::cancelActiveLongTask() { if(mCurrentTask) mCurrentTask->stop(); } -void Driver::quitNow() +void DriverPrivate::quitNow() { // Handle quit state if(mQuitRequested) @@ -338,3 +348,21 @@ void Driver::quitNow() logEvent(LOG_EVENT_QUIT_REQUEST); quit(); } + +//=============================================================================================================== +// Driver +//=============================================================================================================== + +//-Constructor-------------------------------------------------------------------- +//Public: +Driver::Driver(QStringList arguments) : d_ptr(std::make_unique(this, arguments)) {} + +//-Destructor-------------------------------------------------------------------- +//Public: +Driver::~Driver() = default; + +//-Slots-------------------------------------------------------------------------------- +//Public: +void Driver::drive() { Q_D(Driver); d->drive(); } +void Driver::cancelActiveLongTask() { Q_D(Driver); d->cancelActiveLongTask(); } +void Driver::quitNow() { Q_D(Driver); d->quitNow(); } diff --git a/app/src/kernel/driver.h b/lib/backend/src/kernel/driver_p.h similarity index 87% rename from app/src/kernel/driver.h rename to lib/backend/src/kernel/driver_p.h index ec968c2..5a5cb86 100644 --- a/app/src/kernel/driver.h +++ b/lib/backend/src/kernel/driver_p.h @@ -1,5 +1,5 @@ -#ifndef DRIVER_H -#define DRIVER_H +#ifndef DRIVER_P_H +#define DRIVER_P_H // Qt Includes #include @@ -11,11 +11,16 @@ // Project Includes #include "kernel/errorstatus.h" #include "kernel/directorate.h" -#include "kernel/core.h" + +//TODO: Use PIMPL so that this is the only class exposed in the lib + +class Driver; +class Core; +namespace Fp { class Install; } class QX_ERROR_TYPE(DriverError, "DriverError", 1202) { - friend class Driver; + friend class DriverPrivate; //-Class Enums------------------------------------------------------------- public: enum Type @@ -55,9 +60,9 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1202) QString deriveSecondary() const override; }; -class Driver : public QObject, public Directorate +class DriverPrivate : public Directorate { - Q_OBJECT + Q_DECLARE_PUBLIC(Driver); //-Class Variables------------------------------------------------------------------------------------------------------ private: // Error Messages @@ -90,10 +95,13 @@ class Driver : public QObject, public Directorate //-Instance Variables------------------------------------------------------------------------------------------------------------ private: + Driver* const q_ptr; QStringList mArguments; - - Core* mCore; // Must not be spawned during construction but after object is moved to thread and operated (since it uses signals/slots) + std::unique_ptr mCore; // Must not be spawned during construction but after object is moved to thread and operated (since it uses signals/slots) /* + * This note might be outdated, now that we're using PIMPL, as parenting the members to the public class might cause race conditions for signal disconnection + * purposes. + * * NOTE: The pointer members here could be on stack if they are assigned as children to this Driver instance in its initialization list, but at the moment using * pointers instead for simplicity. If set as a child of this instance, they will be moved with the instance automatically when the instance is moved to a separate * thread. Otherwise (and without using pointers and init during drive()), the connections they have will be invoked on the main thread since they are spawned when @@ -114,7 +122,7 @@ class Driver : public QObject, public Directorate //-Constructor------------------------------------------------------------------------------------------------- public: - Driver(QStringList arguments); + DriverPrivate(Driver* q, QStringList arguments); //-Instance Functions------------------------------------------------------------------------------------------------------------ private: @@ -125,6 +133,7 @@ class Driver : public QObject, public Directorate // Process void startNextTask(); + void completeTaskHandler(const Qx::Error& e = {}); void cleanup(); void finish(); @@ -133,25 +142,10 @@ class Driver : public QObject, public Directorate // Helper std::unique_ptr findFlashpointInstall(); -//-Signals & Slots------------------------------------------------------------------------------------------------------------ -private slots: - void completeTaskHandler(Qx::Error e = {}); - -public slots: - // Worker main +public: void drive(); - - // Termination void cancelActiveLongTask(); void quitNow(); - -signals: - // Worker status - void finished(ErrorCode errorCode); - - // Director forwarders - void asyncDirectiveAccounced(const AsyncDirective& aDirective); - void syncDirectiveAccounced(const SyncDirective& sDirective); }; -#endif // DRIVER_H +#endif // DRIVER_P_H diff --git a/app/src/kernel/errorstatus.cpp b/lib/backend/src/kernel/errorstatus.cpp similarity index 100% rename from app/src/kernel/errorstatus.cpp rename to lib/backend/src/kernel/errorstatus.cpp diff --git a/app/src/kernel/errorstatus.h b/lib/backend/src/kernel/errorstatus.h similarity index 100% rename from app/src/kernel/errorstatus.h rename to lib/backend/src/kernel/errorstatus.h diff --git a/app/src/task/t-awaitdocker.cpp b/lib/backend/src/task/t-awaitdocker.cpp similarity index 100% rename from app/src/task/t-awaitdocker.cpp rename to lib/backend/src/task/t-awaitdocker.cpp diff --git a/app/src/task/t-awaitdocker.h b/lib/backend/src/task/t-awaitdocker.h similarity index 100% rename from app/src/task/t-awaitdocker.h rename to lib/backend/src/task/t-awaitdocker.h diff --git a/app/src/task/t-bideprocess.cpp b/lib/backend/src/task/t-bideprocess.cpp similarity index 100% rename from app/src/task/t-bideprocess.cpp rename to lib/backend/src/task/t-bideprocess.cpp diff --git a/app/src/task/t-bideprocess.h b/lib/backend/src/task/t-bideprocess.h similarity index 100% rename from app/src/task/t-bideprocess.h rename to lib/backend/src/task/t-bideprocess.h diff --git a/app/src/task/t-download.cpp b/lib/backend/src/task/t-download.cpp similarity index 100% rename from app/src/task/t-download.cpp rename to lib/backend/src/task/t-download.cpp diff --git a/app/src/task/t-download.h b/lib/backend/src/task/t-download.h similarity index 100% rename from app/src/task/t-download.h rename to lib/backend/src/task/t-download.h diff --git a/app/src/task/t-exec.cpp b/lib/backend/src/task/t-exec.cpp similarity index 100% rename from app/src/task/t-exec.cpp rename to lib/backend/src/task/t-exec.cpp diff --git a/app/src/task/t-exec.h b/lib/backend/src/task/t-exec.h similarity index 100% rename from app/src/task/t-exec.h rename to lib/backend/src/task/t-exec.h diff --git a/app/src/task/t-exec_linux.cpp b/lib/backend/src/task/t-exec_linux.cpp similarity index 100% rename from app/src/task/t-exec_linux.cpp rename to lib/backend/src/task/t-exec_linux.cpp diff --git a/app/src/task/t-exec_win.cpp b/lib/backend/src/task/t-exec_win.cpp similarity index 100% rename from app/src/task/t-exec_win.cpp rename to lib/backend/src/task/t-exec_win.cpp diff --git a/app/src/task/t-extra.cpp b/lib/backend/src/task/t-extra.cpp similarity index 100% rename from app/src/task/t-extra.cpp rename to lib/backend/src/task/t-extra.cpp diff --git a/app/src/task/t-extra.h b/lib/backend/src/task/t-extra.h similarity index 100% rename from app/src/task/t-extra.h rename to lib/backend/src/task/t-extra.h diff --git a/app/src/task/t-extract.cpp b/lib/backend/src/task/t-extract.cpp similarity index 100% rename from app/src/task/t-extract.cpp rename to lib/backend/src/task/t-extract.cpp diff --git a/app/src/task/t-extract.h b/lib/backend/src/task/t-extract.h similarity index 100% rename from app/src/task/t-extract.h rename to lib/backend/src/task/t-extract.h diff --git a/app/src/task/t-generic.cpp b/lib/backend/src/task/t-generic.cpp similarity index 100% rename from app/src/task/t-generic.cpp rename to lib/backend/src/task/t-generic.cpp diff --git a/app/src/task/t-generic.h b/lib/backend/src/task/t-generic.h similarity index 100% rename from app/src/task/t-generic.h rename to lib/backend/src/task/t-generic.h diff --git a/app/src/task/t-message.cpp b/lib/backend/src/task/t-message.cpp similarity index 100% rename from app/src/task/t-message.cpp rename to lib/backend/src/task/t-message.cpp diff --git a/app/src/task/t-message.h b/lib/backend/src/task/t-message.h similarity index 100% rename from app/src/task/t-message.h rename to lib/backend/src/task/t-message.h diff --git a/app/src/task/t-mount.cpp b/lib/backend/src/task/t-mount.cpp similarity index 100% rename from app/src/task/t-mount.cpp rename to lib/backend/src/task/t-mount.cpp diff --git a/app/src/task/t-mount.h b/lib/backend/src/task/t-mount.h similarity index 100% rename from app/src/task/t-mount.h rename to lib/backend/src/task/t-mount.h diff --git a/app/src/task/t-sleep.cpp b/lib/backend/src/task/t-sleep.cpp similarity index 100% rename from app/src/task/t-sleep.cpp rename to lib/backend/src/task/t-sleep.cpp diff --git a/app/src/task/t-sleep.h b/lib/backend/src/task/t-sleep.h similarity index 100% rename from app/src/task/t-sleep.h rename to lib/backend/src/task/t-sleep.h diff --git a/app/src/task/task.cpp b/lib/backend/src/task/task.cpp similarity index 100% rename from app/src/task/task.cpp rename to lib/backend/src/task/task.cpp diff --git a/app/src/task/task.h b/lib/backend/src/task/task.h similarity index 98% rename from app/src/task/task.h rename to lib/backend/src/task/task.h index 9887822..32a7bfd 100644 --- a/app/src/task/task.h +++ b/lib/backend/src/task/task.h @@ -3,7 +3,6 @@ // Qt Includes #include -#include // Qx Includes #include diff --git a/app/src/tools/blockingprocessmanager.cpp b/lib/backend/src/tools/blockingprocessmanager.cpp similarity index 100% rename from app/src/tools/blockingprocessmanager.cpp rename to lib/backend/src/tools/blockingprocessmanager.cpp diff --git a/app/src/tools/blockingprocessmanager.h b/lib/backend/src/tools/blockingprocessmanager.h similarity index 100% rename from app/src/tools/blockingprocessmanager.h rename to lib/backend/src/tools/blockingprocessmanager.h diff --git a/app/src/tools/deferredprocessmanager.cpp b/lib/backend/src/tools/deferredprocessmanager.cpp similarity index 100% rename from app/src/tools/deferredprocessmanager.cpp rename to lib/backend/src/tools/deferredprocessmanager.cpp diff --git a/app/src/tools/deferredprocessmanager.h b/lib/backend/src/tools/deferredprocessmanager.h similarity index 100% rename from app/src/tools/deferredprocessmanager.h rename to lib/backend/src/tools/deferredprocessmanager.h diff --git a/app/src/tools/mounter_game_server.cpp b/lib/backend/src/tools/mounter_game_server.cpp similarity index 100% rename from app/src/tools/mounter_game_server.cpp rename to lib/backend/src/tools/mounter_game_server.cpp diff --git a/app/src/tools/mounter_game_server.h b/lib/backend/src/tools/mounter_game_server.h similarity index 100% rename from app/src/tools/mounter_game_server.h rename to lib/backend/src/tools/mounter_game_server.h diff --git a/app/src/tools/mounter_qmp.cpp b/lib/backend/src/tools/mounter_qmp.cpp similarity index 100% rename from app/src/tools/mounter_qmp.cpp rename to lib/backend/src/tools/mounter_qmp.cpp diff --git a/app/src/tools/mounter_qmp.h b/lib/backend/src/tools/mounter_qmp.h similarity index 92% rename from app/src/tools/mounter_qmp.h rename to lib/backend/src/tools/mounter_qmp.h index 69c067d..0d7a963 100644 --- a/app/src/tools/mounter_qmp.h +++ b/lib/backend/src/tools/mounter_qmp.h @@ -18,7 +18,7 @@ class QX_ERROR_TYPE(MounterQmpError, "MounterQmpError", 1233) { friend class MounterQmp; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -28,7 +28,7 @@ class QX_ERROR_TYPE(MounterQmpError, "MounterQmpError", 1233) QemuCommand }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, @@ -37,16 +37,16 @@ class QX_ERROR_TYPE(MounterQmpError, "MounterQmpError", 1233) {QemuCommand, u"QMPI command error."_s}, }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: MounterQmpError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; diff --git a/app/src/tools/mounter_router.cpp b/lib/backend/src/tools/mounter_router.cpp similarity index 100% rename from app/src/tools/mounter_router.cpp rename to lib/backend/src/tools/mounter_router.cpp diff --git a/app/src/tools/mounter_router.h b/lib/backend/src/tools/mounter_router.h similarity index 88% rename from app/src/tools/mounter_router.h rename to lib/backend/src/tools/mounter_router.h index a406777..ba41167 100644 --- a/app/src/tools/mounter_router.h +++ b/lib/backend/src/tools/mounter_router.h @@ -17,7 +17,7 @@ class QX_ERROR_TYPE(MounterRouterError, "MounterRouterError", 1234) { friend class MounterRouter; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -25,23 +25,23 @@ class QX_ERROR_TYPE(MounterRouterError, "MounterRouterError", 1234) Failed = 1 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, {Failed, u"Failed to mount data pack via router."_s}, }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: MounterRouterError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; diff --git a/app/src/utility.h b/lib/backend/src/utility.h similarity index 83% rename from app/src/utility.h rename to lib/backend/src/utility.h index 3ae94cb..3362f22 100644 --- a/app/src/utility.h +++ b/lib/backend/src/utility.h @@ -19,10 +19,4 @@ #define CLIFP_CANONICAL_APP_FILNAME u"clifp"_s #endif -namespace Utility -{ -//-Functions------------------------------------ -const QIcon& appIconFromResources(); -bool installAppIconForUser(); -} #endif // UTILITY_H diff --git a/lib/frontend_framework/CMakeLists.txt b/lib/frontend_framework/CMakeLists.txt new file mode 100644 index 0000000..a3ede88 --- /dev/null +++ b/lib/frontend_framework/CMakeLists.txt @@ -0,0 +1,45 @@ +# Add via ob standard object library +include(OB/Library) + +# Originally was gonna do this with an OBJECT library, for a lightly faster compilation, +# but because this includes a header it causes some wack with MOC and redefinitions, +# as MOC compiles the header file twice: here, and by consumers + +ob_add_standard_library(${FRONTEND_FRAMEWORK_TARGET_NAME} + NAMESPACE "${PROJECT_NAMESPACE}" + ALIAS "${FRONTEND_FRAMEWORK_ALIAS_NAME}" + TYPE "STATIC" + HEADERS_API + FILES + frontend/framework.h + IMPLEMENTATION + frontend/framework.cpp + frontend/framework_linux.cpp + RESOURCE "frontend_framework.qrc" + LINKS + PUBLIC + CLIFp::Backend + ${Qt}::Gui # Needed for QIcon, silly, but technically not an issue +) + +# Include ICO hash in the following on Linux +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + include(FrontendFramework) + app_ico_hash(ico_hash) + set(COND_ICO_HASH "APP_ICO_HASH" "\"${ico_hash}\"") +else() + set(COND_ICO_HASH "") +endif() + + +## Forward select project variables to C++ code +include(OB/CppVars) +ob_add_cpp_vars(${FRONTEND_FRAMEWORK_TARGET_NAME} + NAME "_frontend_project_vars" + PREFIX "PROJECT_" + VARS + VERSION_STR "\"${PROJECT_VERSION_VERBOSE}\"" + APP_NAME "\"${PROJECT_FORMAL_NAME}\"" + SHORT_NAME "\"${PROJECT_NAME}\"" + ${COND_ICO_HASH} +) diff --git a/lib/frontend_framework/cmake/FrontendFramework.cmake b/lib/frontend_framework/cmake/FrontendFramework.cmake new file mode 100644 index 0000000..744c687 --- /dev/null +++ b/lib/frontend_framework/cmake/FrontendFramework.cmake @@ -0,0 +1,21 @@ +function(set_clip_exe_details tgt name) + include(OB/WinExecutableDetails) + ob_set_win_executable_details(${tgt} + ICON "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../res/app/CLIFp.ico" + FILE_VER ${PROJECT_VERSION} + PRODUCT_VER ${TARGET_FP_VERSION_PREFIX} + COMPANY_NAME "oblivioncth" + FILE_DESC "CLI for Flashpoint Archive" + INTERNAL_NAME "${name}" + COPYRIGHT "Open Source @ 2024 oblivioncth" + TRADEMARKS_ONE "All Rights Reserved" + TRADEMARKS_TWO "GNU AGPL V3" + ORIG_FILENAME "${name}.exe" + PRODUCT_NAME "${PROJECT_FORMAL_NAME}" + ) +endfunction() + +function(app_ico_hash return) + file(SHA1 "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../res/app/CLIFp.ico" hash) + set(${return} ${hash} PARENT_SCOPE) +endfunction() diff --git a/lib/frontend_framework/include/frontend/framework.h b/lib/frontend_framework/include/frontend/framework.h new file mode 100644 index 0000000..bcc785e --- /dev/null +++ b/lib/frontend_framework/include/frontend/framework.h @@ -0,0 +1,98 @@ +#ifndef FRAMEWORK_H +#define FRAMEWORK_H + +// Qt Includes +#include +#include +#include + +// Project Includes +#include "kernel/errorcode.h" +#include "kernel/directive.h" + +class QGuiApplication; +class Driver; + +class SafeDriver +{ + /* This whole class exists to allow Framework to control Driver without using signals, safely. + * Call it overkill, but it makes Framework itself cleaner overall. + * + * Specifically, this ensure that the Driver pointer is valid and only allows using it to invoke + * methods in drivers thread, which prevents accidental direct method use in Framework's thread. + */ +private: + QPointer mDriver; + +public: + SafeDriver(Driver* driver = nullptr); + + // This signature needs to be tweaked if we ever need the version that can pass method arguments added in Qt 6.7 + template + bool invokeMethod(Functor&& function, FunctorReturnType* ret = nullptr); + SafeDriver& operator=(Driver* driver); +}; + +class FrontendFramework : public QObject +{ + Q_OBJECT +//-Instance Variables---------------------------------------------------------------------------------------------- +private: + QThread mWorkerThread; + QGuiApplication* mApp; + SafeDriver mDriver; + std::optional mExitCode; + +//-Constructor------------------------------------------------------------------------------------------------------- +public: + explicit FrontendFramework(QGuiApplication* app); // Can be QCoreApplication* if dependency on QGui for QIcon et. al. is ever broken + +//-Destructor---------------------------------------------------------------------------------------------------------- +public: + virtual ~FrontendFramework(); + +//-Class Functions--------------------------------------------------------------------------------------------------------- +protected: +#ifdef __linux__ + static bool updateUserIcons(); +#endif + static const QIcon& appIconFromResources(); + +//-Instance Functions------------------------------------------------------------------------------------------------------ +protected: + // Async directive handlers + virtual void handleMessage(const DMessage& d) = 0; + virtual void handleError(const DError& d) = 0; + virtual void handleProcedureStart(const DProcedureStart& d) = 0; + virtual void handleProcedureStop(const DProcedureStop& d) = 0; + virtual void handleProcedureProgress(const DProcedureProgress& d) = 0; + virtual void handleProcedureScale(const DProcedureScale& d) = 0; + virtual void handleClipboardUpdate(const DClipboardUpdate& d) = 0; + virtual void handleStatusUpdate(const DStatusUpdate& d) = 0; + + // Sync directive handlers + virtual void handleBlockingMessage(const DBlockingMessage& d) = 0; + virtual void handleBlockingError(const DBlockingError& d) = 0; + virtual void handleSaveFilename(const DSaveFilename& d) = 0; + virtual void handleExistingDir(const DExistingDir& d) = 0; + virtual void handleItemSelection(const DItemSelection& d) = 0; + virtual void handleYesOrNo(const DYesOrNo& d) = 0; + + // Control + virtual bool aboutToExit(); + void shutdownDriver(); + void cancelDriverTask(); + bool readyToExit() const; + void exit(); + +public: + ErrorCode exec(); + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +private slots: + void threadFinishHandler(); + void asyncDirectiveHandler(const AsyncDirective& aDirective); + void syncDirectiveHandler(const SyncDirective& sDirective); +}; + +#endif // FRAMEWORK_H diff --git a/lib/frontend_framework/res/app/CLIFp.ico b/lib/frontend_framework/res/app/CLIFp.ico new file mode 100644 index 0000000..43a609b Binary files /dev/null and b/lib/frontend_framework/res/app/CLIFp.ico differ diff --git a/lib/frontend_framework/res/frontend_framework.qrc b/lib/frontend_framework/res/frontend_framework.qrc new file mode 100644 index 0000000..73ca77a --- /dev/null +++ b/lib/frontend_framework/res/frontend_framework.qrc @@ -0,0 +1,5 @@ + + + app/CLIFp.ico + + diff --git a/lib/frontend_framework/src/frontend/framework.cpp b/lib/frontend_framework/src/frontend/framework.cpp new file mode 100644 index 0000000..06a336c --- /dev/null +++ b/lib/frontend_framework/src/frontend/framework.cpp @@ -0,0 +1,171 @@ +// Unit Include +#include "frontend/framework.h" + +// Qt Includes +#include +#include + +// Qx Includes +#include + +// Project Includes +#include "kernel/driver.h" +#include "_frontend_project_vars.h" + +//=============================================================================================================== +// SafeDriver +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Public: +SafeDriver::SafeDriver(Driver* driver) : mDriver(driver){} + +//-Instance Functions------------------------------------------------------ +//Public: +template +bool SafeDriver::invokeMethod(Functor&& function, FunctorReturnType* ret) +{ + if(!mDriver) + { + qWarning("Tried using driver when it does not exist!"); + return false; + } + + return QMetaObject::invokeMethod(mDriver, function, Qt::QueuedConnection, ret); +} + +SafeDriver& SafeDriver::operator=(Driver* driver) { mDriver = driver; return *this; } + +//=============================================================================================================== +// Framework +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Public: +FrontendFramework::FrontendFramework(QGuiApplication* app) : + mApp(app), + mExitCode(0) +{ + /* Turns out when embedding a qrc into static lib, this call is needed to load it. Not sure if this macro + * must be used in code called directly by the application, or if it can be in purely library code, but + * it works in this case where the base class of classes derived in application code calls it. + */ + Q_INIT_RESOURCE(frontend_framework); + + // Prepare app + mApp->setApplicationName(PROJECT_APP_NAME); + mApp->setApplicationVersion(PROJECT_VERSION_STR); + + // Register metatypes + qRegisterMetaType(); + qRegisterMetaType(); + + // Create driver + Driver* driver = new Driver(mApp->arguments()); + driver->moveToThread(&mWorkerThread); + + // Connect driver - Operation + connect(&mWorkerThread, &QThread::started, driver, &Driver::drive); // Thread start causes driver start + connect(driver, &Driver::finished, this, [this](ErrorCode ec){ mExitCode = ec; }); // Result handling + connect(driver, &Driver::finished, driver, &QObject::deleteLater); // Have driver clean up itself + connect(driver, &Driver::finished, &mWorkerThread, &QThread::quit); // Have driver finish cause thread finish + connect(&mWorkerThread, &QThread::finished, this, &FrontendFramework::threadFinishHandler); // Start execution finish when thread quits + + // Connect driver - Directives + connect(driver, &Driver::asyncDirectiveAccounced, this, &FrontendFramework::asyncDirectiveHandler); + connect(driver, &Driver::syncDirectiveAccounced, this, &FrontendFramework::syncDirectiveHandler, Qt::BlockingQueuedConnection); + + // Store driver for use later + mDriver = driver; + +#ifdef __linux__ + if(!updateUserIcons()) + qWarning("Failed to upate user app icons!"); +#endif +} + +//-Destructor------------------------------------------------------------- +//Public: +FrontendFramework::~FrontendFramework() +{ + // Just to be safe, but never should be the case + if(mWorkerThread.isRunning()) + { + mWorkerThread.quit(); + mWorkerThread.wait(); + } + + Q_CLEANUP_RESOURCE(frontend_framework); // Not sure if needed +} + +//-Class Functions----------------------------------------------------------------------------- +//Protected: +const QIcon& FrontendFramework::appIconFromResources() { static QIcon ico(u":/frontend/app/CLIFp.ico"_s); return ico; } + +//-Instance Functions-------------------------------------------------------------------------- +//Protected: +bool FrontendFramework::aboutToExit() +{ + /* Derivatives can override this to do things last minute, and return true, + * or they can return false in order to delay exit until they manually call exit() + */ + return true; +} + +void FrontendFramework::shutdownDriver() { mDriver.invokeMethod(&Driver::quitNow); } +void FrontendFramework::cancelDriverTask() { mDriver.invokeMethod(&Driver::cancelActiveLongTask); } + +bool FrontendFramework::readyToExit() const { return mExitCode && mWorkerThread.isFinished(); } + +void FrontendFramework::exit() +{ + // Ignore if driver thread is not finished + if(!readyToExit()) + return; + + mApp->exit(*mExitCode); +} + +//Public: +ErrorCode FrontendFramework::exec() +{ + // Run thread + mWorkerThread.start(); + + // Loop until quit + return mApp->exec(); +} + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +//Private Slots: +void FrontendFramework::threadFinishHandler() +{ + if(aboutToExit()) + exit(); +} + +void FrontendFramework::asyncDirectiveHandler(const AsyncDirective& aDirective) +{ + std::visit(qxFuncAggregate{ + [this](DMessage d){ handleMessage(d); }, + [this](DError d) { handleError(d); }, + [this](DProcedureStart d) { handleProcedureStart(d); }, + [this](DProcedureStop d) { handleProcedureStop(d); }, + [this](DProcedureProgress d) { handleProcedureProgress(d); }, + [this](DProcedureScale d) { handleProcedureScale(d); }, + [this](DClipboardUpdate d) { handleClipboardUpdate(d); }, + [this](DStatusUpdate d) { handleStatusUpdate(d); }, + }, aDirective); +} + +void FrontendFramework::syncDirectiveHandler(const SyncDirective& sDirective) +{ + std::visit(qxFuncAggregate{ + [this](DBlockingMessage d){ handleBlockingMessage(d); }, + [this](DBlockingError d){ handleBlockingError(d); }, + [this](DSaveFilename d) { handleSaveFilename(d); }, + [this](DExistingDir d) { handleExistingDir(d); }, + [this](DItemSelection d) { handleItemSelection(d); }, + [this](DYesOrNo d) { handleYesOrNo(d); } + }, sDirective); +} diff --git a/lib/frontend_framework/src/frontend/framework_linux.cpp b/lib/frontend_framework/src/frontend/framework_linux.cpp new file mode 100644 index 0000000..0f6a057 --- /dev/null +++ b/lib/frontend_framework/src/frontend/framework_linux.cpp @@ -0,0 +1,73 @@ +// Unit Include +#include "frontend/framework.h" + +// Qt Includes +#include +#include +#include +#include +#include + +// Project Includes +#include "_frontend_project_vars.h" + +namespace +{ + +const QString dimStr(int w, int h) +{ + static const QString dimTemplate = u"%1x%2"_s; + return dimTemplate.arg(w).arg(h); +} + +} + +//=============================================================================================================== +// Framework +//=============================================================================================================== + +//-Class Functions----------------------------------------------------------------------------- +//Protected: +bool FrontendFramework::updateUserIcons() +{ + static const QString HASH_KEY = u"CLIFP_ICO_HASH"_s; + static const QDir ICON_DEST_BASE_DIR(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + u"/icons/hicolor"_s); + + QImageReader imgReader; + QImageWriter imgWriter; + + const QIcon& appIcon = appIconFromResources(); + for(const QSize& size : appIcon.availableSizes()) + { + QString resSpecificSubPath = dimStr(size.width(), size.height()) + u"/apps"_s; + + // Ensure path exists + ICON_DEST_BASE_DIR.mkpath(u"./"_s + resSpecificSubPath); + + // Determine dest + QFile destFile(ICON_DEST_BASE_DIR.absolutePath() + '/' + resSpecificSubPath + '/' + PROJECT_SHORT_NAME + u".png"_s); + + if(destFile.exists()) + { + // Check if file seems up-to-date + imgReader.setDevice(&destFile); + if(imgReader.text(HASH_KEY) == PROJECT_APP_ICO_HASH) + continue; + + // Remove old file + if(!destFile.remove()) + return false; + } + + // Synthesize specifc size image + QImage img = appIcon.pixmap(size).toImage(); + + // Write image + imgWriter.setDevice(&destFile); + imgWriter.setText(HASH_KEY, PROJECT_APP_ICO_HASH); // I think we have to do this each time after changing the file + if(!imgWriter.write(img)) + return false; + } + + return true; +}