diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 0ee6714aa..e35ef66ef 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -21,6 +21,8 @@ set(OLIVE_SOURCES common/cancelableobject.h common/channellayout.h common/clamp.h + common/commandlineparser.h + common/commandlineparser.cpp common/crashhandler.h common/crashhandler.cpp common/crashpadinterface.cpp diff --git a/app/common/commandlineparser.cpp b/app/common/commandlineparser.cpp new file mode 100644 index 000000000..584f6f1f3 --- /dev/null +++ b/app/common/commandlineparser.cpp @@ -0,0 +1,149 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "commandlineparser.h" + +#include +#include + +CommandLineParser::~CommandLineParser() +{ + foreach (const KnownOption& o, options_) { + delete o.option; + } + + foreach (const KnownPositionalArgument& a, positional_args_) { + delete a.option; + } +} + +const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description) +{ + Option* o = new Option(); + + options_.append({strings, description, o}); + + return o; +} + +const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArgument(const QString &name, const QString &description, bool required) +{ + PositionalArgument* a = new PositionalArgument(); + + positional_args_.append({name, description, a, required}); + + return a; +} + +void CommandLineParser::Process(int argc, char **argv) +{ + int positional_index = 0; + + for (int i=1; iSet(); + matched_known = true; + goto found_flag; + } + } + } + +found_flag: + if (!matched_known) { + qWarning() << "Unknown parameter:" << argv[i]; + } + + } else { + // Must be a positional flag + if (positional_index < positional_args_.size()) { + positional_args_[positional_index].option->SetSetting(argv[i]); + positional_index++; + } else { + qWarning() << "Unknown parameter:" << argv[i]; + } + } + } +} + +void CommandLineParser::PrintHelp(const char* filename) +{ + printf("%s %s\n", + QCoreApplication::applicationName().toUtf8().constData(), + QCoreApplication::applicationVersion().toUtf8().constData()); + + printf("Copyright (C) 2018-2020 Olive Team\n"); + + QString positional_args; + for (int i=0; i 0) { + positional_args.append(' '); + } + + positional_args.append('['); + positional_args.append(positional_args_.at(i).name); + positional_args.append(']'); + } + + const char* basename; +#ifdef Q_OS_WINDOWS + basename = strrchr(filename, '\\'); + if (!basename) { + basename = strrchr(filename, '/'); + } +#else + basename = strrchr(filename, '/'); +#endif + + printf("Usage: %s [options] %s\n\n", basename + 1, positional_args.toUtf8().constData()); + + foreach (const KnownOption& o, options_) { + QString all_args; + + for (int i=0; i 0) { + all_args.append(QStringLiteral(", ")); + } + + const QString& this_arg = o.args.at(i); + + all_args.append('-'); + all_args.append(this_arg); + } + + printf(" %s\n", all_args.toUtf8().constData()); + + printf(" %s\n\n", o.description.toUtf8().constData()); + } + + printf("\n"); +} diff --git a/app/common/commandlineparser.h b/app/common/commandlineparser.h new file mode 100644 index 000000000..ce568f3db --- /dev/null +++ b/app/common/commandlineparser.h @@ -0,0 +1,107 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef COMMANDLINEPARSER_H +#define COMMANDLINEPARSER_H + +#include + +#include "common/define.h" + +class CommandLineParser +{ +public: + ~CommandLineParser(); + + DISABLE_COPY_MOVE(CommandLineParser); + + class Option { + public: + Option() + { + is_set_ = false; + } + + bool IsSet() const + { + return is_set_; + } + + void Set() + { + is_set_ = true; + } + + private: + bool is_set_; + + }; + + class PositionalArgument + { + public: + PositionalArgument() = default; + + const QString& GetSetting() const + { + return setting_; + } + + void SetSetting(const QString& s) + { + setting_ = s; + } + + private: + QString setting_; + + }; + + CommandLineParser() = default; + + const Option* AddOption(const QStringList& strings, const QString& description); + + const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false); + + void Process(int argc, char** argv); + + void PrintHelp(const char* filename); + +private: + struct KnownOption { + QStringList args; + QString description; + Option* option; + }; + + struct KnownPositionalArgument { + QString name; + QString description; + PositionalArgument* option; + bool required; + }; + + QVector options_; + + QVector positional_args_; + +}; + +#endif // COMMANDLINEPARSER_H diff --git a/app/core.cpp b/app/core.cpp index 4ab066d1c..9790246ed 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -64,93 +63,23 @@ OLIVE_NAMESPACE_ENTER -Core Core::instance_; +Core* Core::instance_ = nullptr; -Core::Core() : +Core::Core(const CoreParams& params) : main_window_(nullptr), tool_(Tool::kPointer), addable_object_(Tool::kAddableEmpty), snapping_(true), - gui_active_(false) + core_params_(params) { + // Store reference to this object, making the assumption that Core will only ever be made in + // main(). This will obviously break if not. + instance_ = this; } Core *Core::instance() { - return &instance_; -} - -int Core::execute(QCoreApplication* a) -{ - int exit_code = 1; - - // - // Parse command line arguments - // - - QCommandLineParser parser; - QCommandLineOption help_option = parser.addHelpOption(); - QCommandLineOption version_option = parser.addVersionOption(); - - // Project from command line option - // FIXME: What's the correct way to make a visually "optional" positional argument, or is manually adding square - // brackets like this correct? - parser.addPositionalArgument("[project]", tr("Project to open on startup")); - - // Create fullscreen option - QCommandLineOption fullscreen_option({"f", "fullscreen"}, tr("Start in full screen mode")); - parser.addOption(fullscreen_option); - - // Create headless export option - QCommandLineOption headless_export_option({"x", "export"}, tr("Export project from command line")); - parser.addOption(headless_export_option); - - // Parse options - parser.process(*a); - - if (parser.isSet(help_option) || parser.isSet(version_option)) { - // These options don't launch any of the application proper - return a->exec(); - } - - // Start core - OLIVE_NAMESPACE::Core::instance()->Start(); - - QStringList args = parser.positionalArguments(); - - // Detect project to load on startup - if (!args.isEmpty()) { - startup_project_ = args.first(); - } - - gui_active_ = !parser.isSet(headless_export_option); - - if (gui_active_) { - - // Start GUI - StartGUI(parser.isSet(fullscreen_option)); - - // If we have a startup - QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection); - - // Run application loop and receive exit code - exit_code = a->exec(); - - } else { - - if (parser.isSet(headless_export_option)) { - // Start a headless export - if (StartHeadlessExport()) { - exit_code = 0; - } - } - - } - - // Clear core memory - OLIVE_NAMESPACE::Core::instance()->Stop(); - - return exit_code; + return instance_; } void Core::DeclareTypesForQt() @@ -204,6 +133,22 @@ void Core::Start() // qInfo() << "Using Qt version:" << qVersion(); + + switch (core_params_.run_mode()) { + case CoreParams::kRunNormal: + // Start GUI + StartGUI(core_params_.fullscreen()); + + // If we have a startup + QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection); + break; + case CoreParams::kHeadlessExport: + qInfo() << "Headless export is not fully implemented yet"; + break; + case CoreParams::kHeadlessPreCache: + qInfo() << "Headless pre-cache is not fully implemented yet"; + break; + } } void Core::Stop() @@ -571,18 +516,20 @@ void Core::ProjectWasModified(bool e) bool Core::StartHeadlessExport() { - if (startup_project_.isEmpty()) { + const QString& startup_project = core_params_.startup_project(); + + if (startup_project.isEmpty()) { qCritical().noquote() << tr("You must specify a project file to export"); return false; } - if (!QFileInfo::exists(startup_project_)) { + if (!QFileInfo::exists(startup_project)) { qCritical().noquote() << tr("Specified project does not exist"); return false; } // Start a load task and try running it - ProjectLoadTask plm(startup_project_); + ProjectLoadTask plm(startup_project); CLITaskDialog task_dialog(&plm); if (task_dialog.Run()) { @@ -651,21 +598,24 @@ bool Core::StartHeadlessExport() void Core::OpenStartupProject() { + const QString& startup_project = core_params_.startup_project(); + bool startup_project_exists = !startup_project.isEmpty() && QFileInfo::exists(startup_project); + // Load startup project - if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) { + if (!startup_project_exists && !startup_project.isEmpty()) { QMessageBox::warning(main_window_, tr("Failed to open startup file"), - tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_), + tr("The project \"%1\" doesn't exist. " + "A new project will be started instead.").arg(startup_project), QMessageBox::Ok); - - startup_project_.clear(); } - if (startup_project_.isEmpty()) { + if (startup_project_exists) { + // If a startup project was set and exists, open it now + OpenProjectInternal(startup_project); + } else { // If no load project is set, create a new one on open CreateNewProject(); - } else { - OpenProjectInternal(startup_project_); } } @@ -1228,4 +1178,10 @@ void Core::OpenProject() } } +Core::CoreParams::CoreParams() : + mode_(kRunNormal), + run_fullscreen_(false) +{ +} + OLIVE_NAMESPACE_EXIT diff --git a/app/core.h b/app/core.h index 01d941f0a..71fa43b75 100644 --- a/app/core.h +++ b/app/core.h @@ -52,12 +52,62 @@ class Core : public QObject { Q_OBJECT public: + class CoreParams + { + public: + CoreParams(); + + enum RunMode { + kRunNormal, + kHeadlessExport, + kHeadlessPreCache + }; + + bool fullscreen() const + { + return run_fullscreen_; + } + + void set_fullscreen(bool e) + { + run_fullscreen_ = e; + } + + RunMode run_mode() const + { + return mode_; + } + + void set_run_mode(RunMode m) + { + mode_ = m; + } + + const QString startup_project() const + { + return startup_project_; + } + + void set_startup_project(const QString& p) + { + startup_project_ = p; + } + + private: + RunMode mode_; + + QString startup_project_; + + bool run_fullscreen_; + + }; + /** * @brief Core Constructor * * Currently empty */ - Core(); + Core(const CoreParams& params); /** * @brief Core object accessible from anywhere in the code @@ -66,7 +116,10 @@ public: */ static Core* instance(); - int execute(QCoreApplication *a); + const CoreParams& core_params() const + { + return core_params_; + } /** * @brief Start Olive Core @@ -407,14 +460,6 @@ private: */ MainWindow* main_window_; - /** - * @brief Internal startup project object - * - * If the user specifies a project file on the command line, the command line parser in Start() will write the - * project URL here to be loaded once Olive has finished initializing. - */ - QString startup_project_; - /** * @brief List of currently open projects */ @@ -456,14 +501,14 @@ private: QStringList recent_projects_; /** - * @brief Internal variable for whether the GUI is active + * @brief Parameters set up in main() determining how the program should run */ - bool gui_active_; + CoreParams core_params_; /** * @brief Static singleton core instance */ - static Core instance_; + static Core* instance_; private slots: void SaveAutorecovery(); diff --git a/app/main.cpp b/app/main.cpp index d58b65ebf..3cfa34db2 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -34,34 +34,23 @@ extern "C" { #include #include +#include #include #include "core.h" +#include "common/commandlineparser.h" #include "common/debug.h" #ifdef USE_CRASHPAD #include "common/crashpadinterface.h" #endif // USE_CRASHPAD -int main(int argc, char *argv[]) { - // Set OpenGL display profile (3.2 Core) - QSurfaceFormat format; - format.setVersion(3, 2); - format.setDepthBufferSize(24); - format.setProfile(QSurfaceFormat::CoreProfile); - QSurfaceFormat::setDefaultFormat(format); - - //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); - - // Create application instance - QApplication a(argc, argv); - - // Set application metadata - QCoreApplication::setOrganizationName("olivevideoeditor.org"); - QCoreApplication::setOrganizationDomain("olivevideoeditor.org"); - QCoreApplication::setApplicationName("Olive"); +int main(int argc, char *argv[]) +{ + // Set up debug handler + qInstallMessageHandler(OLIVE_NAMESPACE::DebugHandler); + // Generate version string QString app_version = APPVERSION; #ifdef GITHASH // Anything after the hyphen is considered "unimportant" information. Text BEFORE the hyphen is used in version @@ -70,14 +59,85 @@ int main(int argc, char *argv[]) { app_version.append(GITHASH); #endif + // Set application metadata + QCoreApplication::setOrganizationName("olivevideoeditor.org"); + QCoreApplication::setOrganizationDomain("olivevideoeditor.org"); + QCoreApplication::setApplicationName("Olive"); + QCoreApplication::setApplicationVersion(app_version); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) QGuiApplication::setDesktopFileName("org.olivevideoeditor.Olive"); #endif - // Set up debug handler - qInstallMessageHandler(OLIVE_NAMESPACE::DebugHandler); + // + // Parse command line arguments + // + + OLIVE_NAMESPACE::Core::CoreParams startup_params; + + CommandLineParser parser; + + const CommandLineParser::Option* help_option = + parser.AddOption({QStringLiteral("h"), QStringLiteral("-help")}, + QCoreApplication::translate("main", "Show this help text")); + + const CommandLineParser::Option* version_option = + parser.AddOption({QStringLiteral("v"), QStringLiteral("-version")}, + QCoreApplication::translate("main", "Show application version")); + + const CommandLineParser::Option* fullscreen_option = + parser.AddOption({QStringLiteral("f"), QStringLiteral("-fullscreen")}, + QCoreApplication::translate("main", "Start in full-screen mode")); + + const CommandLineParser::Option* export_option = + parser.AddOption({QStringLiteral("x"), QStringLiteral("-export")}, + QCoreApplication::translate("main", "Export only (No GUI)")); + + const CommandLineParser::PositionalArgument* project_argument = + parser.AddPositionalArgument(QStringLiteral("project"), + QCoreApplication::translate("main", "Project to open on startup")); + + parser.Process(argc, argv); + + if (help_option->IsSet()) { + // Show help + parser.PrintHelp(argv[0]); + return 0; + } + + if (version_option->IsSet()) { + // Print version + printf("%s\n", app_version.toUtf8().constData()); + return 0; + } + + if (export_option->IsSet()) { + startup_params.set_run_mode(OLIVE_NAMESPACE::Core::CoreParams::kHeadlessExport); + } + + startup_params.set_fullscreen(fullscreen_option->IsSet()); + + startup_params.set_startup_project(project_argument->GetSetting()); + + // Set OpenGL display profile (3.2 Core) + QSurfaceFormat format; + format.setVersion(3, 2); + format.setDepthBufferSize(24); + format.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(format); + + // Enable application automatically using higher resolution images from icons + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + + // Create application instance + std::unique_ptr a; + + if (startup_params.run_mode() == OLIVE_NAMESPACE::Core::CoreParams::kRunNormal) { + a.reset(new QApplication(argc, argv)); + } else { + a.reset(new QCoreApplication(argc, argv)); + } // Register FFmpeg codecs and filters (deprecated in 4.0+) #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) @@ -94,5 +154,14 @@ int main(int argc, char *argv[]) { } #endif // USE_CRASHPAD - return OLIVE_NAMESPACE::Core::instance()->execute(&a); + // Start core + OLIVE_NAMESPACE::Core c(startup_params); + c.Start(); + + int ret = a->exec(); + + // Clear core memory + c.Stop(); + + return ret; }