1
0
mirror of https://github.com/narzoul/DDrawCompat synced 2024-12-30 08:55:36 +01:00

Added support for OBS window capture and taskbar thumbnails

This commit is contained in:
narzoul 2023-07-30 22:37:23 +02:00
parent d734c41443
commit 5ce45f8063
10 changed files with 208 additions and 39 deletions

View File

@ -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()

View File

@ -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<decltype(&CreateWindowExA)>(
Compat::getProcAddress(GetModuleHandle("user32"), "CreateWindowExA"));
static auto createWindowExW = reinterpret_cast<decltype(&CreateWindowExW)>(
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;

View File

@ -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);

View File

@ -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<const char*>(g_classAtom),
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY | (owner ? 0 : WS_EX_TOOLWINDOW),
reinterpret_cast<const wchar_t*>(g_classAtom),
nullptr,
WS_DISABLED | WS_POPUP,
0, 0, 1, 1,

View File

@ -3,6 +3,7 @@
#include <Windows.h>
#include <Windowsx.h>
#include <dwmapi.h>
#include <Common/Hook.h>
#include <Common/Log.h>
@ -49,6 +50,10 @@ namespace
WNDPROC wndProcW;
};
decltype(&DwmSetIconicThumbnail) g_dwmSetIconicThumbnail = nullptr;
decltype(&SetThreadDpiAwarenessContext) g_setThreadDpiAwarenessContext = nullptr;
wchar_t g_dummyWindowText;
std::map<HMENU, UINT> g_menuMaxHeight;
std::set<Gdi::WindowPosChangeNotifyFunc> 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<MINMAXINFO*>(lParam));
break;
@ -123,6 +134,19 @@ namespace
}
break;
case WM_NULL:
if (WM_GETTEXT == wParam && reinterpret_cast<LPARAM>(&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<LPARAM>(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<LPARAM>(windowText));
windowText[MAX_LEN - 1] = 0;
return windowText;
}
else
{
char windowText[MAX_LEN] = {};
CallWindowProcA(wndProc, hwnd, WM_GETTEXT, MAX_LEN, reinterpret_cast<LPARAM>(windowText));
windowText[MAX_LEN - 1] = 0;
return std::wstring(windowText, windowText + strlen(windowText));
}
}
template <auto func, typename... Params>
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<decltype(&DwmSetIconicThumbnail)>(
GetProcAddress(GetModuleHandle("dwmapi"), "DwmSetIconicThumbnail"));
g_setThreadDpiAwarenessContext = reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
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<LPARAM>(&g_dummyWindowText));
}
void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc)
{
g_windowPosChangeNotifyFuncs.insert(notifyFunc);

View File

@ -10,6 +10,7 @@ namespace Gdi
void installHooks();
void onCreateWindow(HWND hwnd);
void startFrame();
void updatePresentationWindowText(HWND owner);
void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc);
}
}

View File

@ -14,6 +14,7 @@
#include <Gdi/PresentationWindow.h>
#include <Gdi/VirtualScreen.h>
#include <Gdi/Window.h>
#include <Gdi/WinProc.h>
#include <Input/Input.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
@ -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)

View File

@ -20,7 +20,7 @@ namespace Gdi
HWND getPresentationWindow(HWND hwnd);
std::vector<LayeredWindow> getVisibleLayeredWindows();
std::vector<LayeredWindow> getVisibleOverlayWindows();
bool hasFullscreenWindow();
HWND getFullscreenWindow();
bool isTopLevelWindow(HWND hwnd);
void onStyleChanged(HWND hwnd, WPARAM wParam);
void onSyncPaint(HWND hwnd);

View File

@ -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)

View File

@ -5,6 +5,8 @@
#include <Windows.h>
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);