diff --git a/DDrawCompat/DDraw/RealPrimarySurface.cpp b/DDrawCompat/DDraw/RealPrimarySurface.cpp index 288863a..1b4a289 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.cpp +++ b/DDrawCompat/DDraw/RealPrimarySurface.cpp @@ -408,35 +408,50 @@ namespace void updatePresentationWindow() { LOG_FUNC("RealPrimarySurface::updatePresentationWindow"); - const bool isFullscreen = isProcessActive() && (g_isFullscreen || - g_frontBuffer && DDraw::PrimarySurface::getPrimary() && SUCCEEDED(g_frontBuffer->IsLost(g_frontBuffer)) && - Gdi::Window::hasFullscreenWindow()); + + const bool isActive = isProcessActive(); + HWND fullscreenWindow = nullptr; + if (g_isFullscreen && IsWindowVisible(g_deviceWindow) && !IsIconic(g_deviceWindow)) + { + fullscreenWindow = g_deviceWindow; + } + else if (g_frontBuffer && DDraw::PrimarySurface::getPrimary() && SUCCEEDED(g_frontBuffer->IsLost(g_frontBuffer))) + { + fullscreenWindow = Gdi::Window::getFullscreenWindow(); + } + + fullscreenWindow = fullscreenWindow ? Gdi::Window::getPresentationWindow(fullscreenWindow) : nullptr; if (g_windowedBackBuffer) { auto resource = D3dDdi::Device::findResource( DDraw::DirectDrawSurface::getDriverResourceHandle(*g_windowedBackBuffer)); - resource->setFullscreenMode(isFullscreen); + resource->setFullscreenMode(isActive && fullscreenWindow); } - Gdi::GuiThread::execute([&]() - { - if (isFullscreen) + if (!isActive) + { + return; + } + + g_presentationWindow = fullscreenWindow; + + if (g_presentationWindow) + { + Gdi::GuiThread::execute([&]() { - if (!g_presentationWindow) - { - g_presentationWindow = Gdi::PresentationWindow::create(nullptr); - } CALL_ORIG_FUNC(SetWindowPos)(g_presentationWindow, HWND_TOPMOST, g_monitorRect.left, g_monitorRect.top, g_monitorRect.right - g_monitorRect.left, g_monitorRect.bottom - g_monitorRect.top, SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER | SWP_SHOWWINDOW); - } - else if (g_presentationWindow) - { - DestroyWindow(g_presentationWindow); - g_presentationWindow = nullptr; - } - }); + }); + } + + static HWND prevPresentationWindow = nullptr; + if (prevPresentationWindow && prevPresentationWindow != g_presentationWindow) + { + Gdi::Window::updatePresentationWindowPos(prevPresentationWindow, GetParent(prevPresentationWindow)); + } + prevPresentationWindow = g_presentationWindow; } unsigned WINAPI updateThreadProc(LPVOID /*lpParameter*/) @@ -605,7 +620,7 @@ namespace DDraw Compat::ScopedCriticalSection lock(g_presentCs); isPresentationWindowUpdateNeeded = 0 != g_qpcUpdatePresentationWindow && Time::queryPerformanceCounter() - g_qpcUpdatePresentationWindow >= 0 || - g_presentationWindow && !isProcessActive(); + !isProcessActive(); } if (isPresentationWindowUpdateNeeded) @@ -799,14 +814,14 @@ namespace DDraw void RealPrimarySurface::setPresentationWindowTopmost() { - Gdi::GuiThread::execute([&]() - { - if (g_presentationWindow) + if (g_presentationWindow && IsWindowVisible(g_presentationWindow)) + { + Gdi::GuiThread::execute([&]() { CALL_ORIG_FUNC(SetWindowPos)(g_presentationWindow, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER); - } - }); + SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER); + }); + } } void RealPrimarySurface::setUpdateReady() diff --git a/DDrawCompat/Gdi/GuiThread.cpp b/DDrawCompat/Gdi/GuiThread.cpp index 52b22fe..868a5e7 100644 --- a/DDrawCompat/Gdi/GuiThread.cpp +++ b/DDrawCompat/Gdi/GuiThread.cpp @@ -105,17 +105,17 @@ namespace Gdi { namespace GuiThread { - HWND createWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + HWND createWindow(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { // Workaround for ForceSimpleWindow shim - static auto createWindowExA = reinterpret_cast( - Compat::getProcAddress(GetModuleHandle("user32"), "CreateWindowExA")); + static auto createWindowExW = reinterpret_cast( + Compat::getProcAddress(GetModuleHandle("user32"), "CreateWindowExW")); HWND hwnd = nullptr; execute([&]() { - hwnd = createWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, + hwnd = createWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); }); return hwnd; diff --git a/DDrawCompat/Gdi/GuiThread.h b/DDrawCompat/Gdi/GuiThread.h index ea59870..269e916 100644 --- a/DDrawCompat/Gdi/GuiThread.h +++ b/DDrawCompat/Gdi/GuiThread.h @@ -16,7 +16,7 @@ namespace Gdi { namespace GuiThread { - HWND createWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + HWND createWindow(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); void deleteTaskbarTab(HWND hwnd); void destroyWindow(HWND hwnd); diff --git a/DDrawCompat/Gdi/PresentationWindow.cpp b/DDrawCompat/Gdi/PresentationWindow.cpp index cef30a7..564b49f 100644 --- a/DDrawCompat/Gdi/PresentationWindow.cpp +++ b/DDrawCompat/Gdi/PresentationWindow.cpp @@ -26,8 +26,8 @@ namespace Gdi GuiThread::execute([&]() { presentationWindow = GuiThread::createWindow( - WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY | WS_EX_TOOLWINDOW, - reinterpret_cast(g_classAtom), + WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY | (owner ? 0 : WS_EX_TOOLWINDOW), + reinterpret_cast(g_classAtom), nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, diff --git a/DDrawCompat/Gdi/WinProc.cpp b/DDrawCompat/Gdi/WinProc.cpp index 9a010ed..5afceea 100644 --- a/DDrawCompat/Gdi/WinProc.cpp +++ b/DDrawCompat/Gdi/WinProc.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -49,6 +50,10 @@ namespace WNDPROC wndProcW; }; + decltype(&DwmSetIconicThumbnail) g_dwmSetIconicThumbnail = nullptr; + decltype(&SetThreadDpiAwarenessContext) g_setThreadDpiAwarenessContext = nullptr; + + wchar_t g_dummyWindowText; std::map g_menuMaxHeight; std::set g_windowPosChangeNotifyFuncs; @@ -63,7 +68,9 @@ namespace thread_local bool g_isFrameStarted = false; thread_local bool g_waiting = false; + void dwmSendIconicThumbnail(HWND hwnd, LONG width, LONG height); WindowProc getWindowProc(HWND hwnd); + std::wstring getWindowText(HWND hwnd, WNDPROC wndProc); bool isUser32ScrollBar(HWND hwnd); void onDestroyWindow(HWND hwnd); void onGetMinMaxInfo(MINMAXINFO& mmi); @@ -106,6 +113,10 @@ namespace Gdi::checkDesktopComposition(); break; + case WM_DWMSENDICONICTHUMBNAIL: + dwmSendIconicThumbnail(hwnd, HIWORD(lParam), LOWORD(lParam)); + return 0; + case WM_GETMINMAXINFO: onGetMinMaxInfo(*reinterpret_cast(lParam)); break; @@ -123,6 +134,19 @@ namespace } break; + case WM_NULL: + if (WM_GETTEXT == wParam && reinterpret_cast(&g_dummyWindowText) == lParam) + { + auto presentationWindow = Gdi::Window::getPresentationWindow(hwnd); + if (presentationWindow) + { + std::wstring windowText(L"[DDrawCompat] " + getWindowText(hwnd, wndProc)); + SendMessageW(presentationWindow, WM_SETTEXT, 0, reinterpret_cast(windowText.c_str())); + } + return 0; + } + break; + case WM_SYNCPAINT: if (Gdi::Window::isTopLevelWindow(hwnd)) { @@ -266,6 +290,59 @@ namespace return LOG_RESULT(CALL_ORIG_FUNC(SetWindowLongA)(hWnd, nIndex, dwNewLong)); } + void dwmSendIconicThumbnail(HWND hwnd, LONG width, LONG height) + { + auto presentationWindow = Gdi::Window::getPresentationWindow(hwnd); + if (!presentationWindow || !g_dwmSetIconicThumbnail) + { + return; + } + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + void* bits = nullptr; + HBITMAP bmp = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!bmp) + { + return; + } + + HDC srcDc = GetWindowDC(presentationWindow); + HDC dstDc = CreateCompatibleDC(nullptr); + auto prevBmp = SelectBitmap(dstDc, bmp); + + RECT srcRect = {}; + GetWindowRect(presentationWindow, &srcRect); + + SetStretchBltMode(dstDc, HALFTONE); + CALL_ORIG_FUNC(StretchBlt)(dstDc, 0, 0, width, height, + srcDc, 0, 0, srcRect.right - srcRect.left, srcRect.bottom - srcRect.top, SRCCOPY); + + SelectBitmap(dstDc, prevBmp); + DeleteDC(dstDc); + ReleaseDC(presentationWindow, srcDc); + + DPI_AWARENESS_CONTEXT prevContext = nullptr; + if (g_setThreadDpiAwarenessContext) + { + prevContext = g_setThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } + + g_dwmSetIconicThumbnail(hwnd, bmp, 0); + + if (prevContext) + { + g_setThreadDpiAwarenessContext(prevContext); + } + + DeleteObject(bmp); + } + BOOL WINAPI getCursorPos(LPPOINT lpPoint) { if (lpPoint && g_cursorPos) @@ -326,6 +403,25 @@ namespace return g_windowProc[hwnd]; } + std::wstring getWindowText(HWND hwnd, WNDPROC wndProc) + { + const UINT MAX_LEN = 256; + if (IsWindowUnicode(hwnd)) + { + wchar_t windowText[MAX_LEN] = {}; + CallWindowProcW(wndProc, hwnd, WM_GETTEXT, MAX_LEN, reinterpret_cast(windowText)); + windowText[MAX_LEN - 1] = 0; + return windowText; + } + else + { + char windowText[MAX_LEN] = {}; + CallWindowProcA(wndProc, hwnd, WM_GETTEXT, MAX_LEN, reinterpret_cast(windowText)); + windowText[MAX_LEN - 1] = 0; + return std::wstring(windowText, windowText + strlen(windowText)); + } + } + template int WINAPI messageBox(Params... params) { @@ -462,6 +558,13 @@ namespace { DDraw::RealPrimarySurface::setPresentationWindowTopmost(); Gdi::Window::updateAll(); + + if (g_dwmSetIconicThumbnail) + { + const BOOL isIconic = IsIconic(hwnd); + DwmSetWindowAttribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION, &isIconic, sizeof(isIconic)); + DwmSetWindowAttribute(hwnd, DWMWA_HAS_ICONIC_BITMAP, &isIconic, sizeof(isIconic)); + } } if (wp.flags & SWP_FRAMECHANGED) @@ -670,6 +773,12 @@ namespace break; case EVENT_OBJECT_NAMECHANGE: + if (OBJID_WINDOW == idObject && Gdi::Window::isTopLevelWindow(hwnd) && !Gdi::GuiThread::isGuiThreadWindow(hwnd)) + { + Gdi::WinProc::updatePresentationWindowText(hwnd); + break; + } + case EVENT_OBJECT_SHOW: case EVENT_OBJECT_HIDE: if (OBJID_CURSOR == idObject && Gdi::Cursor::isEmulated()) @@ -712,6 +821,7 @@ namespace break; } } + break; } } } @@ -766,6 +876,11 @@ namespace Gdi HOOK_FUNCTION(user32, SetWindowLongW, setWindowLongW); HOOK_FUNCTION(user32, SetWindowPos, setWindowPos); + g_dwmSetIconicThumbnail = reinterpret_cast( + GetProcAddress(GetModuleHandle("dwmapi"), "DwmSetIconicThumbnail")); + g_setThreadDpiAwarenessContext = reinterpret_cast( + GetProcAddress(GetModuleHandle("user32"), "SetThreadDpiAwarenessContext")); + Compat::hookIatFunction(Dll::g_origDDrawModule, "SetWindowLongA", ddrawSetWindowLongA); SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, @@ -864,6 +979,11 @@ namespace Gdi } } + void updatePresentationWindowText(HWND owner) + { + SendNotifyMessageW(owner, WM_NULL, WM_GETTEXT, reinterpret_cast(&g_dummyWindowText)); + } + void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc) { g_windowPosChangeNotifyFuncs.insert(notifyFunc); diff --git a/DDrawCompat/Gdi/WinProc.h b/DDrawCompat/Gdi/WinProc.h index 9507ec3..6c5028e 100644 --- a/DDrawCompat/Gdi/WinProc.h +++ b/DDrawCompat/Gdi/WinProc.h @@ -10,6 +10,7 @@ namespace Gdi void installHooks(); void onCreateWindow(HWND hwnd); void startFrame(); + void updatePresentationWindowText(HWND owner); void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc); } } diff --git a/DDrawCompat/Gdi/Window.cpp b/DDrawCompat/Gdi/Window.cpp index 3eef34f..caea535 100644 --- a/DDrawCompat/Gdi/Window.cpp +++ b/DDrawCompat/Gdi/Window.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -269,6 +270,7 @@ namespace if (!isLayered) { it->second.presentationWindow = Gdi::PresentationWindow::create(hwnd); + Gdi::WinProc::updatePresentationWindowText(hwnd); setPresentationWindowRgn = true; } else if (it->second.presentationWindow) @@ -350,7 +352,10 @@ namespace Gdi::GuiThread::setWindowRgn(it->second.presentationWindow, it->second.windowRegion); } - Gdi::Window::updatePresentationWindowPos(it->second.presentationWindow, hwnd); + if (it->second.presentationWindow != DDraw::RealPrimarySurface::getPresentationWindow()) + { + Gdi::Window::updatePresentationWindowPos(it->second.presentationWindow, hwnd); + } } } return TRUE; @@ -420,7 +425,7 @@ namespace Gdi return layeredWindows; } - bool hasFullscreenWindow() + HWND getFullscreenWindow() { D3dDdi::ScopedCriticalSection lock; RECT mr = DDraw::PrimarySurface::getMonitorRect(); @@ -434,10 +439,10 @@ namespace Gdi IsWindowVisible(window.first) && !IsIconic(window.first)) { - return true; + return window.first; } } - return false; + return nullptr; } bool isTopLevelWindow(HWND hwnd) @@ -626,11 +631,14 @@ namespace Gdi void updatePresentationWindowPos(HWND presentationWindow, HWND owner) { - const bool isOwnerVisible = IsWindowVisible(owner) && !IsIconic(owner); + if (IsIconic(owner)) + { + return; + } WINDOWPOS wp = {}; wp.flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOSENDCHANGING; - if (isOwnerVisible) + if (IsWindowVisible(owner)) { wp.hwndInsertAfter = GetWindow(owner, GW_HWNDPREV); if (!wp.hwndInsertAfter) diff --git a/DDrawCompat/Gdi/Window.h b/DDrawCompat/Gdi/Window.h index 9ca59a7..e9e37e8 100644 --- a/DDrawCompat/Gdi/Window.h +++ b/DDrawCompat/Gdi/Window.h @@ -20,7 +20,7 @@ namespace Gdi HWND getPresentationWindow(HWND hwnd); std::vector getVisibleLayeredWindows(); std::vector getVisibleOverlayWindows(); - bool hasFullscreenWindow(); + HWND getFullscreenWindow(); bool isTopLevelWindow(HWND hwnd); void onStyleChanged(HWND hwnd, WPARAM wParam); void onSyncPaint(HWND hwnd); diff --git a/DDrawCompat/Win32/Log.cpp b/DDrawCompat/Win32/Log.cpp index d349f41..539acb9 100644 --- a/DDrawCompat/Win32/Log.cpp +++ b/DDrawCompat/Win32/Log.cpp @@ -110,6 +110,29 @@ std::ostream& operator<<(std::ostream& os, const BITMAP& bm) << bm.bmBits; } +std::ostream& operator<<(std::ostream& os, const BITMAPINFO& bmi) +{ + return Compat::LogStruct(os) + << bmi.bmiHeader + << Compat::array(bmi.bmiColors, 1); +} + +std::ostream& operator<<(std::ostream& os, const BITMAPINFOHEADER& bmih) +{ + return Compat::LogStruct(os) + << bmih.biSize + << bmih.biWidth + << bmih.biHeight + << bmih.biPlanes + << bmih.biBitCount + << bmih.biCompression + << bmih.biSizeImage + << bmih.biXPelsPerMeter + << bmih.biYPelsPerMeter + << bmih.biClrUsed + << bmih.biClrImportant; +} + std::ostream& operator<<(std::ostream& os, const COMPAREITEMSTRUCT& cis) { return Compat::LogStruct(os) diff --git a/DDrawCompat/Win32/Log.h b/DDrawCompat/Win32/Log.h index e4f0064..e0c8349 100644 --- a/DDrawCompat/Win32/Log.h +++ b/DDrawCompat/Win32/Log.h @@ -5,6 +5,8 @@ #include std::ostream& operator<<(std::ostream& os, const BITMAP& bm); +std::ostream& operator<<(std::ostream& os, const BITMAPINFO& bmi); +std::ostream& operator<<(std::ostream& os, const BITMAPINFOHEADER& bmih); std::ostream& operator<<(std::ostream& os, const COMPAREITEMSTRUCT& cis); std::ostream& operator<<(std::ostream& os, const COPYDATASTRUCT& cds); std::ostream& operator<<(std::ostream& os, const CREATESTRUCTA& cs);