#include "singletons/NativeMessaging.hpp" #include "Application.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Paths.hpp" #include "util/PostToThread.hpp" #include #include #include #include #include #include #include namespace ipc = boost::interprocess; #ifdef Q_OS_WIN #include #include #include "singletons/WindowManager.hpp" #include "widgets/AttachedWindow.hpp" #endif #include #define EXTENSION_ID "glknmaideaikkmemifbfkhnomoknepka" #define MESSAGE_SIZE 1024 namespace chatterino { void registerNmManifest(Paths &paths, const QString &manifestFilename, const QString ®istryKeyName, const QJsonDocument &document); void registerNmHost(Paths &paths) { if (paths.isPortable()) return; auto getBaseDocument = [&] { QJsonObject obj; obj.insert("name", "com.chatterino.chatterino"); obj.insert("description", "Browser interaction with chatterino."); obj.insert("path", QCoreApplication::applicationFilePath()); obj.insert("type", "stdio"); return obj; }; // chrome { QJsonDocument document; auto obj = getBaseDocument(); QJsonArray allowed_origins_arr = {"chrome-extension://" EXTENSION_ID "/"}; obj.insert("allowed_origins", allowed_origins_arr); document.setObject(obj); registerNmManifest( paths, "/native-messaging-manifest-chrome.json", "HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.chatterino.chatterino", document); } // firefox { QJsonDocument document; auto obj = getBaseDocument(); QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"}; obj.insert("allowed_extensions", allowed_extensions); document.setObject(obj); registerNmManifest( paths, "/native-messaging-manifest-firefox.json", "HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com.chatterino.chatterino", document); } } void registerNmManifest(Paths &paths, const QString &manifestFilename, const QString ®istryKeyName, const QJsonDocument &document) { (void)registryKeyName; // save the manifest QString manifestPath = paths.miscDirectory + manifestFilename; QFile file(manifestPath); file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.write(document.toJson()); file.flush(); #ifdef Q_OS_WIN // clang-format off QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f"); // clang-format on #endif } std::string &getNmQueueName(Paths &paths) { static std::string name = "chatterino_gui" + paths.applicationFilePathHash.toStdString(); return name; } // CLIENT void NativeMessagingClient::sendMessage(const QByteArray &array) { try { ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui"); messageQueue.try_send(array.data(), array.size(), 1); } catch (ipc::interprocess_exception &ex) { qDebug() << "send to gui process:" << ex.what(); } } void NativeMessagingClient::writeToCout(const QByteArray &array) { auto *data = array.data(); auto size = uint32_t(array.size()); std::cout.write(reinterpret_cast(&size), 4); std::cout.write(data, size); std::cout.flush(); } // SERVER void NativeMessagingServer::start() { this->thread.start(); } void NativeMessagingServer::ReceiverThread::run() { ipc::message_queue::remove("chatterino_gui"); ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100, MESSAGE_SIZE); while (true) { try { std::unique_ptr buf(static_cast(malloc(MESSAGE_SIZE))); ipc::message_queue::size_type retSize; unsigned int priority; messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority); QJsonDocument document = QJsonDocument::fromJson(QByteArray::fromRawData(buf.get(), retSize)); this->handleMessage(document.object()); } catch (ipc::interprocess_exception &ex) { qDebug() << "received from gui process:" << ex.what(); } } } void NativeMessagingServer::ReceiverThread::handleMessage(const QJsonObject &root) { auto app = getApp(); QString action = root.value("action").toString(); if (action.isNull()) { qDebug() << "NM action was null"; return; } if (action == "select") { QString _type = root.value("type").toString(); bool attach = root.value("attach").toBool(); QString name = root.value("name").toString(); qDebug() << attach; #ifdef USEWINSDK AttachedWindow::GetArgs args; args.winId = root.value("winId").toString(); args.yOffset = root.value("yOffset").toInt(-1); args.width = root.value("size").toObject().value("width").toInt(-1); args.height = root.value("size").toObject().value("height").toInt(-1); if (_type.isNull() || args.winId.isNull()) { qDebug() << "NM type, name or winId missing"; attach = false; return; } #endif if (_type == "twitch") { postToThread([=] { if (!name.isEmpty()) { app->twitch.server->watchingChannel.update( app->twitch.server->getOrAddChannel(name)); } if (attach) { #ifdef USEWINSDK // if (args.height != -1) { auto *window = AttachedWindow::get(::GetForegroundWindow(), args); if (!name.isEmpty()) { window->setChannel(app->twitch.server->getOrAddChannel(name)); } // } // window->show(); #endif } }); } else { qDebug() << "NM unknown channel type"; } } else if (action == "detach") { QString winId = root.value("winId").toString(); if (winId.isNull()) { qDebug() << "NM winId missing"; return; } #ifdef USEWINSDK postToThread([winId] { qDebug() << "NW detach"; AttachedWindow::detach(winId); }); #endif } else { qDebug() << "NM unknown action " + action; } } } // namespace chatterino