diff --git a/.github/workflows/master-pull-request-merge-reaction.yml b/.github/workflows/master-pull-request-merge-reaction.yml index 77bfe55..6bff98e 100644 --- a/.github/workflows/master-pull-request-merge-reaction.yml +++ b/.github/workflows/master-pull-request-merge-reaction.yml @@ -68,6 +68,30 @@ jobs: uses: ./.github/workflows/build-clifp-linux.yml secrets: qt_ffynnon_cred: ${{ secrets.OBYBOT_FFYNNON_CREDS }} + + update-redirect: + name: Update redirect page + if: github.event.pull_request.merged == true + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.page-deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Setup pages + uses: actions/configure-pages@v3 + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v1 + with: + path: ${{ github.workspace }}/redirector + - name: Deploy pages artifact + id: page-deployment + uses: actions/deploy-pages@v1 create-release: name: Create GitHub release diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f10810..26bc418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "v0.5.3" + REF "b46a1e011a1447c3abee92e0a433d7290d6bcc59" COMPONENTS ${CLIFP_QX_COMPONENTS} ) diff --git a/README.md b/README.md index c619c93..3b4c789 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,28 @@ If the application needs to use files from a Data Pack that pack will need to be The applications and arguments that are used for each game/animation can be found within the Flashpoint database ([FP Install Dir]\Data\flashpoint.sqlite) +### Flashpoint Protocol +CLIFp supports the "flashpoint" protocol, which means it can launch titles through URL with a custom scheme, followed by a title's UUID, like this: + + flashpoint://37e5c215-9c39-4a3d-9912-b4343a17027e + +This makes it fast and easy to share games that you think your friends should try out. + +To register CLIFp as the handler for these URLs, simply run: + + CLIFp share -c + +To easily create a share link if you don't already know the UUID of a game, you can use the **share** command with the **-t** switch followed by a title: + + CLIFp share -t "Simple, Tasty Buttons" + +This will create a share link for that title which will be displayed via a message box and automatically copied to the system clipboard. + +If for whatever reason the service through which you wish to share a link does not support links with custom schemes, you can use the **-u** switch to generate a standard "https" link that utilizes a GitHub-hosted redirect page, enabling share links to be provided everywhere. + +[!IMPORTANT] +You will want to disable the "Register As Protocol Handler" option in the default launcher or else it will replace CLIFp as the "flashpoint" protocol handler every time it's started (**NOTE: This option is not currently available in a released version of Flashpoint**). + ## All Commands/Options The recommended way to use all switches is to use their short form when the value for the switch has no spaces: @@ -190,6 +212,29 @@ When using the **--exe** and **--param** switches all quotes that are part of th See http://www.robvanderwoude.com/escapechars.php for more information. +-------------------------------------------------------------------------------- + +**share** - Generates a URL for starting a Flashpoint title that can be shared to other users. + +Options: + - **-i | --id:** UUID of title to make a share link for + - **-t | --title:** Title to make a share link for + - **-T | --title-strict:** Same as **-t**, but only exact matches are considered + - **-s | --subtitle:** Name of additional-app under the title to make a share link for. Must be used with **-t**/**-T** + - **-S | --subtitle-strict:** Same as **-s**, but only exact matches are considered + - **-u | --universal:** Creates a standard HTTPS link that utilizes a redirect page. May be easier to share on some platforms. + - **-c | --configure:** Registers CLIFp as the default handler for "flashpoint" protocol links. + - **-C | --unconfigure:** Removes CLIFp as the default handler for "flashpoint" protocol links. + - **-h | --help | -?:** Prints command specific usage information + +Requires: +**-i**, **-t** or **-c** + +Notes: + + - By default, the standard Flashpoint launcher is registered to handle share links; therefore, its "Register As Protocol Handler" option should likely be disabled if you intend to use CLIFp instead. + - See the **play** command notes for information regarding the **t**/**T** and **s**/**S** switches. + -------------------------------------------------------------------------------- **show** - Display a message or extra folder @@ -203,7 +248,7 @@ Requires: **-m** or **-e** ### Remarks -With any use of the **--title** option for the commands that support it the title must be entered verbatim as it appears within Flashpoint, as close matches are not checked (due to technical limitations). If two entries happen to share the title specified, a dialog window with more information will be displayed so that the intended title can be selected. +With any use of the **--title**/**--subtitle** options for the commands that support them, the provided title should match as closely as possible to how it appears within Flashpoint, as checks for close matches are limited due to technical restrictions. If more than one entry is found, a dialog window with more information will be displayed so that the intended title can be selected. ## Other Features CLIFp displays a system tray icon so that one can be sure it is still running. This icon also will display basic status messages when clicked on and features a context menu with an option to exit at any time. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6000ad6..cdbf01f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -18,6 +18,8 @@ set(CLIFP_SOURCE 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/title-command.h diff --git a/app/src/command/c-link.h b/app/src/command/c-link.h index bdf66d0..3779dce 100644 --- a/app/src/command/c-link.h +++ b/app/src/command/c-link.h @@ -7,7 +7,7 @@ // Project Includes #include "command/title-command.h" -class QX_ERROR_TYPE(CLinkError, "CLinkError", 1212) +class QX_ERROR_TYPE(CLinkError, "CLinkError", 1213) { friend class CLink; //-Class Enums------------------------------------------------------------- diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index 096e905..092dca9 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -12,6 +12,29 @@ #include "../task/t-message.h" #include "../task/t-extra.h" +//=============================================================================================================== +// CPlayError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +CPlayError::CPlayError(Type t, const QString& s) : + mType(t), + mSpecific(s) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool CPlayError::isValid() const { return mType != NoError; } +QString CPlayError::specific() const { return mSpecific; } +CPlayError::Type CPlayError::type() const { return mType; } + +//Private: +Qx::Severity CPlayError::deriveSeverity() const { return Qx::Critical; } +quint32 CPlayError::deriveValue() const { return mType; } +QString CPlayError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString CPlayError::deriveSecondary() const { return mSpecific; } + //=============================================================================================================== // CPLAY //=============================================================================================================== @@ -262,15 +285,29 @@ Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, } //Protected: +QList CPlay::options() { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } QString CPlay::name() { return NAME; } Qx::Error CPlay::perform() { - // Get ID of title to start + // Get ID of title to start, prioritizing URL QUuid titleId; - if(Qx::Error ide = getTitleId(titleId); ide.isValid()) + if(mParser.isSet(CL_OPTION_URL)) + { + QRegularExpressionMatch urlMatch = URL_REGEX.match(mParser.value(CL_OPTION_URL)); + if(!urlMatch.hasMatch()) + { + CPlayError err(CPlayError::InvalidUrl); + mCore.postError(NAME, err); + return err; + } + + titleId = QUuid(urlMatch.captured(u"id"_s)); + } + else if(Qx::Error ide = getTitleId(titleId); ide.isValid()) return ide; + Qx::Error errorStatus; // Enqueue required tasks diff --git a/app/src/command/c-play.h b/app/src/command/c-play.h index 4dcaeb3..6c78d5d 100644 --- a/app/src/command/c-play.h +++ b/app/src/command/c-play.h @@ -7,6 +7,46 @@ // Project Includes #include "command/title-command.h" +class QX_ERROR_TYPE(CPlayError, "CPlayError", 1212) +{ + friend class CPlay; + //-Class Enums------------------------------------------------------------- +public: + enum Type + { + NoError = 0, + InvalidUrl = 1, + }; + + //-Class Variables------------------------------------------------------------- +private: + static inline const QHash ERR_STRINGS{ + {NoError, u""_s}, + {InvalidUrl, u"The provided 'flashpoint://' scheme URL is invalid."_s} + }; + + //-Instance Variables------------------------------------------------------------- +private: + Type mType; + QString mSpecific; + + //-Constructor------------------------------------------------------------- +private: + CPlayError(Type t = NoError, const QString& s = {}); + + //-Instance Functions------------------------------------------------------------- +public: + bool isValid() const; + Type type() const; + QString specific() const; + +private: + Qx::Severity deriveSeverity() const override; + quint32 deriveValue() const override; + QString derivePrimary() const override; + QString deriveSecondary() const override; +}; + class CPlay : public TitleCommand { //-Class Variables------------------------------------------------------------------------------------------------------ @@ -14,8 +54,23 @@ class CPlay : public TitleCommand // Status static inline const QString STATUS_PLAY = u"Playing"_s; + // General + static inline const QRegularExpression URL_REGEX = QRegularExpression( + u"flashpoint:\\/\\/(?[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12})\\/?$"_s + ); + + // Command line option strings + static inline const QString CL_OPT_URL_S_NAME = u"u"_s; + static inline const QString CL_OPT_URL_L_NAME = u"url"_s; + static inline const QString CL_OPT_URL_DESC = u""_s; + + // Command line options + static inline const QCommandLineOption CL_OPTION_URL{{CL_OPT_URL_S_NAME, CL_OPT_URL_L_NAME}, CL_OPT_URL_DESC, u"url"_s}; // Takes value + static inline const QList CL_OPTIONS_SPECIFIC{&CL_OPTION_URL}; + // Logging - Messages static inline const QString LOG_EVENT_ENQ_AUTO = u"Enqueuing automatic tasks..."_s; + static inline const QString LOG_EVENT_URL_ID = u"ID from URL: %1"_s; static inline const QString LOG_EVENT_ID_MATCH_TITLE = u"ID matches main title: %1"_s; static inline const QString LOG_EVENT_ID_MATCH_ADDAPP = u"ID matches additional app: %1 (Child of %2)"_s; static inline const QString LOG_EVENT_QUEUE_CLEARED = u"Previous queue entries cleared due to auto task being a Message/Extra"_s; @@ -45,6 +100,7 @@ class CPlay : public TitleCommand Qx::Error enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage); protected: + QList options() override; QString name() override; Qx::Error perform() override; }; diff --git a/app/src/command/c-share.cpp b/app/src/command/c-share.cpp new file mode 100644 index 0000000..8a8c784 --- /dev/null +++ b/app/src/command/c-share.cpp @@ -0,0 +1,117 @@ +// Unit Include +#include "c-share.h" + +// Qx Includes +#include + +// Project Includes +#include "task/t-message.h" +#include "utility.h" + +//=============================================================================================================== +// CShareError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +CShareError::CShareError(Type t, const QString& s) : + mType(t), + mSpecific(s) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool CShareError::isValid() const { return mType != NoError; } +QString CShareError::specific() const { return mSpecific; } +CShareError::Type CShareError::type() const { return mType; } + +//Private: +Qx::Severity CShareError::deriveSeverity() const { return Qx::Critical; } +quint32 CShareError::deriveValue() const { return mType; } +QString CShareError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString CShareError::deriveSecondary() const { return mSpecific; } + +//=============================================================================================================== +// CShare +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Public: +CShare::CShare(Core& coreRef) : TitleCommand(coreRef) {} + +//-Instance Functions------------------------------------------------------------- +//Protected: +QList CShare::options() { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } +QString CShare::name() { return NAME; } + +Qx::Error CShare::perform() +{ + // Prioritize scheme (un)registration + if(mParser.isSet(CL_OPTION_CONFIGURE)) + { + mCore.logEvent(NAME, LOG_EVENT_REGISTRATION); + + if(!Qx::setDefaultProtocolHandler(SCHEME, SCHEME_NAME, CLIFP_PATH, {u"play"_s, u"-u"_s})) + { + CShareError err(CShareError::RegistrationFailed); + mCore.postError(NAME, err); + return err; + } + + // Enqueue success message task + TMessage* successMsg = new TMessage(&mCore); + successMsg->setStage(Task::Stage::Primary); + successMsg->setText(MSG_REGISTRATION_COMPLETE); + mCore.enqueueSingleTask(successMsg); + + return CShareError(); + } + else if(mParser.isSet(CL_OPTION_UNCONFIGURE)) + { + mCore.logEvent(NAME, LOG_EVENT_UNREGISTRATION); + +#ifdef __linux__ // Function is too jank on linux right now, so always fail/no-op there + if(true) +#else + if(!Qx::removeDefaultProtocolHandler(SCHEME, CLIFP_PATH)) +#endif + { + CShareError err(CShareError::UnregistrationFailed); + mCore.postError(NAME, err); + return err; + } + + // Enqueue success message task + TMessage* successMsg = new TMessage(&mCore); + successMsg->setStage(Task::Stage::Primary); + successMsg->setText(MSG_UNREGISTRATION_COMPLETE); + mCore.enqueueSingleTask(successMsg); + + return CShareError(); + } + + // Get ID of title to share + QUuid shareId; + if(Qx::Error ide = getTitleId(shareId); ide.isValid()) + return ide; + + mCore.setStatus(STATUS_SHARE, shareId.toString(QUuid::WithoutBraces)); + + // Generate URL + QString idStr = shareId.toString(QUuid::WithoutBraces); + QString shareUrl = mParser.isSet(CL_OPTION_UNIVERSAL) ? SCHEME_TEMPLATE_UNI.arg(idStr) : SCHEME_TEMPLATE_STD.arg(idStr); + mCore.logEvent(NAME, LOG_EVENT_URL.arg(shareUrl)); + + // Add URL to clipboard + mCore.requestClipboardUpdate(shareUrl); + + // Enqueue message task + TMessage* urlMsg = new TMessage(&mCore); + urlMsg->setStage(Task::Stage::Primary); + urlMsg->setText(MSG_GENERATED_URL.arg(shareUrl)); + urlMsg->setSelectable(true); + mCore.enqueueSingleTask(urlMsg); + + // Return success + return Qx::Error(); +} diff --git a/app/src/command/c-share.h b/app/src/command/c-share.h new file mode 100644 index 0000000..c7196e3 --- /dev/null +++ b/app/src/command/c-share.h @@ -0,0 +1,109 @@ +#ifndef CSHARE_H +#define CSHARE_H + +// Project Includes +#include "command/title-command.h" + +class QX_ERROR_TYPE(CShareError, "CShareError", 1216) +{ + friend class CShare; + //-Class Enums------------------------------------------------------------- +public: + enum Type + { + NoError = 0, + RegistrationFailed = 1, + UnregistrationFailed = 2 + }; + + //-Class Variables------------------------------------------------------------- +private: + static inline const QHash ERR_STRINGS{ + {NoError, u""_s}, + {RegistrationFailed, u"Failed to register CLIFp as the 'flashpoint' scheme handler."_s}, + {UnregistrationFailed, u"Failed to remove CLIFp as the 'flashpoint' scheme handler."_s} + }; + + //-Instance Variables------------------------------------------------------------- +private: + Type mType; + QString mSpecific; + + //-Constructor------------------------------------------------------------- +private: + CShareError(Type t = NoError, const QString& s = {}); + + //-Instance Functions------------------------------------------------------------- +public: + bool isValid() const; + Type type() const; + QString specific() const; + +private: + Qx::Severity deriveSeverity() const override; + quint32 deriveValue() const override; + QString derivePrimary() const override; + QString deriveSecondary() const override; +}; + +class CShare : public TitleCommand +{ +//-Class Variables------------------------------------------------------------------------------------------------------ +private: + // Status + static inline const QString STATUS_SHARE = u"Sharing"_s; + + // General + static inline const QString SCHEME_TEMPLATE_STD = u"flashpoint://%1"_s; + static inline const QString SCHEME_TEMPLATE_UNI = u"https://oblivioncth.github.io/CLIFp/redirect.html?uuid=%1"_s; + static inline const QString SCHEME = u"flashpoint"_s; + static inline const QString SCHEME_NAME = u"Flashpoint (via CLIFp)"_s; + + // Messages + static inline const QString MSG_REGISTRATION_COMPLETE = u"Successfully registered CLIFp to respond to 'flashpoint://' requests."_s; + static inline const QString MSG_UNREGISTRATION_COMPLETE = u"Successfully removed CLIFp as the 'flashpoint://' request handler."_s; + static inline const QString MSG_GENERATED_URL = u"Share URL placed in clipboard:\n\n%1"_s; + + // Logging - Messages + static inline const QString LOG_EVENT_REGISTRATION = u"Registering CLIFp to handle flashpoint protocol links..."_s; + static inline const QString LOG_EVENT_UNREGISTRATION = u"Removing CLIFp as the flashpoint protocol link handler..."_s; + static inline const QString LOG_EVENT_URL = u"Share URL generated: %1"_s; + + // Command line option strings + static inline const QString CL_OPT_CONFIGURE_S_NAME = u"c"_s; + static inline const QString CL_OPT_CONFIGURE_L_NAME = u"configure"_s; + static inline const QString CL_OPT_CONFIGURE_DESC = u"Registers CLIFp at its current location to handle 'flashpoint://' links."_s; + + static inline const QString CL_OPT_UNCONFIGURE_S_NAME = u"C"_s; + static inline const QString CL_OPT_UNCONFIGURE_L_NAME = u"unconfigure"_s; + static inline const QString CL_OPT_UNCONFIGURE_DESC = u"Unregisters CLIFp as the 'flashpoint://' link handler if registered."_s; + + static inline const QString CL_OPT_UNIVERSAL_S_NAME = u"u"_s; + static inline const QString CL_OPT_UNIVERSAL_L_NAME = u"universal"_s; + static inline const QString CL_OPT_UNIVERSAL_DESC = u"Creates a share URL that utilizes an https redirect for increased portability."_s; + + // Command line options + static inline const QCommandLineOption CL_OPTION_CONFIGURE{{CL_OPT_CONFIGURE_S_NAME, CL_OPT_CONFIGURE_L_NAME}, CL_OPT_CONFIGURE_DESC}; // Boolean + static inline const QCommandLineOption CL_OPTION_UNCONFIGURE{{CL_OPT_UNCONFIGURE_S_NAME, CL_OPT_UNCONFIGURE_L_NAME}, CL_OPT_UNCONFIGURE_DESC}; // Boolean + static inline const QCommandLineOption CL_OPTION_UNIVERSAL{{CL_OPT_UNIVERSAL_S_NAME, CL_OPT_UNIVERSAL_L_NAME}, CL_OPT_UNIVERSAL_DESC}; // Boolean + + static inline const QList CL_OPTIONS_SPECIFIC{&CL_OPTION_CONFIGURE, &CL_OPTION_UNCONFIGURE, &CL_OPTION_UNIVERSAL}; + +public: + // Meta + static inline const QString NAME = u"share"_s; + static inline const QString DESCRIPTION = u"Generates a URL for starting a Flashpoint title that can be shared to other users."_s; + +//-Constructor---------------------------------------------------------------------------------------------------------- +public: + CShare(Core& coreRef); + +//-Instance Functions------------------------------------------------------------------------------------------------------ +protected: + QList options() override; + QString name() override; + Qx::Error perform() override; +}; +REGISTER_COMMAND(CShare::NAME, CShare, CShare::DESCRIPTION); + +#endif // CSHARE_H diff --git a/app/src/command/c-show.h b/app/src/command/c-show.h index 9d0c253..679a29a 100644 --- a/app/src/command/c-show.h +++ b/app/src/command/c-show.h @@ -7,7 +7,7 @@ // Project Includes #include "command/command.h" -class QX_ERROR_TYPE(CShowError, "CShowError", 1216) +class QX_ERROR_TYPE(CShowError, "CShowError", 1217) { friend class CShow; //-Class Enums------------------------------------------------------------- diff --git a/app/src/command/title-command.cpp b/app/src/command/title-command.cpp index bc74392..0577e52 100644 --- a/app/src/command/title-command.cpp +++ b/app/src/command/title-command.cpp @@ -201,7 +201,7 @@ Qx::Error TitleCommand::getTitleId(QUuid& id) QUuid titleId; QUuid addAppId; - if(mParser.isSet(CL_OPTION_ID)) + if(mParser.isSet(CL_OPTION_ID)) // TODO: Check that directly supplied IDs actually belong to a title { QString idStr = mParser.value(CL_OPTION_ID); if((titleId = QUuid(idStr)).isNull()) diff --git a/app/src/controller.cpp b/app/src/controller.cpp index e2663db..87565be 100644 --- a/app/src/controller.cpp +++ b/app/src/controller.cpp @@ -30,18 +30,19 @@ Controller::Controller(QObject* parent) : 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 - Status + // Connect driver - Status/Non-blocking connect(driver, &Driver::statusChanged, &mStatusRelay, &StatusRelay::statusChangeHandler); connect(driver, &Driver::errorOccurred, &mStatusRelay, &StatusRelay::errorHandler); - connect(driver, &Driver::message, &mStatusRelay, &StatusRelay::messageHandler, Qt::BlockingQueuedConnection); // Allows optional blocking connect(driver, &Driver::longTaskProgressChanged, &mStatusRelay, &StatusRelay::longTaskProgressHandler); connect(driver, &Driver::longTaskTotalChanged, &mStatusRelay, &StatusRelay::longTaskTotalHandler); connect(driver, &Driver::longTaskStarted, &mStatusRelay, &StatusRelay::longTaskStartedHandler); connect(driver, &Driver::longTaskFinished, &mStatusRelay, &StatusRelay::longTaskFinishedHandler); connect(&mStatusRelay, &StatusRelay::longTaskCanceled, driver, &Driver::cancelActiveLongTask); connect(&mStatusRelay, &StatusRelay::longTaskCanceled, this, &Controller::longTaskCanceledHandler); + connect(driver, &Driver::clipboardUpdateRequested, &mStatusRelay, &StatusRelay::clipboardUpdateRequestHandler); - // Connect driver - Response Requested (BlockingQueuedConnection since response must be waited for) + // Connect driver - Response Requests/Blocking (BlockingQueuedConnection since response must be waited for) + connect(driver, &Driver::message, &mStatusRelay, &StatusRelay::messageHandler, Qt::BlockingQueuedConnection); // Allows optional blocking connect(driver, &Driver::blockingErrorOccurred, &mStatusRelay, &StatusRelay::blockingErrorHandler, Qt::BlockingQueuedConnection); connect(driver, &Driver::saveFileRequested, &mStatusRelay, &StatusRelay::saveFileRequestHandler, Qt::BlockingQueuedConnection); connect(driver, &Driver::itemSelectionRequested, &mStatusRelay, &StatusRelay::itemSelectionRequestHandler, Qt::BlockingQueuedConnection); diff --git a/app/src/frontend/statusrelay.cpp b/app/src/frontend/statusrelay.cpp index 0a9bdd2..f525461 100644 --- a/app/src/frontend/statusrelay.cpp +++ b/app/src/frontend/statusrelay.cpp @@ -14,7 +14,8 @@ //-Constructor-------------------------------------------------------------------- StatusRelay::StatusRelay(QObject* parent) : - QObject(parent) + QObject(parent), + mSystemClipboard(QGuiApplication::clipboard()) { setupTrayIcon(); setupProgressDialog(); @@ -121,6 +122,11 @@ void StatusRelay::itemSelectionRequestHandler(QSharedPointer item, cons qFatal("No response argument provided!"); } +void StatusRelay::clipboardUpdateRequestHandler(const QString& text) +{ + mSystemClipboard->setText(text); +} + void StatusRelay::longTaskProgressHandler(quint64 progress) { mLongTaskProgressDialog.setValue(progress); diff --git a/app/src/frontend/statusrelay.h b/app/src/frontend/statusrelay.h index ce02b95..84e1d2c 100644 --- a/app/src/frontend/statusrelay.h +++ b/app/src/frontend/statusrelay.h @@ -8,6 +8,7 @@ #include #include #include +#include // Qx Includes #include @@ -31,6 +32,7 @@ class StatusRelay : public QObject QSystemTrayIcon mTrayIcon; QMenu mTrayIconContextMenu; + QClipboard* mSystemClipboard; //-Constructor---------------------------------------------------------------------------------------------------------- public: @@ -43,13 +45,14 @@ class StatusRelay : public QObject //-Signals & Slots------------------------------------------------------------------------------------------------------ public slots: - // Status messages + // Request/status handlers void statusChangeHandler(const QString& statusHeading, const QString& statusMessage); void errorHandler(Core::Error error); void blockingErrorHandler(QSharedPointer response, Core::BlockingError blockingError); void messageHandler(const Message& message); void saveFileRequestHandler(QSharedPointer file, Core::SaveFileRequest request); void itemSelectionRequestHandler(QSharedPointer item, const Core::ItemSelectionRequest& request); + void clipboardUpdateRequestHandler(const QString& text); // Long Job void longTaskProgressHandler(quint64 progress); diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index aa1c966..418f6e4 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -842,6 +842,8 @@ QString Core::requestItemSelection(const ItemSelectionRequest& request) return *item; } +void Core::requestClipboardUpdate(const QString& text) { emit clipboardUpdateRequested(text); } + Fp::Install& Core::fpInstall() { return *mFlashpointInstall; } const QProcessEnvironment& Core::childTitleProcessEnvironment() { return mChildTitleProcEnv; } Core::NotificationVerbosity Core::notifcationVerbosity() const { return mNotificationVerbosity; } diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 2941c41..d54041d 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -282,6 +282,7 @@ class Core : public QObject void postMessage(const Message& msg); QString requestSaveFilePath(const SaveFileRequest& request); QString requestItemSelection(const ItemSelectionRequest& request); + void requestClipboardUpdate(const QString& text); // Member access Fp::Install& fpInstall(); @@ -305,6 +306,7 @@ class Core : public QObject void saveFileRequested(QSharedPointer file, const Core::SaveFileRequest& request); void itemSelectionRequested(QSharedPointer item, const Core::ItemSelectionRequest& request); void message(const Message& message); + void clipboardUpdateRequested(const QString& text); }; //-Metatype Declarations----------------------------------------------------------------------------------------- diff --git a/app/src/kernel/driver.cpp b/app/src/kernel/driver.cpp index 4099e65..a736259 100644 --- a/app/src/kernel/driver.cpp +++ b/app/src/kernel/driver.cpp @@ -61,6 +61,7 @@ void Driver::init() connect(mCore, &Core::message, this, &Driver::message); connect(mCore, &Core::saveFileRequested, this, &Driver::saveFileRequested); connect(mCore, &Core::itemSelectionRequested, this, &Driver::itemSelectionRequested); + connect(mCore, &Core::clipboardUpdateRequested, this, &Driver::clipboardUpdateRequested); //-Setup deferred process manager------ /* NOTE: It looks like the manager should just be a stack member of TExec that is constructed diff --git a/app/src/kernel/driver.h b/app/src/kernel/driver.h index 5b8ffc8..bfbff3a 100644 --- a/app/src/kernel/driver.h +++ b/app/src/kernel/driver.h @@ -155,6 +155,7 @@ public slots: void message(const Message& message); void saveFileRequested(QSharedPointer file, const Core::SaveFileRequest& request); void itemSelectionRequested(QSharedPointer item, const Core::ItemSelectionRequest& request); + void clipboardUpdateRequested(const QString& text); // Long task void longTaskProgressChanged(quint64 progress); diff --git a/app/src/utility.h b/app/src/utility.h index f6a8ad0..9fe5475 100644 --- a/app/src/utility.h +++ b/app/src/utility.h @@ -10,6 +10,7 @@ //-Macros---------------------------------------- #define ENUM_NAME(eenum) QString(magic_enum::enum_name(eenum).data()) #define CLIFP_DIR_PATH QCoreApplication::applicationDirPath() +#define CLIFP_PATH QCoreApplication::applicationFilePath() #define CLIFP_CUR_APP_FILENAME QFileInfo(QCoreApplication::applicationFilePath()).fileName() #define CLIFP_CUR_APP_BASENAME QFileInfo(QCoreApplication::applicationFilePath()).baseName() diff --git a/redirector/icon.png b/redirector/icon.png new file mode 100644 index 0000000..815b8a0 Binary files /dev/null and b/redirector/icon.png differ diff --git a/redirector/redirect.html b/redirector/redirect.html new file mode 100644 index 0000000..b8870cd --- /dev/null +++ b/redirector/redirect.html @@ -0,0 +1,58 @@ + + + + + + +
+ CLIFp Icon +
+ + + \ No newline at end of file