diff --git a/src/commandlineoptions.cpp b/src/commandlineoptions.cpp new file mode 100644 index 00000000..34a08f6d --- /dev/null +++ b/src/commandlineoptions.cpp @@ -0,0 +1,55 @@ +#include "commandlineoptions.h" + +#include +#include +#include +#include + +#define TR(x) QCoreApplication::translate("main", (x)) + +CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_arguments) +{ + QCommandLineParser parser; + parser.setApplicationDescription(TR("A pleasant note-taking platform.")); + const auto helpOpt = parser.addHelpOption(); + const auto versionOpt = parser.addVersionOption(); + + // Positional arguments. + parser.addPositionalArgument("paths", TR("Files or folders to open.")); + + const QCommandLineOption verboseOpt("verbose", TR("Print more logs.")); + parser.addOption(verboseOpt); + + // WebEngine options. + // No need to handle them. Just add them to the parser to avoid parse error. + QCommandLineOption webRemoteDebuggingPortOpt("remote-debugging-port", + TR("WebEngine remote debugging port."), + "port_number"); + webRemoteDebuggingPortOpt.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(webRemoteDebuggingPortOpt); + + if (!parser.parse(p_arguments)) { + m_errorMsg = parser.errorText(); + return ParseResult::Error; + } + + // Handle results. + m_helpText = parser.helpText(); + if (parser.isSet(helpOpt)) { + return ParseResult::HelpRequested; + } + + if (parser.isSet(versionOpt)) { + return ParseResult::VersionRequested; + } + + // Position arguments. + const auto args = parser.positionalArguments(); + m_pathsToOpen = args; + + if (parser.isSet(verboseOpt)) { + m_verbose = true; + } + + return ParseResult::Ok; +} diff --git a/src/commandlineoptions.h b/src/commandlineoptions.h new file mode 100644 index 00000000..f8b22b80 --- /dev/null +++ b/src/commandlineoptions.h @@ -0,0 +1,30 @@ +#ifndef COMMANDLINEOPTIONS_H +#define COMMANDLINEOPTIONS_H + +#include + +class CommandLineOptions +{ +public: + enum ParseResult + { + Ok, + Error, + VersionRequested, + HelpRequested + }; + + CommandLineOptions() = default; + + ParseResult parse(const QStringList &p_arguments); + + QString m_errorMsg; + + QString m_helpText; + + QStringList m_pathsToOpen; + + bool m_verbose = false; +}; + +#endif // COMMANDLINEOPTIONS_H diff --git a/src/core/configmgr.cpp b/src/core/configmgr.cpp index dd675a59..6f4b3a14 100644 --- a/src/core/configmgr.cpp +++ b/src/core/configmgr.cpp @@ -229,7 +229,7 @@ QString ConfigMgr::getConfigFilePath(Source p_src) const QString configPath; switch (p_src) { case Source::Default: - configPath = QStringLiteral(":/vnotex/data/core/") + c_configFileName; + configPath = getDefaultConfigFilePath(); break; case Source::App: @@ -255,9 +255,13 @@ QString ConfigMgr::getConfigFilePath(Source p_src) const return configPath; } +QString ConfigMgr::getDefaultConfigFilePath() +{ + return QStringLiteral(":/vnotex/data/core/") + c_configFileName; +} + QSharedPointer ConfigMgr::getSettings(Source p_src) const { - return ConfigMgr::Settings::fromFile(getConfigFilePath(p_src)); } @@ -433,3 +437,18 @@ QString ConfigMgr::getDocumentOrHomePath() return docHomePath; } + +QString ConfigMgr::getApplicationVersion() +{ + static QString appVersion; + + if (appVersion.isEmpty()) { + auto defaultSettings = ConfigMgr::Settings::fromFile(getDefaultConfigFilePath()); + const auto &defaultObj = defaultSettings->getJson(); + + auto metaDataObj = defaultObj.value(QStringLiteral("metadata")).toObject(); + appVersion = metaDataObj.value(QStringLiteral("version")).toString(); + } + + return appVersion; +} diff --git a/src/core/configmgr.h b/src/core/configmgr.h index 48d1d637..39c837f9 100644 --- a/src/core/configmgr.h +++ b/src/core/configmgr.h @@ -104,6 +104,8 @@ namespace vnotex static QString getDocumentOrHomePath(); + static QString getApplicationVersion(); + static const QString c_orgName; static const QString c_appName; @@ -129,6 +131,8 @@ namespace vnotex // Update it if in need. void checkAppConfig(); + static QString getDefaultConfigFilePath(); + QScopedPointer m_config;; // Session config. diff --git a/src/core/singleinstanceguard.cpp b/src/core/singleinstanceguard.cpp index aa92c976..e05e1308 100644 --- a/src/core/singleinstanceguard.cpp +++ b/src/core/singleinstanceguard.cpp @@ -12,6 +12,8 @@ using namespace vnotex; const QString SingleInstanceGuard::c_serverName = "vnote"; +const QChar SingleInstanceGuard::c_stringListSeparator = '>'; + SingleInstanceGuard::SingleInstanceGuard() { qInfo() << "guarding is on"; @@ -54,6 +56,14 @@ void SingleInstanceGuard::requestOpenFiles(const QStringList &p_files) if (p_files.isEmpty()) { return; } + + Q_ASSERT(!m_online); + if (!m_client || m_client->state() != QLocalSocket::ConnectedState) { + qWarning() << "failed to request open files" << m_client->errorString(); + return ; + } + + sendRequest(m_client.data(), OpCode::OpenFiles, p_files.join(c_stringListSeparator)); } void SingleInstanceGuard::requestShow() @@ -157,34 +167,49 @@ void SingleInstanceGuard::receiveCommand(QLocalSocket *p_socket) inStream.setDevice(p_socket); inStream.setVersion(QDataStream::Qt_5_12); - if (m_command.m_opCode == OpCode::Null) { - // Relies on the fact that QDataStream serializes a quint32 into - // sizeof(quint32) bytes. - if (p_socket->bytesAvailable() < (int)sizeof(quint32) * 2) { + while (p_socket->bytesAvailable() > 0) { + if (m_command.m_opCode == OpCode::Null) { + // Relies on the fact that QDataStream serializes a quint32 into + // sizeof(quint32) bytes. + if (p_socket->bytesAvailable() < (int)sizeof(quint32) * 2) { + return; + } + + quint32 opCode = 0; + inStream >> opCode; + m_command.m_opCode = static_cast(opCode); + inStream >> m_command.m_size; + } + + if (p_socket->bytesAvailable() < m_command.m_size) { return; } - quint32 opCode = 0; - inStream >> opCode; - m_command.m_opCode = static_cast(opCode); - inStream >> m_command.m_size; - } + qDebug() << "op code" << m_command.m_opCode << m_command.m_size << p_socket->bytesAvailable(); - if (p_socket->bytesAvailable() < m_command.m_size) { - return; - } + switch (m_command.m_opCode) { + case OpCode::Show: + Q_ASSERT(m_command.m_size == 0); + emit showRequested(); + break; - qDebug() << "op code" << m_command.m_opCode << m_command.m_size; + case OpCode::OpenFiles: + { + Q_ASSERT(m_command.m_size != 0); + QString payload; + inStream >> payload; + const auto files = payload.split(c_stringListSeparator); + emit openFilesRequested(files); + break; + } - switch (m_command.m_opCode) { - case OpCode::Show: - Q_ASSERT(m_command.m_size == 0); - emit showRequested(); - break; + default: + qWarning() << "unknown op code" << m_command.m_opCode; + m_command.clear(); + return; + } - default: - qWarning() << "unknown op code" << m_command.m_opCode; - break; + m_command.clear(); } } diff --git a/src/core/singleinstanceguard.h b/src/core/singleinstanceguard.h index ffa1acad..85fc7bfb 100644 --- a/src/core/singleinstanceguard.h +++ b/src/core/singleinstanceguard.h @@ -41,7 +41,8 @@ namespace vnotex enum OpCode { Null = 0, - Show + Show, + OpenFiles }; struct Command @@ -78,6 +79,8 @@ namespace vnotex Command m_command; static const QString c_serverName; + + static const QChar c_stringListSeparator; }; } // ns vnotex diff --git a/src/main.cpp b/src/main.cpp index 948afc0c..6fb3ee5c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "commandlineoptions.h" using namespace vnotex; @@ -28,6 +29,8 @@ void loadTranslators(QApplication &p_app); void initWebEngineSettings(); +void showMessageOnCommandLineIfAvailable(const QString &p_msg); + int main(int argc, char *argv[]) { QTextCodec *codec = QTextCodec::codecForName("UTF8"); @@ -76,18 +79,44 @@ int main(int argc, char *argv[]) app.setApplicationName(ConfigMgr::c_appName); app.setOrganizationName(ConfigMgr::c_orgName); + + app.setApplicationVersion(ConfigMgr::getApplicationVersion()); + } + + CommandLineOptions cmdOptions; + switch (cmdOptions.parse(app.arguments())) { + case CommandLineOptions::Ok: + break; + + case CommandLineOptions::Error: + showMessageOnCommandLineIfAvailable(cmdOptions.m_errorMsg); + return -1; + + case CommandLineOptions::VersionRequested: + { + auto versionStr = QString("%1 %2").arg(app.applicationName()).arg(app.applicationVersion()); + showMessageOnCommandLineIfAvailable(versionStr); + return 0; + } + + case CommandLineOptions::HelpRequested: + Q_FALLTHROUGH(); + default: + showMessageOnCommandLineIfAvailable(cmdOptions.m_helpText); + return 0; } // Guarding. SingleInstanceGuard guard; bool canRun = guard.tryRun(); if (!canRun) { + guard.requestOpenFiles(cmdOptions.m_pathsToOpen); guard.requestShow(); return 0; } try { - app.setApplicationVersion(ConfigMgr::getInst().getConfig().getVersion()); + ConfigMgr::getInst(); } catch (Exception &e) { MessageBoxHelper::notify(MessageBoxHelper::Critical, MainWindow::tr("%1 failed to start.").arg(ConfigMgr::c_appName), @@ -98,7 +127,7 @@ int main(int argc, char *argv[]) } // Init logger after app info is set. - Logger::init(false); + Logger::init(cmdOptions.m_verbose); qInfo() << QString("%1 (v%2) started at %3 (%4)").arg(ConfigMgr::c_appName, app.applicationVersion(), @@ -112,8 +141,6 @@ int main(int argc, char *argv[]) qWarning() << "versions of the built and linked OpenSSL mismatch, network may not work"; } - // TODO: parse command line options. - // Should set the correct locale before VNoteX::getInst(). loadTranslators(app); @@ -131,8 +158,10 @@ int main(int argc, char *argv[]) QObject::connect(&guard, &SingleInstanceGuard::showRequested, &window, &MainWindow::showMainWindow); + QObject::connect(&guard, &SingleInstanceGuard::openFilesRequested, + &window, &MainWindow::openFiles); - window.kickOffOnStart(); + window.kickOffOnStart(cmdOptions.m_pathsToOpen); int ret = app.exec(); if (ret == RESTART_EXIT_CODE) { @@ -208,3 +237,13 @@ void initWebEngineSettings() auto settings = QWebEngineSettings::defaultSettings(); settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); } + +void showMessageOnCommandLineIfAvailable(const QString &p_msg) +{ +#if defined(Q_OS_WIN) + MessageBoxHelper::notify(MessageBoxHelper::Information, + QString("
%1
").arg(p_msg)); +#else + printf("%s\n", qPrintable(p_msg)); +#endif +} diff --git a/src/src.pro b/src/src.pro index c14903fd..fd716c69 100644 --- a/src/src.pro +++ b/src/src.pro @@ -30,6 +30,7 @@ ICON = data/core/icons/vnote.icns TRANSLATIONS += data/core/translations/vnote_zh_CN.ts SOURCES += \ + commandlineoptions.cpp \ main.cpp INCLUDEPATH *= $$PWD @@ -137,3 +138,6 @@ unix:!macx { INSTALLS += extraresource message("VNote will be installed in prefix $${PREFIX}") } + +HEADERS += \ + commandlineoptions.h diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 15db3d19..10ed8de9 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -70,15 +70,26 @@ MainWindow::~MainWindow() m_viewArea = nullptr; } -void MainWindow::kickOffOnStart() +void MainWindow::kickOffOnStart(const QStringList &p_paths) { - VNoteX::getInst().initLoad(); + QTimer::singleShot(300, [this, p_paths]() { + VNoteX::getInst().initLoad(); - emit mainWindowStarted(); + emit mainWindowStarted(); - emit layoutChanged(); + emit layoutChanged(); - demoWidget(); + demoWidget(); + + openFiles(p_paths); + }); +} + +void MainWindow::openFiles(const QStringList &p_files) +{ + for (const auto &file : p_files) { + emit VNoteX::getInst().openFileRequested(file, QSharedPointer::create()); + } } void MainWindow::setupUI() diff --git a/src/widgets/mainwindow.h b/src/widgets/mainwindow.h index 53887138..a570b824 100644 --- a/src/widgets/mainwindow.h +++ b/src/widgets/mainwindow.h @@ -33,7 +33,7 @@ namespace vnotex MainWindow(const MainWindow &) = delete; void operator=(const MainWindow &) = delete; - void kickOffOnStart(); + void kickOffOnStart(const QStringList &p_paths); void resetStateAndGeometry(); @@ -53,6 +53,8 @@ namespace vnotex void quitApp(); + void openFiles(const QStringList &p_files); + signals: void mainWindowStarted();