#include "fiko/platform/pencere.hpp" #include #include // DragAcceptFiles / DragQueryFileW / DragFinish #include // GET_X_LPARAM / GET_Y_LPARAM (WM_POINTER ekran koordinati) #include // DWM bulaniklik (glass pencere) #include "fiko/core/gunluk.hpp" #include "fiko/core/kare_istek.hpp" #include #include namespace fiko { bool& surukleCiz() { static bool a = true; // eski davranış return a; } namespace { constexpr const wchar_t* sinifAdi = L"FikoYTPencere"; // wchar (UTF-16) → std::string (UTF-8). dosya_islemleri.cpp ile ayni desen. std::string wcharUtf8(const wchar_t* w) { if (!w || !*w) return {}; const int bayt = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); if (bayt <= 1) return {}; std::string s(bayt - 1, 0); WideCharToMultiByte(CP_UTF8, 0, w, -1, s.data(), bayt, nullptr, nullptr); return s; } } struct PencereYordam { static LRESULT CALLBACK yordam(HWND hwnd, UINT mesaj, WPARAM w, LPARAM l) { auto* p = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); // On-demand render: kullanıcı girdisi/pencere değişimi → birkaç kare çiz (etkileşim // kuyruğu). Boşta hiç girdi gelmeyince ana döngü uyur → GPU/pil tasarrufu. switch (mesaj) { case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_LBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: case WM_KEYDOWN: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_CHAR: case WM_SIZE: case WM_SETFOCUS: case WM_KILLFOCUS: case WM_DPICHANGED: case WM_DROPFILES: case WM_PAINT: case WM_ACTIVATE: case WM_POINTERDOWN: case WM_POINTERUPDATE: case WM_POINTERUP: kareIste(6); break; } // Dokunmatik/kalem (WM_POINTER, Win8+). Uygulama TUKETIRSE (true) fare // yukseltmesi olmaz → saf jest yolu. Tuketmezse DefWindowProc'a duser ve // Windows dokunuşu fare mesajina cevirir = ESKI davranis (ImGui panelleri). // NOT: EnableMouseInPointer CAGIRILMAZ — fare mesaj yolu (nav_kip CATIA semasi) // aynen legacy WM_MOUSE* olarak akar, tek satiri degismez. switch (mesaj) { case WM_POINTERDOWN: case WM_POINTERUPDATE: case WM_POINTERUP: case WM_POINTERCAPTURECHANGED: { if (!p || !p->dokunmaOlayi) break; const u32 pkimlik = GET_POINTERID_WPARAM(w); POINTER_INPUT_TYPE ptip = PT_POINTER; if (!GetPointerType(pkimlik, &ptip)) break; // Fare/touchpad de pointer mesaji uretebilir (EnableMouseInPointer acikken); // biz yalniz parmak/kalemi ele aliriz, fare ESKI yoldan aksin. if (ptip != PT_TOUCH && ptip != PT_PEN) break; DokunmaOlay o; o.kimlik = pkimlik; o.kalem = (ptip == PT_PEN); if (o.kalem) { POINTER_PEN_INFO kalemBilgi{}; if (GetPointerPenInfo(pkimlik, &kalemBilgi)) { o.govdeTusu = (kalemBilgi.penFlags & PEN_FLAG_BARREL) != 0; o.silgi = (kalemBilgi.penFlags & (PEN_FLAG_INVERTED | PEN_FLAG_ERASER)) != 0; } } // WM_POINTER* konumu EKRAN uzayindadir (WM_MOUSE*'in aksine) → pencere-yerele çevir. POINT nk{GET_X_LPARAM(l), GET_Y_LPARAM(l)}; ScreenToClient(hwnd, &nk); o.x = (f32)nk.x; o.y = (f32)nk.y; o.tur = (mesaj == WM_POINTERDOWN) ? DokunmaOlayTur::Bas : (mesaj == WM_POINTERUPDATE) ? DokunmaOlayTur::Hareket : (mesaj == WM_POINTERUP) ? DokunmaOlayTur::Birak : DokunmaOlayTur::Iptal; // WM_POINTERDOWN alan pencere o isaretci icin ORTUK capture alir (fare gibi // SetCapture gerekmez) → parmak pencere disina taşsa da UPDATE/UP gelir. if (p->dokunmaOlayi(o)) return 0; break; } } if (p && p->hamMesaj && p->hamMesaj(hwnd, mesaj, w, l)) return 1; switch (mesaj) { case WM_MOUSEMOVE: if (p && p->fareHareket) p->fareHareket((f32)(i16)LOWORD(l), (f32)(i16)HIWORD(l)); return 0; case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: { if (p && p->fareDugme) { const int dugme = (mesaj == WM_LBUTTONDOWN || mesaj == WM_LBUTTONUP) ? 0 : (mesaj == WM_MBUTTONDOWN || mesaj == WM_MBUTTONUP) ? 1 : 2; const bool basildi = (mesaj == WM_LBUTTONDOWN || mesaj == WM_MBUTTONDOWN || mesaj == WM_RBUTTONDOWN); if (basildi) SetCapture(hwnd); else ReleaseCapture(); p->fareDugme(dugme, basildi, (f32)(i16)LOWORD(l), (f32)(i16)HIWORD(l)); } return 0; } case WM_MOUSEWHEEL: if (p && p->fareTeker) p->fareTeker((f32)GET_WHEEL_DELTA_WPARAM(w) / (f32)WHEEL_DELTA); return 0; case WM_LBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case WM_RBUTTONDBLCLK: { if (p && p->fareCiftTik) { const int dugme = (mesaj == WM_LBUTTONDBLCLK) ? 0 : (mesaj == WM_MBUTTONDBLCLK) ? 1 : 2; p->fareCiftTik(dugme, (f32)(i16)LOWORD(l), (f32)(i16)HIWORD(l)); } return 0; } case WM_KEYDOWN: if (p && p->tusBasildi && !(l & (1 << 30))) // tekrar degil, ilk basis p->tusBasildi((u32)w); return 0; case WM_DROPFILES: { // Explorer'dan surukle-birak: birakilirsa TUM dosyalari sirayla ac. HDROP hDrop = reinterpret_cast(w); const UINT adet = DragQueryFileW(hDrop, 0xFFFFFFFFu, nullptr, 0); if (p && p->dosyaBirakildi) { std::vector tampon; for (UINT i = 0; i < adet; ++i) { const UINT uzun = DragQueryFileW(hDrop, i, nullptr, 0); // NUL haric if (uzun == 0) continue; tampon.assign(uzun + 1u, L'\0'); if (DragQueryFileW(hDrop, i, tampon.data(), uzun + 1u)) { const std::string yol = wcharUtf8(tampon.data()); if (!yol.empty()) p->dosyaBirakildi(yol); } } } DragFinish(hDrop); return 0; } case WM_NCCREATE: { auto* cs = reinterpret_cast(l); SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(cs->lpCreateParams)); break; } case WM_SIZE: if (p) { p->genislik_ = LOWORD(l); p->yukseklik_ = HIWORD(l); // Win+D / görev çubuğundan küçültme sırasında Windows 0×0 istemci alanı // bildirir. Bu anda swapchain yeniden kurmak veya senkron kare çizmek WSI // geçişiyle yarışıp VK_ERROR_DEVICE_LOST'a yol açabilir. Geri yüklemede gelen // normal WM_SIZE hem yeniden kurma bayrağını koyar hem de yeni kareyi çizer. if (w == SIZE_MINIMIZED || p->simgeDurumundaMi()) return 0; if (p->boyutDegisti) p->boyutDegisti(p->genislik_, p->yukseklik_); // Modal resize döngüsü içindeyken de anlık yeniden çiz → boyutlanma akıcı // (WM_TIMER'a ek: sürüklerken her boyut mesajında bir kare). // `render.surukle_ciz` KAPALI ise bu senkron çizim atlanır: kare acquire // semaforunu beklerken WM_SIZE'ı gönderen taraf da bloke oluyordu (ölçüldü). if (p->kareCiz && (surukleCiz() || !p->boyutlaniyor_)) p->kareCiz(); } return 0; case WM_ENTERSIZEMOVE: // Kullanıcı kenardan tutup sürüklemeye başladı → Windows MODAL mesaj döngüsüne // girer, uygulamanın ana döngüsü (mesajPompala+kareCalistir) DURUR. Timer kur: // WM_TIMER modal döngü içinde gelir → her ateşlemede bir kare çizeriz. // boyutlaniyor_: sürükleme boyunca offscreen viewport/secim resize'ı ertelenir. if (p) p->boyutlaniyor_ = true; SetTimer(hwnd, 0xF1C0 /*resize-pump timer*/, USER_TIMER_MINIMUM, nullptr); return 0; case WM_EXITSIZEMOVE: KillTimer(hwnd, 0xF1C0); // Sürükleme bitti → bir sonraki kare offscreen hedefi tam çözünürlüğe getirir. if (p) { p->boyutlaniyor_ = false; if (p->kareCiz) p->kareCiz(); } return 0; case WM_TIMER: if (p && w == 0xF1C0 && p->kareCiz) { p->kareCiz(); return 0; } break; case WM_CLOSE: if (p) p->kapatildi_ = true; return 0; case WM_DESTROY: // NOT: PostQuitMessage BURADA ÇAĞRILMAZ artık — bu, ANY pencere yıkılınca // (ikincil/ek pencereler dahil) tüm uygulamayı kapatırdı. Karar Pencere::~Pencere() // içinde ayar.anaPencere'ye göre verilir (bkz. orada). return 0; } return DefWindowProcW(hwnd, mesaj, w, l); } }; Pencere::Pencere(const PencereAyar& ayar) { HINSTANCE ornek = GetModuleHandleW(nullptr); ornek_ = ornek; WNDCLASSEXW wc{}; wc.cbSize = sizeof(wc); wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = PencereYordam::yordam; wc.hInstance = ornek; wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); wc.lpszClassName = sinifAdi; RegisterClassExW(&wc); RECT r{0, 0, (LONG)ayar.genislik, (LONG)ayar.yukseklik}; AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE); const int kx = ayar.x == PencereAyar::KONUM_YOK ? CW_USEDEFAULT : ayar.x; const int ky = ayar.y == PencereAyar::KONUM_YOK ? CW_USEDEFAULT : ayar.y; // sahipTutamac dolu ise hWndParent olarak geçilir → WS_CHILD OLMADAN "owned window" // (Win32 semantiği: z-order sahibinin üstünde, sahibiyle minimize/restore olur; bu // WS_OVERLAPPEDWINDOW ile birlikte çalışır — child pencere DEĞİLDİR, hâlâ kendi // başlık çubuğu/kenarı olan, taşınabilir bağımsız bir OS penceresidir). HWND hwndSahip = (HWND)ayar.sahipTutamac; HWND hwnd = CreateWindowExW( 0, sinifAdi, ayar.baslik.c_str(), WS_OVERLAPPEDWINDOW, kx, ky, r.right - r.left, r.bottom - r.top, hwndSahip, nullptr, ornek, this); FIKO_ASSERT(hwnd != nullptr, "Pencere olusturulamadi"); tutamac_ = hwnd; genislik_ = ayar.genislik; yukseklik_ = ayar.yukseklik; anaPencere_ = ayar.anaPencere; // Cam (glassmorphism) pencere: başlık çubuğu + arka planı saydam bırak, DWM // bulanıklık uygula (frosted-glass). Yalnız ana pencere + glass bayraklı; // uygulanamazsa sessiz geç (eski koyu araka görünür). if (ayar.glass) { // SDK 10 eksikse DWM_BLURBEHIND tanimi yoktur — elden kurarız. DWM_BLURBEHIND bb{}; bb.dwFlags = DWM_BB_ENABLE; bb.fEnable = TRUE; bb.hRgnBlur = nullptr; bb.fTransitionOnMaximized = FALSE; if (SUCCEEDED(DwmEnableBlurBehindWindow(hwnd, &bb))) { // Başlık çubuğu saydam kalsın (bulanıklık görünür olsun). const DWORD dc{0x00000000}; DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, &dc, sizeof(dc)); } } // Surukle-birak ile dosya acma: WM_DROPFILES etkinlestir. Uygulama yukseltilmis // (elevated) calisirsa Explorer (non-elevated) → app drop'u UIPI ile engellenebilir; // WM_DROPFILES/WM_COPYDATA/WM_COPYGLOBALDATA'ya izin ver (basarisizsa sessiz gec). DragAcceptFiles(hwnd, TRUE); ChangeWindowMessageFilterEx(hwnd, WM_DROPFILES, MSGFLT_ALLOW, nullptr); ChangeWindowMessageFilterEx(hwnd, WM_COPYDATA, MSGFLT_ALLOW, nullptr); ChangeWindowMessageFilterEx(hwnd, 0x0049 /* WM_COPYGLOBALDATA */, MSGFLT_ALLOW, nullptr); ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); } Pencere::~Pencere() { if (tutamac_) { // KRİTİK (kapanış UAF fix): DestroyWindow WM_DESTROY/WM_NCDESTROY üretir → yordam() // senkron çalışır. O ana dek ImGuiKatman yıkıldıysa p->hamMesaj YIKILMIŞ ImGui // context'ine (SetCurrentContext + WndProcHandler) dokunur = use-after-free (AV). // USERDATA'yı sıfırla → yordam p=null görür, hiçbir ölü geri-çağrı tetiklenmez. SetWindowLongPtrW((HWND)tutamac_, GWLP_USERDATA, 0); DestroyWindow((HWND)tutamac_); // PostQuitMessage KARARI BURADA (WM_DESTROY içinde DEĞİL): USERDATA sıfırlandığı // için WM_DESTROY sırasında Pencere*'ye erişim yok, anaPencere_ bayrağını burada // (nesne hâlâ canlıyken) okuyoruz. Yalnız GERÇEK ana pencere kapanışta uygulamanın // mesaj döngüsünü sonlandırır; ikincil pencereler (kafa_penceresi OS-penceresi vb.) // kendi başına kapanır, uygulamayı kapatmaz. if (anaPencere_) PostQuitMessage(0); } UnregisterClassW(sinifAdi, (HINSTANCE)ornek_); } bool Pencere::mesajlariIsle() { mesajPompala(); return !kapatildi_; } void Pencere::mesajPompala() { MSG msg; while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } } }