#include "flutter_window.h" #include #include #include #include #include #include #include #include #include #include "com_bridge.h" #include "flutter/generated_plugin_registrant.h" // Plugin onarımı için: bu başlıklar ilgili plugin target'larının INTERFACE // include yollarından gelir (windows/flutter/generated_plugins.cmake). #include #include #include FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} namespace { // com_bridge.cpp'deki LogLine ile aynı dosyaya (exe'nin yanındaki // com_bridge.log) yazan, bu dosyaya özel küçük bir kopyası — masaüstü // algılama zincirini (getWindowDesktop çağrısı → EnumWindows → VDM) teşhis // edebilmek için. void LogDesktopLine(const std::wstring& msg) { wchar_t path[MAX_PATH]; ::GetModuleFileNameW(nullptr, path, MAX_PATH); ::PathRemoveFileSpecW(path); std::wstring logPath = std::wstring(path) + L"\\com_bridge.log"; HANDLE h = ::CreateFileW(logPath.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (h == INVALID_HANDLE_VALUE) return; ::SetFilePointer(h, 0, nullptr, FILE_END); SYSTEMTIME st; ::GetLocalTime(&st); wchar_t line[512]; StringCchPrintfW(line, 512, L"%04d-%02d-%02dT%02d:%02d:%02d %s\r\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, msg.c_str()); int size = ::WideCharToMultiByte(CP_UTF8, 0, line, -1, nullptr, 0, nullptr, nullptr); std::vector utf8(size); ::WideCharToMultiByte(CP_UTF8, 0, line, -1, utf8.data(), size, nullptr, nullptr); DWORD written = 0; ::WriteFile(h, utf8.data(), static_cast(utf8.size() - 1), &written, nullptr); ::CloseHandle(h); } } // namespace // Bazı SDK sürümlerinde CLSID bildirimi yok → manuel tanımla. (Dosyada hem // GetWindowDesktopGuid hem `moveWindowToDesktop` handler'ı kullanıyor.) class DECLSPEC_UUID("aa509086-5ca9-4c25-8f95-6001db6052cc") VirtualDesktopManager; namespace { std::wstring Utf8ToW(const std::string& s) { if (s.empty()) return std::wstring(); int size = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0); if (size <= 0) return std::wstring(); std::wstring out(size, L'\0'); ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), out.data(), size); return out; } const flutter::EncodableValue* FindArg(const flutter::EncodableMap& m, const char* key) { auto it = m.find(flutter::EncodableValue(key)); return it == m.end() ? nullptr : &it->second; } // PID'in görünür, sahipsiz, cloaked olmayan ilk üst seviye penceresi. HWND FindMainWindowOfPid(DWORD pid) { struct Data { DWORD pid; HWND hwnd; } data{pid, nullptr}; EnumWindows( [](HWND hwnd, LPARAM lparam) -> BOOL { auto* d = reinterpret_cast(lparam); DWORD wpid = 0; GetWindowThreadProcessId(hwnd, &wpid); if (wpid != d->pid) return TRUE; if (GetWindow(hwnd, GW_OWNER) != nullptr) return TRUE; BOOL cloaked = FALSE; if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked))) && cloaked) { return TRUE; } if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_VISIBLE)) return TRUE; d->hwnd = hwnd; return FALSE; }, reinterpret_cast(&data)); return data.hwnd; } std::string WToUtf8(const std::wstring& w) { if (w.empty()) return std::string(); int size = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); if (size <= 0) return std::string(); std::string out(size, '\0'); ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), out.data(), size, nullptr, nullptr); return out; } std::string GetStringArg(const flutter::EncodableMap* m, const char* key) { if (!m) return std::string(); const auto* v = FindArg(*m, key); if (!v) return std::string(); if (const auto* s = std::get_if(v)) return *s; return std::string(); } int64_t GetInt64Arg(const flutter::EncodableMap* m, const char* key, int64_t def = 0) { if (!m) return def; const auto* v = FindArg(*m, key); if (!v) return def; if (const auto* i = std::get_if(v)) return *i; if (const auto* l = std::get_if(v)) return *l; return def; } bool GetBoolArg(const flutter::EncodableMap* m, const char* key, bool def) { if (!m) return def; const auto* v = FindArg(*m, key); if (!v) return def; if (const auto* b = std::get_if(v)) return *b; return def; } // --------------------------------------------------------------------------- // Sistem dosya diyalogları (IFileDialog). Ana (STA) thread'de modal olarak // çalışır; diyalog açıkken Flutter/COM mesaj döngüsü akmaya devam eder. // true = dosya seçildi (outPath dolu) // false = kullanıcı vazgeçti ya da hata (hata ise *hrOut FAILED olur). // --------------------------------------------------------------------------- bool ShowFileDialog(HWND owner, bool save, const std::wstring& title, const std::wstring& defaultName, const std::wstring& filterName, const std::wstring& ext, const std::wstring& allLabel, std::wstring* outPath, HRESULT* hrOut) { *hrOut = S_OK; Microsoft::WRL::ComPtr dlg; HRESULT hr = save ? ::CoCreateInstance(__uuidof(FileSaveDialog), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dlg)) : ::CoCreateInstance(__uuidof(FileOpenDialog), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dlg)); if (FAILED(hr) || !dlg) { *hrOut = FAILED(hr) ? hr : E_FAIL; return false; } DWORD opts = 0; dlg->GetOptions(&opts); opts |= FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | (save ? FOS_OVERWRITEPROMPT : FOS_FILEMUSTEXIST); dlg->SetOptions(opts); if (!title.empty()) dlg->SetTitle(title.c_str()); // Filtre metinleri Show() bitene kadar yaşamalı. const std::wstring spec = L"*." + ext; const std::wstring allSpec = L"*.*"; COMDLG_FILTERSPEC filters[2] = { {filterName.c_str(), spec.c_str()}, {allLabel.c_str(), allSpec.c_str()}, }; dlg->SetFileTypes(2, filters); dlg->SetFileTypeIndex(1); if (!ext.empty()) dlg->SetDefaultExtension(ext.c_str()); if (save && !defaultName.empty()) dlg->SetFileName(defaultName.c_str()); hr = dlg->Show(owner); if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) return false; if (FAILED(hr)) { *hrOut = hr; return false; } Microsoft::WRL::ComPtr item; hr = dlg->GetResult(&item); if (FAILED(hr) || !item) { *hrOut = FAILED(hr) ? hr : E_FAIL; return false; } PWSTR p = nullptr; hr = item->GetDisplayName(SIGDN_FILESYSPATH, &p); if (FAILED(hr) || !p) { *hrOut = FAILED(hr) ? hr : E_FAIL; return false; } *outPath = p; ::CoTaskMemFree(p); return true; } // --------------------------------------------------------------------------- // desktop_multi_window alt pencereleri: bulma / boyutlandırma / ortalama. // Plugin alt pencereyi sabit 800x600'de, gerçek "kapat" API'si olmadan // açıyor; burada oturum seçim penceresini kompakt yapıp ekranın ortasına // koyuyoruz ve X'e basılınca yok etmek yerine gizletiyoruz (engine/isolate // yok edilmesin, pencere tekrar kullanılabilsin). // --------------------------------------------------------------------------- constexpr wchar_t kChildWindowClass[] = L"FLUTTER_MULTI_WINDOW_WIN32_WINDOW"; std::vector FindChildFlutterWindows() { std::vector out; ::EnumWindows( [](HWND hwnd, LPARAM lparam) -> BOOL { DWORD pid = 0; ::GetWindowThreadProcessId(hwnd, &pid); if (pid != ::GetCurrentProcessId()) return TRUE; wchar_t cls[96] = {}; if (::GetClassNameW(hwnd, cls, 96) == 0) return TRUE; if (::wcscmp(cls, kChildWindowClass) == 0) { reinterpret_cast*>(lparam)->push_back(hwnd); } return TRUE; }, reinterpret_cast(&out)); return out; } std::map& ChildOrigProcs() { static std::map procs; return procs; } // --------------------------------------------------------------------------- // Masaüstü toast bildirimi (yeni CATIA bağlandığında). Basit Win32 penceresi: // engine'e hiç dokunmaz, kendi WM_TIMER'ı ile süresi bitince yok olur. // --------------------------------------------------------------------------- struct ToastData { std::wstring title; std::wstring subtitle; }; constexpr wchar_t kToastClassName[] = L"FikoToastWnd"; LRESULT CALLBACK ToastWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { auto* data = reinterpret_cast( ::GetWindowLongPtrW(hwnd, GWLP_USERDATA)); switch (msg) { case WM_CREATE: { auto* cs = reinterpret_cast(lp); ::SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(cs->lpCreateParams)); return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = ::BeginPaint(hwnd, &ps); RECT rc; ::GetClientRect(hwnd, &rc); // Arka plan: koyu, hafif saydam katman (layered window alpha ile). HBRUSH bg = ::CreateSolidBrush(RGB(20, 23, 28)); ::FillRect(hdc, &rc, bg); ::DeleteObject(bg); // Sol kenarda amber vurgu şeridi. RECT strip = {0, 0, 3, rc.bottom}; HBRUSH amber = ::CreateSolidBrush(RGB(255, 184, 77)); ::FillRect(hdc, &strip, amber); ::DeleteObject(amber); ::SetBkMode(hdc, TRANSPARENT); RECT trc = {12, 7, rc.right - 8, rc.bottom - 6}; HFONT bold = ::CreateFontW(-15, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI"); HFONT old = (HFONT)::SelectObject(hdc, bold); ::SetTextColor(hdc, RGB(240, 242, 245)); ::DrawTextW(hdc, data ? data->title.c_str() : L"", -1, &trc, DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS); ::SelectObject(hdc, old); ::DeleteObject(bold); if (data && !data->subtitle.empty()) { RECT src = {12, trc.top + 19, rc.right - 8, rc.bottom - 5}; HFONT smallFont = ::CreateFontW( -13, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI"); old = (HFONT)::SelectObject(hdc, smallFont) ::SetTextColor(hdc, RGB(150, 158, 168)); ::DrawTextW(hdc, data->subtitle.c_str(), -1, &src, DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS); ::SelectObject(hdc, old); ::DeleteObject(smallFont); } ::EndPaint(hwnd, &ps); return 0; } case WM_TIMER: if (wp == 1) { ::KillTimer(hwnd, 1); ::DestroyWindow(hwnd); } return 0; case WM_NCDESTROY: delete data; ::SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); return 0; } return ::DefWindowProcW(hwnd, msg, wp, lp); } void ShowToast(const std::wstring& title, const std::wstring& subtitle, int durationMs) { static bool classRegistered = false; if (!classRegistered) { WNDCLASSEXW wc = {}; wc.cbSize = sizeof(wc); wc.lpfnWndProc = &ToastWndProc; wc.hInstance = ::GetModuleHandleW(nullptr); wc.lpszClassName = kToastClassName; wc.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); if (::RegisterClassExW(&wc) == 0 && ::GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { return; } classRegistered = true; } // Çalışma alanında, sağ altta (tray'in üstünde) konumlandır. RECT work = {}; ::SystemParametersInfoW(SPI_GETWORKAREA, 0, &work, 0); const int w = 250, h = 54; const int x = work.right - w - 12; const int y = work.bottom - h - 12; auto* data = new ToastData{title, subtitle}; HWND hwnd = ::CreateWindowExW( WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE, kToastClassName, L"", WS_POPUP, x, y, w, h, nullptr, nullptr, ::GetModuleHandleW(nullptr), data); if (!hwnd) { delete data; return; } ::SetLayeredWindowAttributes(hwnd, 0, 242, LWA_ALPHA); ::ShowWindow(hwnd, SW_SHOWNOACTIVATE); ::SetTimer(hwnd, 1, durationMs < 500 ? 500 : (durationMs > 10000 ? 10000 : durationMs), nullptr); } // PID'e ait ana pencerenin başlığını değiştirir (CATIA başlıklarını // FikoCATIA'daki özel isimle eşitlemek için). WM_SETTEXT cross-process // çalışır; SendMessageTimeout ile CATIA donarsa bile biz kilitlemeyiz. bool SetWindowTitleOfPid(DWORD pid, const std::wstring& title) { HWND hwnd = FindMainWindowOfPid(pid); if (!hwnd) return false; const std::wstring text = title.empty() ? L"CATIA V5" : title; ::SendMessageTimeoutW(hwnd, WM_SETTEXT, 0, reinterpret_cast(text.c_str()), SMTO_ABORTIFHUNG, 1500, nullptr); return true; } LRESULT CALLBACK HideOnCloseProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { if (msg == WM_CLOSE) { ::ShowWindow(hwnd, SW_HIDE); return 0; } auto it = ChildOrigProcs().find(hwnd); if (it == ChildOrigProcs().end()) { return ::DefWindowProcW(hwnd, msg, wp, lp); } WNDPROC orig = it->second; if (msg == WM_NCDESTROY) ChildOrigProcs().erase(it); return ::CallWindowProcW(orig, hwnd, msg, wp, lp); } bool SetupChildWindow(HWND hwnd, int width, int height, const std::wstring& title, bool show) { if (!hwnd || !::IsWindow(hwnd)) return false; // X'e basınca pencereyi yok etme, gizle (yalnızca bir kez bağla). if (ChildOrigProcs().find(hwnd) == ChildOrigProcs().end()) { WNDPROC orig = reinterpret_cast(::SetWindowLongPtrW( hwnd, GWLP_WNDPROC, reinterpret_cast(&HideOnCloseProc))); if (orig) ChildOrigProcs()[hwnd] = orig; } // Sabit boyutlu, kompakt bir pencere: büyüt/küçült/boyutlandır yok. LONG_PTR style = ::GetWindowLongPtrW(hwnd, GWL_STYLE); style &= ~static_cast(WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME); ::SetWindowLongPtrW(hwnd, GWL_STYLE, style); ::SetWindowTextW(hwnd, title.c_str()); UINT dpi = ::GetDpiForWindow(hwnd); if (dpi == 0) dpi = 96; RECT rc = {0, 0, ::MulDiv(width, static_cast(dpi), 96), ::MulDiv(height, static_cast(dpi), 96)}; ::AdjustWindowRectExForDpi( &rc, static_cast(style), FALSE, static_cast(::GetWindowLongPtrW(hwnd, GWL_EXSTYLE)), dpi); const int w = rc.right - rc.left; const int h = rc.bottom - rc.top; // Ana ekranın çalışma alanında ortala (tray popup'ı da ana ekranda açılıyor). HMONITOR mon = ::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY); MONITORINFO mi = {}; mi.cbSize = sizeof(mi); int x = 100, y = 100; if (::GetMonitorInfoW(mon, &mi)) { x = mi.rcWork.left + ((mi.rcWork.right - mi.rcWork.left) - w) / 2; y = mi.rcWork.top + ((mi.rcWork.bottom - mi.rcWork.top) - h) / 2; } ::SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED | (show ? SWP_SHOWWINDOW : 0)); if (show) { ::ShowWindow(hwnd, SW_SHOWNORMAL); ::SetForegroundWindow(hwnd); } return true; } } // namespace bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); // ÖNEMLİ (multi-window): desktop_multi_window, alt pencerelerin plugin // kayıtlarını KENDİSİ yapar (multi_window_manager.cc -> // InternalMultiWindowPluginRegisterWithRegistrar, yalnızca // DesktopMultiWindowPlugin). Burada `DesktopMultiWindowSetWindowCreatedCallback` // içinde tekrar RegisterPlugins çağırmak şu hataları üretiyordu: // 1) DesktopMultiWindowPlugin child HWND için ikinci kez kaydedilir, // wrapper iki kez sahiplenilir/destroy edilir -> 0xc0000005 access // violation (uygulama çöker). // 2) window_manager / tray_manager / screen_retriever native tarafındaki // global `channel` değişkenleri child engine'in messenger'ı ile ezilir // -> ana pencereden yapılan windowManager.show()/hide()/isVisible() // çağrıları yanıtsız (asılı) kalır, tray click event'leri ana pencereye // ulaşmaz -> sağ alttaki popup bir daha açılmaz. // Bu yüzden child pencereler için callback'i hiç kaydetmiyoruz; ana pencere // plugin kayıtları bozulmadan kalır. SetChildContent(flutter_controller_->view()->GetNativeWindow()); // `fikocatia/native` kanalı: Dart tarafından plugin onarımı istemek için // (ve CATIA COM bridge push/senkron sorgu için de aynı kanal kullanılır). RegisterPluginRepairChannel(); // COM bridge'e (com_bridge.h) her RegisterInstance/UpdateStatus/ // Unregister sonrası Dart'a `instancesSnapshot` metodunu push etmesi // için callback ver. fire-and-forget InvokeMethod — sonucu beklemiyoruz. fikocatia_com::SetSnapshotCallback( [this](const flutter::EncodableValue& snapshot) { if (plugin_repair_channel_) { plugin_repair_channel_->InvokeMethod( "instancesSnapshot", std::make_unique(snapshot)); } }); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; } void FlutterWindow::OnDestroy() { // Engine yok edilmeden önce callback'i temizle — aksi halde pencere // kapandıktan sonra gelen bir COM çağrısı (nadir ama mümkün) artık var // olmayan `this`'i yakalamış lambda'yı tetikleyebilir. fikocatia_com::SetSnapshotCallback(nullptr); if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. 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); } void FlutterWindow::RegisterPluginRepairChannel() { plugin_repair_channel_ = std::make_unique>( flutter_controller_->engine()->messenger(), "fikocatia/native", &flutter::StandardMethodCodec::GetInstance()); plugin_repair_channel_->SetMethodCallHandler( [this](const flutter::MethodCall& call, std::unique_ptr> result) { if (call.method_name() == "repairPluginChannels") { RepairPluginChannels(); result->Success(flutter::EncodableValue(true)); return; } if (call.method_name() == "getWindowDesktop") { // Argüman: {"pid": int}. PID'in ana görünür penceresinin bulunduğu // sanal masaüstünün GUID'ini string olarak döner. const auto* args = std::get_if(call.arguments()); std::string guid; int pid = 0; if (args) { auto it = args->find(flutter::EncodableValue("pid")); if (it != args->end()) { if (const auto* pidPtr = std::get_if(&it->second)) { pid = *pidPtr; guid = GetWindowDesktopGuid(static_cast(pid)); } } } { wchar_t log[128]; StringCchPrintfW(log, 128, L"getWindowDesktop pid=%d -> %s", pid, guid.empty() ? L"(empty)" : L"(guid)"); LogDesktopLine(log); } if (guid.empty()) { result->Success(flutter::EncodableValue()); } else { result->Success(flutter::EncodableValue(guid)); } return; } if (call.method_name() == "getInstancesSnapshot") { // Dart tarafı açılışta (ilk push henüz gelmeden önce) mevcut // CATIA instance listesini bir kerelik senkron sorgular. result->Success(fikocatia_com::GetCurrentSnapshot()); return; } if (call.method_name() == "openDocuments") { // Argüman: {"instanceId": String, "paths": [String], // "activate": String?}. CATIA'ya döküman açma işini izole bir // worker thread'de başlatır, jobId döner (<0 = hata). const auto* args = std::get_if(call.arguments()); if (!args) { result->Error("bad_args", "openDocuments: argüman yok"); return; } std::wstring instanceId; std::wstring activate; std::vector paths; if (const auto* v = FindArg(*args, "instanceId")) { if (const auto* s = std::get_if(v)) { instanceId = Utf8ToW(*s); } } if (const auto* v = FindArg(*args, "activate")) { if (const auto* s = std::get_if(v)) { activate = Utf8ToW(*s); } } if (const auto* v = FindArg(*args, "paths")) { if (const auto* list = std::get_if(v)) { for (const auto& e : *list) { if (const auto* s = std::get_if(&e)) { paths.push_back(Utf8ToW(*s)); } } } } result->Success(flutter::EncodableValue( fikocatia_com::StartOpenDocumentsJob(instanceId, paths, activate))); return; } if (call.method_name() == "getOpenJob") { const auto* args = std::get_if(call.arguments()); int jobId = 0; if (args) { if (const auto* v = FindArg(*args, "jobId")) { if (const auto* i = std::get_if(v)) jobId = *i; } } result->Success(fikocatia_com::GetOpenDocumentsJob(jobId)); return; } if (call.method_name() == "moveWindowToDesktop") { // Argüman: {"pid": int, "guid": String}. PID'in ana penceresini // verilen sanal masaüstüne taşımayı dener; HRESULT (int) döner // (0 = başarılı). NOT: IVirtualDesktopManager::MoveWindowToDesktop // başka bir process'in penceresi için E_ACCESSDENIED // (0x80070005) dönebilir — Windows'un belgelenmiş kısıtı. const auto* args = std::get_if(call.arguments()); int pid = 0; std::wstring guidStr; if (args) { if (const auto* v = FindArg(*args, "pid")) { if (const auto* i = std::get_if(v)) pid = *i; } if (const auto* v = FindArg(*args, "guid")) { if (const auto* s = std::get_if(v)) { guidStr = Utf8ToW(*s); } } } HRESULT hrMove = E_FAIL; GUID guid{}; HWND hwnd = pid > 0 ? FindMainWindowOfPid(static_cast(pid)) : nullptr; if (!hwnd) { hrMove = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); } else if (FAILED(::CLSIDFromString(guidStr.c_str(), &guid))) { hrMove = E_INVALIDARG; } else { Microsoft::WRL::ComPtr vdm; hrMove = CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&vdm)); if (SUCCEEDED(hrMove) && vdm) { hrMove = vdm->MoveWindowToDesktop(hwnd, guid); } } { wchar_t log[160]; StringCchPrintfW(log, 160, L"moveWindowToDesktop pid=%d -> hr=0x%08X", pid, static_cast(hrMove)); LogDesktopLine(log); } result->Success(flutter::EncodableValue(static_cast(hrMove))); return; } if (call.method_name() == "pickSaveFile" || call.method_name() == "pickOpenFile") { // Argüman: {"title","defaultName","filterName","ext","allLabel"}. // Sistem Kaydet/Aç diyaloğu; seçilen yol (String) ya da null // (vazgeçildi). Diyalog modal: kullanıcı seçim yapana kadar bu // çağrı cevap vermez, Dart tarafı bu yüzden zaman aşımı koymaz. const bool save = call.method_name() == "pickSaveFile"; const auto* args = std::get_if(call.arguments()); std::wstring path; HRESULT hrDlg = S_OK; const bool picked = ShowFileDialog( GetHandle(), save, Utf8ToW(GetStringArg(args, "title")), Utf8ToW(GetStringArg(args, "defaultName")), Utf8ToW(GetStringArg(args, "filterName")), Utf8ToW(GetStringArg(args, "ext")), Utf8ToW(GetStringArg(args, "allLabel")), &path, &hrDlg); { wchar_t log[160]; StringCchPrintfW(log, 160, L"%s hr=0x%08X picked=%d", save ? L"pickSaveFile" : L"pickOpenFile", static_cast(hrDlg), picked ? 1 : 0); LogDesktopLine(log); } if (FAILED(hrDlg)) { wchar_t msg[64]; StringCchPrintfW(msg, 64, L"hr=0x%08X", static_cast(hrDlg)); result->Error("dialog_failed", WToUtf8(msg)); return; } if (!picked) { result->Success(flutter::EncodableValue()); } else { result->Success(flutter::EncodableValue(WToUtf8(path))); } return; } if (call.method_name() == "listChildWindows") { // desktop_multi_window alt pencerelerinin HWND listesi (int64). flutter::EncodableList list; for (HWND h : FindChildFlutterWindows()) { list.push_back(flutter::EncodableValue( static_cast(reinterpret_cast(h)))); } result->Success(flutter::EncodableValue(list)); return; } if (call.method_name() == "setupChildWindow") { // Argüman: {"hwnd": int, "width": int, "height": int, // "title": String, "show": bool}. Alt pencereyi kompakt boyuta // getirir, ana ekranın ortasına koyar, X'i "gizle"ye çevirir. const auto* args = std::get_if(call.arguments()); HWND h = reinterpret_cast( static_cast(GetInt64Arg(args, "hwnd"))); const bool ok = SetupChildWindow( h, static_cast(GetInt64Arg(args, "width", 360)), static_cast(GetInt64Arg(args, "height", 520)), Utf8ToW(GetStringArg(args, "title")), GetBoolArg(args, "show", true)); result->Success(flutter::EncodableValue(ok)); return; } if (call.method_name() == "showToast") { // Argüman: {"title": String, "subtitle": String, "durationMs": int}. const auto* args = std::get_if(call.arguments()); ShowToast(Utf8ToW(GetStringArg(args, "title")), Utf8ToW(GetStringArg(args, "subtitle")), static_cast(GetInt64Arg(args, "durationMs", 2000))); result->Success(flutter::EncodableValue(true)); return; } if (call.method_name() == "setInstanceWindowTitle") { // Argüman: {"pid": int, "title": String}. Boş title → "CATIA V5". const auto* args = std::get_if(call.arguments()); const bool ok = SetWindowTitleOfPid( static_cast(GetInt64Arg(args, "pid", 0)), Utf8ToW(GetStringArg(args, "title"))); result->Success(flutter::EncodableValue(ok)); return; } if (call.method_name() == "setPollInterval") { // Argüman: {"intervalMs": int}. CATIA açık/aktif döküman izleme // aralığı (ms); 0 = kapalı. Merkezi ayarlar penceresinden gelir. const auto* args = std::get_if(call.arguments()); fikocatia_com::SetPollIntervalMs( static_cast(GetInt64Arg(args, "intervalMs", 1500))); result->Success(flutter::EncodableValue(true)); return; } result->NotImplemented(); }); } void FlutterWindow::RepairPluginChannels() { if (!flutter_controller_ || !flutter_controller_->engine()) { return; } // Plugin'lerin native tarafındaki kanal değişkenleri, alt pencere // açıldığında child engine'in messenger'ına kaymış olabilir. Bu durumda // ana pencereden yapılan çağrılar (window_manager) yanıtsız kalır ve tray // event'i gönderilirken yanlış messenger'a erişildiği için uygulama // 0xc0000005 ile çöker. Aşağıdaki yeniden kayıt, kanalları ana engine'in // messenger'ına geri bağlar. auto* registry = flutter_controller_->engine(); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); TrayManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("TrayManagerPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); } // --------------------------------------------------------------------------- // Sanal masaüstü algılama (IVirtualDesktopManager). // PID'in görünür ana penceresini bulur ve hangi sanal masaüstünde // olduğunu GUID string olarak döner. Windows 10 1809+ üzerinde çalışır. // --------------------------------------------------------------------------- std::string FlutterWindow::GetWindowDesktopGuid(DWORD pid) { // 1) PID'e ait görünür (cloaked olmayan) üst seviye pencereyi bul. struct EnumData { DWORD pid; HWND hwnd; int candidatesSeen = 0; // pid eşleşen (owner/cloaked/visible filtresinden // ÖNCE) toplam pencere sayısı — teşhis için. int rejectedCloakCheckFailed = 0; // DWM sorgusu başarısız olduğu için // eskiden yanlışlıkla atlanan pencere. }; EnumData data{pid, nullptr}; WNDENUMPROC proc = [](HWND hwnd, LPARAM lparam) -> BOOL { auto* d = reinterpret_cast(lparam); DWORD pid = 0; GetWindowThreadProcessId(hwnd, &pid); if (pid != d->pid) return TRUE; d->candidatesSeen++; if (GetWindow(hwnd, GW_OWNER) != nullptr) return TRUE; // Cloaked (UWP askıda) pencereleri ele. DWM sorgusu BAŞARISIZ olursa // (ör. RDP/uzak masaüstü oturumlarında, DWM composition kapalıyken) // artık pencereyi "cloaked" SAYMIYORUZ — eskiden bu durumda pencere // yanlışlıkla atlanıp masaüstü hiç bulunamıyordu. BOOL cloaked = FALSE; HRESULT hrCloak = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked)); if (FAILED(hrCloak)) { d->rejectedCloakCheckFailed++; } else if (cloaked) { return TRUE; } LONG style = GetWindowLong(hwnd, GWL_STYLE); if (!(style & WS_VISIBLE)) return TRUE; d->hwnd = hwnd; return FALSE; // bulduk, dur. }; EnumWindows(proc, reinterpret_cast(&data)); if (!data.hwnd) { wchar_t log[160]; StringCchPrintfW( log, 160, L"GetWindowDesktopGuid pid=%lu: pencere BULUNAMADI (aday=%d, " L"dwmFail=%d)", pid, data.candidatesSeen, data.rejectedCloakCheckFailed); LogDesktopLine(log); return ""; } // 2) Pencerenin masaüstünü IVirtualDesktopManager ile sor. std::string guid_str; Microsoft::WRL::ComPtr vdm; HRESULT hr = CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&vdm)); if (FAILED(hr) || !vdm) { wchar_t log[128]; StringCchPrintfW(log, 128, L"GetWindowDesktopGuid pid=%lu: CoCreateInstance(VDM) " L"FAILED hr=0x%08X", pid, hr); LogDesktopLine(log); return ""; } GUID guid; hr = vdm->GetWindowDesktopId(data.hwnd, &guid); if (FAILED(hr)) { wchar_t log[128]; StringCchPrintfW(log, 128, L"GetWindowDesktopGuid pid=%lu: GetWindowDesktopId " L"FAILED hr=0x%08X", pid, hr); LogDesktopLine(log); return ""; } wchar_t buf[64] = {}; StringFromGUID2(guid, buf, 64); std::wstring w(buf); int size = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr); std::vector out(size); WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, out.data(), size, nullptr, nullptr); guid_str.assign(out.data()); wchar_t log[192]; StringCchPrintfW(log, 192, L"GetWindowDesktopGuid pid=%lu: OK guid=%s", pid, buf); LogDesktopLine(log); return guid_str; }