/* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "browser.h" #include "builtins.h" #include "configuration.h" #include "crashhandler.h" #include "session/session.h" #include "session/sessiondialog.h" #include "settings.h" #include "util.h" #include "version.h" #include #include #include #include #include #include #include #include // a helper function to join the keys of a command_map into a string inline std::string join_keys(const command_map &map, const std::string sep = ", ") { std::vector keys(map.size()); std::transform(map.begin(), map.end(), keys.begin(), [](auto pair) { return pair.first; }); std::sort(keys.begin(), keys.end()); std::string k; std::for_each(keys.begin(), keys.end() - 1, [&k, &sep](const std::string &piece) { k += piece + sep; }); k += keys.back(); return k; } int main(int argc, char **argv) { // change log pattern spdlog::set_pattern("[%^%l%$] [%P:%t] %v"); #ifdef QT_DEBUG spdlog::set_level(spdlog::level::debug); // Set global log level to debug #endif const command_map commands{ { "configuration", builtins::configuration } }; const std::vector args(argv + 1, argv + argc); args::ArgumentParser parser("smolbote: yet another no-frills browser", "Subcommands: " + join_keys(commands)); parser.Prog(argv[0]); args::HelpFlag cmd_help(parser, "help", "Display this help message.", { 'h', "help" }); args::Flag cmd_version(parser, "version", "Display version information.", { 'v', "version" }); args::Flag cmd_build(parser, "build", "Display build commit.", { 'b', "build" }); args::ValueFlag cmd_config(parser, "config", "Set the configuration file.", { 'c', "config" }); args::Flag cmd_noRemote(parser, "no-remote", "Do not accept or send remote commands.", { "no-remote" }); args::Flag cmd_pickSession(parser, "pick-session", "Show all available sessions and select which one to open", { "pick-session" }); args::ValueFlag cmd_session(parser, "session", "Open the specified session.", { 's', "session" }); args::PositionalList cmd_args(parser, "URL(s)", "List of URLs to open"); cmd_args.KickOut(true); try { auto next = parser.ParseArgs(args); if(cmd_version) return builtins::version(); if(cmd_build) return builtins::build(); // create and load configuration spdlog::info("Loading configuration {}", init_conf(args::get(cmd_config))); if(cmd_args) { const auto front = args::get(cmd_args).front(); const auto cmd = commands.find(front); if(cmd != commands.end()) { return cmd->second(argv[0], next, std::end(args)); } } } catch(args::Help &) { std::cout << parser; return 0; } catch(args::Error &e) { std::cerr << e.what() << std::endl; std::cerr << parser; return -1; } QVector plugins; CommandHash_t pluginCommands; // Load plugins [&]() { Configuration conf; spdlog::debug("plugins.path={}", conf.value("plugins.path").value()); for(const QString &path : Util::files(conf.value("plugins.path").value(), { "*.so", "*.dll" })) { auto *loader = new PluginLoader(path); const bool loaded = loader->load(); spdlog::info("{} plugin {}", loaded ? "Loaded" : "Failed to load", qUtf8Printable(path)); if(loaded) { plugins.append(loader); auto *plugin = qobject_cast(loader->instance()); pluginCommands.unite(plugin->commands()); } else { spdlog::warn("{}", qUtf8Printable(loader->errorString())); delete loader; } } }(); // argc, argv, allowSecondary Browser app(argc, argv); // set this, otherwise the webview becomes black when using a stylesheet app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); { Configuration conf; if(conf.value("usebreakpad").value()) { CrashHandler::Context ctx( conf.value("path.crashdump").value(), conf.value("path.crashhandler").value()); if(CrashHandler::install_handler(ctx)) { spdlog::info("Installed breakpad crash handler: {}", ctx.dumppath); } else { spdlog::warn("Failed to install breakpad crash handler: {}", ctx.dumppath); } } } const auto profile = []() { Configuration c; return c.value("profile.default").value(); }(); app.setup(plugins); QStringList urls; for(const auto &u : args::get(cmd_args)) { if(pluginCommands.contains(QString::fromStdString(u))) { return pluginCommands.value(QString::fromStdString(u))(); } else { urls.append(QString::fromStdString(u)); } } if(urls.isEmpty()) urls.append(QString()); // if app is primary, create new sessions from received messages if(app.isPrimary() && !cmd_noRemote) { QObject::connect(&app, &Browser::receivedMessage, &app, [](quint32 instanceId, QByteArray message) { Q_UNUSED(instanceId); auto doc = QJsonDocument::fromJson(message); Session::restoreSession(doc.object()); }); } { QJsonObject sessionData; if(cmd_pickSession) { auto *dlg = new SessionDialog(); if(const auto pick = dlg->pickSession()) sessionData = pick.value(); else sessionData = Session::fromCommandLine(profile, urls); } else if(cmd_session) { QFile sessionJson(QString::fromStdString(args::get(cmd_session))); if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { sessionData = QJsonDocument::fromJson(sessionJson.readAll()).object(); sessionJson.close(); } } else { sessionData = Session::fromCommandLine(profile, urls); } if(app.isPrimary() || cmd_noRemote) { Session::restoreSession(sessionData); } else { // app is secondary and not standalone return app.sendMessage(QJsonDocument(sessionData).toJson()); } } return app.exec(); }