#include "flutter_window.h" #include #include #include #include #include #include #include #include #include #include #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} // ─── Input injection helper ────────────────────────────────────────────────── static void HandleInjectInput(const flutter::EncodableMap& args) { auto typeIt = args.find(flutter::EncodableValue("type")); if (typeIt == args.end()) return; std::string type = std::get(typeIt->second); if (type == "move") { double nx = std::get(args.at(flutter::EncodableValue("x"))); double ny = std::get(args.at(flutter::EncodableValue("y"))); INPUT input = {}; input.type = INPUT_MOUSE; input.mi.dx = (LONG)(nx * 65535); input.mi.dy = (LONG)(ny * 65535); input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; SendInput(1, &input, sizeof(INPUT)); } else if (type == "click") { double nx = std::get(args.at(flutter::EncodableValue("x"))); double ny = std::get(args.at(flutter::EncodableValue("y"))); int btn = 0; auto btnIt = args.find(flutter::EncodableValue("button")); if (btnIt != args.end()) btn = std::get(btnIt->second); INPUT moveInput = {}; moveInput.type = INPUT_MOUSE; moveInput.mi.dx = (LONG)(nx * 65535); moveInput.mi.dy = (LONG)(ny * 65535); moveInput.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; SendInput(1, &moveInput, sizeof(INPUT)); INPUT clickInput[2] = {}; clickInput[0].type = INPUT_MOUSE; clickInput[1].type = INPUT_MOUSE; if (btn == 1) { clickInput[0].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; clickInput[1].mi.dwFlags = MOUSEEVENTF_RIGHTUP; } else { clickInput[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; clickInput[1].mi.dwFlags = MOUSEEVENTF_LEFTUP; } SendInput(2, clickInput, sizeof(INPUT)); } else if (type == "keydown" || type == "keyup") { auto keyIt = args.find(flutter::EncodableValue("keyCode")); if (keyIt == args.end()) return; // Flutter logical key IDs dont map 1:1 to VKs — use low byte as rough VK int logicalKey = std::get(keyIt->second); WORD vk = (WORD)(logicalKey & 0xFF); INPUT keyInput = {}; keyInput.type = INPUT_KEYBOARD; keyInput.ki.wVk = vk; if (type == "keyup") keyInput.ki.dwFlags = KEYEVENTF_KEYUP; SendInput(1, &keyInput, sizeof(INPUT)); } } // ─── OnCreate ──────────────────────────────────────────────────────────────── bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) return false; MFStartup(MF_VERSION); RECT frame = GetClientArea(); flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); if (!flutter_controller_->engine() || !flutter_controller_->view()) return false; RegisterPlugins(flutter_controller_->engine()); auto* messenger = flutter_controller_->engine()->messenger(); // Get the texture registrar via the C API (avoids linking flutter_wrapper_plugin). auto reg_ref = flutter_controller_->engine()->GetRegistrarForPlugin("pulsar"); tex_reg_ = FlutterDesktopRegistrarGetTextureRegistrar(reg_ref); // ── com.pulsar/input MethodChannel ────────────────────────────────────── auto input_ch = std::make_shared>( messenger, "com.pulsar/input", &flutter::StandardMethodCodec::GetInstance()); input_ch->SetMethodCallHandler( [](const flutter::MethodCall& call, std::unique_ptr> result) { if (call.method_name() == "injectInput") { if (const auto* args = std::get_if(call.arguments())) HandleInjectInput(*args); result->Success(); } else { result->NotImplemented(); } }); // ── com.pulsar/video/frames EventChannel ──────────────────────────────── screen_encoder_ = std::make_unique(); auto* enc = screen_encoder_.get(); // raw ptr safe: outlives channels auto frame_ch = std::make_shared>( messenger, "com.pulsar/video/frames", &flutter::StandardMethodCodec::GetInstance()); frame_ch->SetStreamHandler( std::make_unique>( [enc](const flutter::EncodableValue*, std::unique_ptr>&& sink) -> std::unique_ptr> { enc->Start(std::move(sink)); return nullptr; }, [enc](const flutter::EncodableValue*) -> std::unique_ptr> { enc->Stop(); return nullptr; })); // ── com.pulsar/video MethodChannel ────────────────────────────────────── auto* decoders = &decoders_; // raw ptr safe: outlives channels auto video_ch = std::make_shared>( messenger, "com.pulsar/video", &flutter::StandardMethodCodec::GetInstance()); video_ch->SetMethodCallHandler( [enc, decoders, this]( const flutter::MethodCall& call, std::unique_ptr> result) { const auto& m = call.method_name(); if (m == "start") { result->Success(); } else if (m == "stop") { enc->Stop(); result->Success(); } else if (m == "forceKeyframe") { enc->ForceKeyframe(); result->Success(); } else if (m == "createDecoder") { const auto* args = std::get_if(call.arguments()); if (!args) { result->Error("BAD_ARGS", ""); return; } auto it = args->find(flutter::EncodableValue("spsPps")); if (it == args->end()) { result->Error("BAD_ARGS", "missing spsPps"); return; } const auto& sps_pps = std::get>(it->second); auto dec = std::make_unique( tex_reg_, sps_pps.data(), sps_pps.size()); if (!dec->is_valid()) { result->Error("INIT_FAILED", "decoder init failed"); return; } int64_t id = dec->texture_id(); (*decoders)[id] = std::move(dec); result->Success(flutter::EncodableValue(flutter::EncodableMap{ {flutter::EncodableValue("textureId"), flutter::EncodableValue(id)}})); } else if (m == "stopDecoder") { const auto* args = std::get_if(call.arguments()); if (args) { auto it = args->find(flutter::EncodableValue("textureId")); if (it != args->end()) decoders->erase(std::get(it->second)); } result->Success(); } else if (m == "feedNal") { const auto* args = std::get_if(call.arguments()); if (!args) { result->Error("BAD_ARGS", ""); return; } auto id_it = args->find(flutter::EncodableValue("textureId")); auto nal_it = args->find(flutter::EncodableValue("nal")); if (id_it == args->end() || nal_it == args->end()) { result->Error("BAD_ARGS", "missing args"); return; } int64_t id = std::get(id_it->second); const auto& nal = std::get>(nal_it->second); auto dit = decoders->find(id); if (dit != decoders->end()) dit->second->FeedNal(nal.data(), nal.size()); result->Success(); } else { result->NotImplemented(); } }); SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); flutter_controller_->ForceRedraw(); return true; } // ─── OnDestroy ─────────────────────────────────────────────────────────────── void FlutterWindow::OnDestroy() { decoders_.clear(); screen_encoder_.reset(); if (flutter_controller_) flutter_controller_ = nullptr; MFShutdown(); Win32Window::OnDestroy(); } // ─── MessageHandler ────────────────────────────────────────────────────────── LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) return *result; } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); }