diff --git a/internal/app/app.go b/internal/app/app.go index 023114c2..fcaf3ecd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -69,9 +69,10 @@ const ( // Hidden flags. const ( - flagLauncher = "launcher" - flagNoWindow = "no-window" - flagParentPID = "parent-pid" + flagLauncher = "launcher" + flagNoWindow = "no-window" + flagParentPID = "parent-pid" + flagSoftwareRenderer = "software-renderer" ) const ( @@ -140,6 +141,12 @@ func New() *cli.App { //nolint:funlen Hidden: true, Value: -1, }, + &cli.BoolFlag{ + Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser. + Usage: "GUI is using software renderer", + Hidden: true, + Value: false, + }, } app.Action = run diff --git a/internal/frontend/bridge-gui/bridge-gui/CommandLine.cpp b/internal/frontend/bridge-gui/bridge-gui/CommandLine.cpp index bd199850..3597fa79 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CommandLine.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/CommandLine.cpp @@ -29,7 +29,7 @@ namespace QString const launcherFlag = "--launcher"; ///< launcher flag parameter used for bridge. QString const noWindowFlag = "--no-window"; ///< The no-window command-line flag. - +QString const softwareRendererFlag = "--software-renderer"; ///< The 'software-renderer' command-line flag. //**************************************************************************************************************************************************** /// \brief parse a command-line string argument as expected by go's CLI package. @@ -86,53 +86,51 @@ Log::Level parseLogLevel(int argc, char *argv[]) //**************************************************************************************************************************************************** /// \param[in] argc number of arguments passed to the application. /// \param[in] argv list of arguments passed to the application. -/// \param[out] args list of arguments passed to the application as a QStringList. -/// \param[out] launcher launcher used in argument, forced to self application if not specify. -/// \param[out] outAttach The value for the 'attach' command-line parameter. -/// \param[out] outLogLevel The parsed log level. If not found, the default log level is returned. -/// \param[out] outNoWindow True if the '--no-window' flag was found on the command-line. +/// \return The parsed options. //**************************************************************************************************************************************************** -void parseCommandLineArguments(int argc, char *argv[], QStringList& args, QString& launcher, bool &outAttach, Log::Level& outLogLevel, - bool &outNoWindow) { +CommandLineOptions parseCommandLine(int argc, char *argv[]) { + CommandLineOptions options; bool flagFound = false; - outNoWindow = false; - launcher = QString::fromLocal8Bit(argv[0]); + options.launcher = QString::fromLocal8Bit(argv[0]); // for unknown reasons, on Windows QCoreApplication::arguments() frequently returns an empty list, which is incorrect, so we rebuild the argument // list from the original argc and argv values. for (int i = 1; i < argc; i++) { QString const &arg = QString::fromLocal8Bit(argv[i]); // we can't use QCommandLineParser here since it will fail on unknown options. // Arguments may contain some bridge flags. - + if (arg == softwareRendererFlag) + options.useSoftwareRenderer = true; if (arg == noWindowFlag) { - outNoWindow = true; + options.noWindow = true; } if (arg == launcherFlag) { - args.append(arg); - launcher = QString::fromLocal8Bit(argv[++i]); - args.append(launcher); + options.bridgeArgs.append(arg); + options.launcher = QString::fromLocal8Bit(argv[++i]); + options.bridgeArgs.append(options.launcher); flagFound = true; } #ifdef QT_DEBUG else if (arg == "--attach" || arg == "-a") { // we don't keep the attach mode within the args since we don't need it for Bridge. - outAttach = true; + options.attach = true; } #endif else { - args.append(arg); + options.bridgeArgs.append(arg); } } if (!flagFound) { // add bridge-gui as launcher - args.append(launcherFlag); - args.append(launcher); + options.bridgeArgs.append(launcherFlag); + options.bridgeArgs.append(options.launcher); } - outLogLevel = parseLogLevel(argc, argv); + options.logLevel = parseLogLevel(argc, argv); + + return options; } diff --git a/internal/frontend/bridge-gui/bridge-gui/CommandLine.h b/internal/frontend/bridge-gui/bridge-gui/CommandLine.h index 4b4a3bab..2d0d3af6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CommandLine.h +++ b/internal/frontend/bridge-gui/bridge-gui/CommandLine.h @@ -23,8 +23,20 @@ #include -void parseCommandLineArguments(int argc, char *argv[], QStringList& args, QString& launcher, bool &outAttach, bridgepp::Log::Level& outLogLevel, - bool &outNoWindow); ///< Parse the command-line arguments +//**************************************************************************************************************************************************** +/// \brief A struct containing the parsed command line options +//**************************************************************************************************************************************************** +struct CommandLineOptions { + QStringList bridgeArgs; ///< The command-line arguments we will pass to bridge when launching it. + QString launcher; ///< The path to the launcher. + bool attach { false }; ///< Is the application running in attached mode? + bridgepp::Log::Level logLevel { bridgepp::Log::defaultLevel }; ///< The log level + bool noWindow { false }; ///< Should the application start without displaying the main window? + bool useSoftwareRenderer { false }; ///< Should QML be renderer in software (i.e. without rendering hardware interface). +}; + + +CommandLineOptions parseCommandLine(int argc, char *argv[]); ///< Parse the command-line arguments #endif //BRIDGE_GUI_COMMAND_LINE_H diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 39b0a2bb..830a712b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -294,16 +294,16 @@ void closeBridgeApp() int main(int argc, char *argv[]) { // Init sentry. - sentry_options_t* options = sentry_options_new(); - sentry_options_set_dsn(options, SentryDNS); + sentry_options_t* sentryOptions = sentry_options_new(); + sentry_options_set_dsn(sentryOptions, SentryDNS); { const QString sentryCachePath = sentryCacheDir(); - sentry_options_set_database_path(options, sentryCachePath.toStdString().c_str()); + sentry_options_set_database_path(sentryOptions, sentryCachePath.toStdString().c_str()); } - sentry_options_set_release(options, SentryProductID); + sentry_options_set_release(sentryOptions, SentryProductID); // Enable this for debugging sentry. - // sentry_options_set_debug(options, 1); - if (sentry_init(options) != 0) { + // sentry_options_set_debug(sentryOptions, 1); + if (sentry_init(sentryOptions) != 0) { std::cerr << "Failed to initialize sentry" << std::endl; } @@ -313,6 +313,7 @@ int main(int argc, char *argv[]) // application instance is create outside the try/catch clause. if (QSysInfo::productType() != "windows") QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + QApplication guiApp(argc, argv); try @@ -328,43 +329,45 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - QStringList args; - QString launcher; - bool attach = false; - bool noWindow; - Log::Level logLevel = Log::defaultLevel; - parseCommandLineArguments(argc, argv, args, launcher, attach, logLevel, noWindow); + CommandLineOptions const cliOptions = parseCommandLine(argc, argv); + #ifdef Q_OS_MACOS - setDockIconVisibleState(!noWindow); + setDockIconVisibleState(!cliOptions.noWindow); #endif // In attached mode, we do not intercept stderr and stdout of bridge, as we did not launch it ourselves, so we output the log to the console. // When not in attached mode, log entries are forwarded to bridge, which output it on stdout/stderr. bridge-gui's process monitor intercept // these outputs and output them on the command-line. - log.setLevel(logLevel); + log.setLevel(cliOptions.logLevel); - if (!attach) + if (!cliOptions.attach) { if (isBridgeRunning()) throw Exception("An orphan instance of bridge is already running. Please terminate it and relaunch the application."); // before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one. GRPCClient::removeServiceConfigFile(); - launchBridge(args); + launchBridge(cliOptions.bridgeArgs); } log.info(QString("Retrieving gRPC service configuration from '%1'").arg(QDir::toNativeSeparators(grpcServerConfigPath()))); - app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(attach ? 0 : grpcServiceConfigWaitDelayMs, app().bridgeMonitor())); - if (!attach) + app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(cliOptions.attach ? 0 : grpcServiceConfigWaitDelayMs, app().bridgeMonitor())); + if (!cliOptions.attach) GRPCClient::removeServiceConfigFile(); // gRPC communication is established. From now on, log events will be sent to bridge via gRPC. bridge will write these to file, // and will output then on console if appropriate. If we are not running in attached mode we intercept bridge stdout & stderr and // display it in our own output and error, so we only continue to log directly to console if we are running in attached mode. - log.setEchoInConsole(attach); + log.setEchoInConsole(cliOptions.attach); log.info("Backend was successfully initialized."); log.stopWritingToFile(); + // The following allows to render QML content in software with a 'Rendering Hardware Interface' (OpenGL, Vulkan, Metal, Direct3D...) + // Note that it is different from the Qt::AA_UseSoftwareOpenGL attribute we use on some platforms that instruct Qt that we would like + // to use a software-only implementation of OpenGL. + QQuickWindow::setSceneGraphBackend(cliOptions.useSoftwareRenderer ? "software" : "rhi"); + log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend())); + QQmlApplicationEngine engine; std::unique_ptr rootComponent(createRootQmlComponent(engine)); std::unique_ptrrootObject(rootComponent->create(engine.rootContext())); @@ -378,12 +381,12 @@ int main(int argc, char *argv[]) if (bridgeMonitor) { const ProcessMonitor::MonitorStatus& status = bridgeMonitor->getStatus(); - if (status.ended && !attach) + if (status.ended && !cliOptions.attach) { // ProcessMonitor already stopped meaning we are attached to an orphan Bridge. // Restart the full process to be sure there is no more bridge orphans app().log().error("Found orphan bridge, need to restart."); - app().backend().forceLauncher(launcher); + app().backend().forceLauncher(cliOptions.launcher); app().backend().restart(); bridgeExited = true; startError = true;