Improve lookup of Chromium-based browser binary

* Consider Edge and Chrome as well
* Read the binary path from the "Application Registration" under Windows
  (see https://learn.microsoft.com/en-us/windows/win32/shell/app-registration)
This commit is contained in:
Martchus 2023-03-28 22:32:03 +02:00
parent db657bb1aa
commit 033cd21d83
3 changed files with 88 additions and 19 deletions

View File

@ -373,8 +373,26 @@ QProcess::ProcessState SyncthingProcess::state() const
* - Only one process can be started at a time. Starting another process while the previous one or any child
* process of it is still running is an error.
* - In case of an error the error strinng is set and errorOccurred() is emitted.
* - If \a program is a relative path, it is tried to be located in PATH.
*/
void SyncthingProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode)
{
start(QStringList{program}, arguments, openMode);
}
/*!
* \brief Starts a new process within a new process group (or job object under Windows) with the specified parameters.
*
* This function is generally the same as the overload that just takes a single program (see its remarks for details).
* The difference is that it allows one to specify fallback \a programs which will be tried to be located in PATH in
* case the first program cannot be located in PATH.
*
* \remarks
* If an absolute path is given, no lookup via PATH will happen. Instead, the absolute path is used as-is and further
* fallbacks are not considered. This is even the case when the absolute path does not exist.
*
*/
void SyncthingProcess::start(const QStringList &programs, const QStringList &arguments, OpenMode openMode)
{
// get Boost.Process' code converter to provoke and handle a possible error when it is setting up its default locale via e.g. `std::locale("")`
try {
@ -419,15 +437,6 @@ void SyncthingProcess::start(const QString &program, const QStringList &argument
m_process = std::make_shared<SyncthingProcessInternalData>(m_handler->ioc);
emit stateChanged(m_process->state = QProcess::Starting);
// convert args
auto prog = LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(program);
auto args = std::vector<decltype(LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(arguments.front()))>();
for (const auto &arg : arguments) {
args.emplace_back(LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(arg));
}
m_process->program = program;
m_process->arguments = arguments;
// define handler
auto successHandler = boost::process::extend::on_success = [this, maybeProcess = m_process->weak_from_this()](auto &executor) {
std::cerr << EscapeCodes::Phrases::Info << "Launched process, PID: "
@ -479,18 +488,39 @@ void SyncthingProcess::start(const QString &program, const QStringList &argument
this, "handleError", Qt::QueuedConnection, Q_ARG(int, error), Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, false));
};
// start the process within a new process group (or job object under Windows)
try {
auto path = boost::filesystem::path(prog);
// convert args
m_process->arguments = arguments;
auto args = std::vector<decltype(LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(arguments.front()))>();
for (const auto &arg : arguments) {
args.emplace_back(LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(arg));
}
// locate program
auto hasProgram = false;
auto path = boost::filesystem::path();
for (const auto &program : programs) {
m_process->program = program;
path = boost::filesystem::path(LIB_SYNCTHING_CONNECTOR_STRING_CONVERSION(program));
if (path.empty()) {
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: no executable specified" << EscapeCodes::Phrases::End;
emit stateChanged(m_process->state = QProcess::NotRunning);
handleError(QProcess::FailedToStart, QStringLiteral("no executable specified"), false);
return;
continue;
}
hasProgram = true;
if (path.is_relative()) {
path = boost::process::search_path(path);
}
if (!path.empty()) {
break;
}
}
if (!hasProgram) {
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: no executable specified" << EscapeCodes::Phrases::End;
emit stateChanged(m_process->state = QProcess::NotRunning);
handleError(QProcess::FailedToStart, QStringLiteral("no executable specified"), false);
return;
}
// start the process within a new process group (or job object under Windows)
try {
if (path.empty()) {
throw boost::process::process_error(
std::make_error_code(std::errc::no_such_file_or_directory), "unable to find the specified executable in the search paths");

View File

@ -44,6 +44,7 @@ public:
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
QProcess::ProcessState state() const;
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly);
void start(const QStringList &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly);
qint64 bytesAvailable() const override;
void close() override;
int exitCode() const;

View File

@ -10,6 +10,11 @@
#include <QMessageBox>
#include <QUrl>
#ifdef Q_OS_WINDOWS
#include <QFile>
#include <QSettings> // for reading registry
#endif
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
#include "./webpage.h"
#include "./webviewinterceptor.h"
@ -25,6 +30,8 @@
#include <QtWebEngineWidgetsVersion>
#endif
#include <initializer_list>
using namespace QtUtilities;
namespace QtGui {
@ -162,13 +169,38 @@ bool WebViewDialog::eventFilter(QObject *watched, QEvent *event)
namespace QtGui {
static QStringList chromiumBasedBrowserBinaries()
{
static const auto envOverride = qEnvironmentVariable(PROJECT_VARNAME_UPPER "_CHROMIUM_BASED_BROWSER");
if (!envOverride.isEmpty()) {
return {envOverride};
}
static const auto relevantBinaries = std::initializer_list<QString>{
#ifdef Q_OS_WINDOWS
QStringLiteral("msedge.exe"), QStringLiteral("chromium.exe"), QStringLiteral("chrome.exe"),
#else
QStringLiteral("chromium"), QStringLiteral("chrome"), QStringLiteral("msedge"),
#endif
};
#ifdef Q_OS_WINDOWS
const auto appPath = QSettings(QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths"), QSettings::NativeFormat);
for (const auto &binaryName : relevantBinaries) {
const auto binaryPath = appPath.value(binaryName + QStringLiteral("/Default")).toString();
if (!binaryPath.isEmpty() && QFile::exists(binaryPath)) {
return {binaryPath};
}
}
#endif
return relevantBinaries;
}
/*!
* \brief Opens the specified \a url as "app" in a Chromium-based web browser.
* \todo Check for other Chromium-based browsers and use the Windows registry to find apps under Windows.
*/
static void openBrowserInAppMode(const QString &url)
{
static const auto app = qEnvironmentVariable(PROJECT_VARNAME_UPPER "_CHROMIUM_BASED_BROWSER", QStringLiteral("chromium"));
const auto appList = chromiumBasedBrowserBinaries();
auto *const process = new Data::SyncthingProcess();
QObject::connect(process, &Data::SyncthingProcess::finished, process, &QObject::deleteLater);
QObject::connect(process, &Data::SyncthingProcess::errorOccurred, process, [process] {
@ -176,11 +208,17 @@ static void openBrowserInAppMode(const QString &url)
messageBox.setWindowTitle(QStringLiteral("Syncthing"));
messageBox.setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
messageBox.setIcon(QMessageBox::Critical);
messageBox.setText(QCoreApplication::translate("QtGui", "Unable to open Syncthing UI via \"%1\": %2").arg(app, process->errorString()));
messageBox.setText(QCoreApplication::translate("QtGui", "Unable to open Syncthing UI via \"%1\": %2").arg(process->program(), process->errorString()));
messageBox.exec();
});
process->setProcessChannelMode(QProcess::ForwardedChannels);
process->startSyncthing(app, QStringList{ QStringLiteral("--app=") + url });
process->start(
appList
#ifndef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
.first()
#endif
, QStringList{ QStringLiteral("--app=") + url });
}
/*!