#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "application_controller.h" #include "backend.h" #include "qobject.h" #include "sharing/linux/app/native_file_logging.h" namespace { constexpr char kQmlHotReloadEnv[] = "NEARBY_QML_HOT_RELOAD"; constexpr char kQmlSourceDirEnv[] = "NEARBY_QML_SOURCE_DIR"; constexpr char kDefaultQmlSourceDir[] = "/home/lasan/Dev/nearby_latest/sharing/linux/app"; int g_signal_pipe[2] = {-1, -1}; void HandleTerminationSignal(int signal) { const char value = static_cast(signal); if (g_signal_pipe[1] != -1) { static_cast(write(g_signal_pipe[1], &value, sizeof(value))); } } bool IsHotReloadEnabled() { const QByteArray value = qgetenv(kQmlHotReloadEnv); return value == "1" || value.toLower() == "true"; } QDir QmlSourceDir() { const QByteArray configured_dir = qgetenv(kQmlSourceDirEnv); if (!configured_dir.isEmpty()) { return QDir(QString::fromLocal8Bit(configured_dir)); } return QDir(QString::fromUtf8(kDefaultQmlSourceDir)); } void WatchQmlFiles(QFileSystemWatcher& watcher, const QDir& source_dir) { const QStringList existing_files = watcher.files(); if (!existing_files.isEmpty()) { watcher.removePaths(existing_files); } const QStringList files = source_dir.entryList(QStringList() << "*.qml", QDir::Files, QDir::Name); for (const QString& file : files) { watcher.addPath(source_dir.absoluteFilePath(file)); } } void DestroyRootObjects(QQmlApplicationEngine& engine) { const QList root_objects = engine.rootObjects(); for (QObject* object : root_objects) { object->deleteLater(); } } bool ReloadInnerContent(QQmlApplicationEngine& engine) { const QList root_objects = engine.rootObjects(); if (root_objects.isEmpty()) { return false; } QObject* root = root_objects.constFirst(); return QMetaObject::invokeMethod(root, "reloadInnerContent"); } bool RequiresFullReload(const QFileInfo& changed_file) { return changed_file.fileName() == QStringLiteral("main.qml"); } void InstallTerminationCleanup(QApplication& app) { if (pipe(g_signal_pipe) != 0) { return; } fcntl(g_signal_pipe[0], F_SETFL, fcntl(g_signal_pipe[0], F_GETFL, 0) | O_NONBLOCK); fcntl(g_signal_pipe[1], F_SETFL, fcntl(g_signal_pipe[1], F_GETFL, 0) | O_NONBLOCK); auto* notifier = new QSocketNotifier(g_signal_pipe[0], QSocketNotifier::Read, &app); QObject::connect(notifier, &QSocketNotifier::activated, &app, [&app, notifier](int socket) { notifier->setEnabled(false); char buffer[32]; while (read(socket, buffer, sizeof(buffer)) > 0) { } app.quit(); }); struct sigaction action{}; action.sa_handler = HandleTerminationSignal; sigemptyset(&action.sa_mask); action.sa_flags = 0; sigaction(SIGINT, &action, nullptr); sigaction(SIGTERM, &action, nullptr); } } // namespace int main(int argc, char* argv[]) { std::unique_ptr native_logging = nearby::sharing::linux::NativeFileLogging::Initialize(); QApplication app(argc, argv); app.setApplicationName(QStringLiteral("QuickShare")); app.setOrganizationDomain(QStringLiteral("com.google")); app.setDesktopFileName(QStringLiteral("quickshare")); app.setWindowIcon(QIcon(QStringLiteral(":/icons/quickshare.svg"))); app.setQuitOnLastWindowClosed(false); ApplicationController application_controller(app); if (!application_controller.ClaimSingleInstance()) { return 0; } Backend backend; application_controller.SetBackend(&backend); application_controller.InitializeTray(); QQmlApplicationEngine engine; InstallTerminationCleanup(app); engine.rootContext()->setContextProperty("backend", &backend); engine.rootContext()->setContextProperty("appController", &application_controller); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &application_controller, [&application_controller](QObject* object, const QUrl&) { if (object != nullptr) { application_controller.AttachWindow(object); } }); const int fontId = QFontDatabase::addApplicationFont(":/googlesans_var.ttf"); if (fontId != -1) { const QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId); if (!fontFamilies.isEmpty()) { const QFont defaultFont(fontFamilies.at(0)); app.setFont(defaultFont); } } if (IsHotReloadEnabled()) { const QDir qml_source_dir = QmlSourceDir(); const QUrl source_url = QUrl::fromLocalFile(qml_source_dir.absoluteFilePath("main.qml")); QFileSystemWatcher watcher; QTimer reload_timer; reload_timer.setSingleShot(true); reload_timer.setInterval(75); QString changed_path; const auto fullReload = [&engine, &watcher, qml_source_dir, source_url]() { WatchQmlFiles(watcher, qml_source_dir); DestroyRootObjects(engine); engine.clearComponentCache(); engine.load(source_url); }; const auto reload = [&engine, &watcher, qml_source_dir, source_url, &changed_path, &fullReload]() { WatchQmlFiles(watcher, qml_source_dir); const QFileInfo changed_file(changed_path); if (RequiresFullReload(changed_file) || engine.rootObjects().isEmpty()) { fullReload(); return; } engine.clearComponentCache(); if (!ReloadInnerContent(engine)) { fullReload(); } }; QObject::connect(&reload_timer, &QTimer::timeout, &app, reload); QObject::connect(&watcher, &QFileSystemWatcher::fileChanged, &app, [&reload_timer, &changed_path](const QString& path) { changed_path = path; reload_timer.start(); }); QObject::connect(&watcher, &QFileSystemWatcher::directoryChanged, &app, [&reload_timer, &changed_path](const QString& path) { changed_path = path; reload_timer.start(); }); watcher.addPath(qml_source_dir.absolutePath()); fullReload(); const int result = app.exec(); backend.shutdown(); return result; } // Loaded via the qrc scheme since it's compiled into the binary const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect( &engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject* obj, const QUrl& objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); const int result = app.exec(); backend.shutdown(); return result; }