Improved graphics capture hooking
parent
30d80eb997
commit
3e800f575f
|
@ -36,6 +36,7 @@ HANDLE textureMutexes[2] = {NULL, NULL};
|
|||
struct WindowInfo
|
||||
{
|
||||
String strClass;
|
||||
String strExecutable;
|
||||
BOOL bRequiresAdmin;
|
||||
};
|
||||
|
||||
|
@ -51,7 +52,10 @@ struct ConfigDialogData
|
|||
inline void ClearData()
|
||||
{
|
||||
for(UINT i=0; i<windowData.Num(); i++)
|
||||
{
|
||||
windowData[i].strClass.Clear();
|
||||
windowData[i].strExecutable.Clear();
|
||||
}
|
||||
windowData.Clear();
|
||||
adminWindows.Clear();
|
||||
}
|
||||
|
@ -59,7 +63,10 @@ struct ConfigDialogData
|
|||
inline ~ConfigDialogData()
|
||||
{
|
||||
for(UINT i=0; i<windowData.Num(); i++)
|
||||
{
|
||||
windowData[i].strClass.Clear();
|
||||
windowData[i].strExecutable.Clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -166,9 +173,19 @@ void RefreshWindowList(HWND hwndCombobox, ConfigDialogData &configData)
|
|||
GetClassName(hwndCurrent, strClassName.Array(), 255);
|
||||
strClassName.SetLength(slen(strClassName));
|
||||
|
||||
TCHAR *baseExeName;
|
||||
baseExeName = wcsrchr(fileName, '\\');
|
||||
if (!baseExeName)
|
||||
baseExeName = fileName;
|
||||
else
|
||||
baseExeName++;
|
||||
|
||||
WindowInfo &info = *configData.windowData.CreateNew();
|
||||
info.strClass = strClassName;
|
||||
info.strExecutable = baseExeName;
|
||||
info.bRequiresAdmin = false; //todo: add later
|
||||
|
||||
info.strExecutable.MakeLower();
|
||||
}
|
||||
}
|
||||
} while (hwndCurrent = GetNextWindow(hwndCurrent, GW_HWNDNEXT));
|
||||
|
@ -189,6 +206,7 @@ void RefreshWindowList(HWND hwndCombobox, ConfigDialogData &configData)
|
|||
|
||||
WindowInfo &info = *configData.windowData.CreateNew();
|
||||
info.strClass = TEXT("Dwm");
|
||||
info.strExecutable = TEXT("dwm.exe");
|
||||
info.bRequiresAdmin = false; //todo: add later
|
||||
}
|
||||
}
|
||||
|
@ -354,6 +372,7 @@ INT_PTR CALLBACK ConfigureDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPA
|
|||
String strWindow = GetCBText(GetDlgItem(hwnd, IDC_APPLIST), windowID);
|
||||
data->SetString(TEXT("window"), strWindow);
|
||||
data->SetString(TEXT("windowClass"), info->windowData[windowID].strClass);
|
||||
data->SetString(TEXT("executable"), info->windowData[windowID].strExecutable);
|
||||
|
||||
data->SetInt(TEXT("stretchImage"), SendMessage(GetDlgItem(hwnd, IDC_STRETCHTOSCREEN), BM_GETCHECK, 0, 0) == BST_CHECKED);
|
||||
data->SetInt(TEXT("ignoreAspect"), SendMessage(GetDlgItem(hwnd, IDC_IGNOREASPECT), BM_GETCHECK, 0, 0) == BST_CHECKED);
|
||||
|
|
|
@ -258,6 +258,12 @@ void GraphicsCaptureSource::EndCapture()
|
|||
if(hSignalExit)
|
||||
CloseHandle(hSignalExit);
|
||||
|
||||
if (hTargetProcess)
|
||||
{
|
||||
CloseHandle(hTargetProcess);
|
||||
hTargetProcess = NULL;
|
||||
}
|
||||
|
||||
hSignalRestart = hSignalEnd = hSignalReady = hSignalExit = hOBSIsAlive = NULL;
|
||||
|
||||
bErrorAcquiring = false;
|
||||
|
@ -286,8 +292,12 @@ void GraphicsCaptureSource::BeginScene()
|
|||
return;
|
||||
|
||||
strWindowClass = data->GetString(TEXT("windowClass"));
|
||||
if(strWindowClass.IsEmpty())
|
||||
strExecutable = data->GetString(TEXT("executable"));
|
||||
if (strWindowClass.IsEmpty() && strExecutable.IsEmpty())
|
||||
{
|
||||
Log(TEXT("GraphicsCaptureSource::BeginScene: No class or executable specified, what's happening?!"));
|
||||
return;
|
||||
}
|
||||
|
||||
bUseDWMCapture = (scmpi(strWindowClass, TEXT("Dwm")) == 0);
|
||||
|
||||
|
@ -368,14 +378,100 @@ HWND FindVisibleWindow(CTSTR lpClass, CTSTR lpTitle)
|
|||
return hwnd;
|
||||
}
|
||||
|
||||
struct FindWindowData
|
||||
{
|
||||
String classname;
|
||||
String exename;
|
||||
HWND hwnd;
|
||||
OPPROC pOpenProcess;
|
||||
};
|
||||
|
||||
// This function is responsible for finding the window the user wanted to capture.
|
||||
// Possible improvements:
|
||||
// * Allow matching on window title and possibly other criteria (foreground, visible, etc)
|
||||
// * Allow user to specify which things to match on as a bitmask to match tricky programs
|
||||
|
||||
BOOL CALLBACK GraphicsCaptureFindWindow(HWND hwnd, LPARAM lParam)
|
||||
{
|
||||
TCHAR windowClass[256];
|
||||
TCHAR windowExecutable[MAX_PATH];
|
||||
|
||||
windowClass[_countof(windowClass)-1] = windowExecutable[MAX_PATH-1] = 0;
|
||||
|
||||
FindWindowData *fwd = (FindWindowData *)lParam;
|
||||
|
||||
if (!IsWindowVisible(hwnd))
|
||||
return TRUE;
|
||||
|
||||
if (GetClassName(hwnd, windowClass, _countof(windowClass) - 1) && !scmp(windowClass, fwd->classname))
|
||||
{
|
||||
//handle old sources which lack an exe name
|
||||
if (fwd->exename.IsEmpty())
|
||||
return TRUE;
|
||||
|
||||
DWORD processID;
|
||||
GetWindowThreadProcessId(hwnd, &processID);
|
||||
|
||||
HANDLE hProc = fwd->pOpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
|
||||
if (hProc)
|
||||
{
|
||||
DWORD wLen = _countof(windowExecutable) - 1;
|
||||
if (QueryFullProcessImageName(hProc, 0, windowExecutable, &wLen))
|
||||
{
|
||||
TCHAR *p;
|
||||
p = wcsrchr(windowExecutable, '\\');
|
||||
if (p)
|
||||
p++;
|
||||
else
|
||||
p = windowExecutable;
|
||||
|
||||
slwr(p);
|
||||
|
||||
if (!scmp(p, fwd->exename))
|
||||
{
|
||||
CloseHandle(hProc);
|
||||
fwd->hwnd = hwnd;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RUNONCE Log(TEXT("OpenProcess worked but QueryFullProcessImageName returned %d for pid %d?"), GetLastError(), processID);
|
||||
}
|
||||
|
||||
CloseHandle(hProc);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void GraphicsCaptureSource::AttemptCapture()
|
||||
{
|
||||
OSDebugOut(TEXT("attempting to capture..\n"));
|
||||
|
||||
if (scmpi(strWindowClass, L"dwm") == 0)
|
||||
{
|
||||
hwndTarget = FindWindow(strWindowClass, NULL);
|
||||
}
|
||||
else
|
||||
hwndTarget = strWindowClass.IsValid() ? FindVisibleWindow(strWindowClass, NULL) : nullptr;
|
||||
{
|
||||
FindWindowData fwd;
|
||||
|
||||
//FIXME: duplicated code, but we need OpenProcess here
|
||||
char pOPStr[12];
|
||||
mcpy(pOPStr, "NpflUvhel{x", 12); //OpenProcess obfuscated
|
||||
for (int i = 0; i<11; i++) pOPStr[i] ^= i ^ 1;
|
||||
|
||||
fwd.pOpenProcess = (OPPROC)GetProcAddress(GetModuleHandle(TEXT("KERNEL32")), pOPStr);
|
||||
fwd.classname = strWindowClass;
|
||||
fwd.exename = strExecutable;
|
||||
fwd.hwnd = nullptr;
|
||||
|
||||
EnumWindows(GraphicsCaptureFindWindow, (LPARAM)&fwd);
|
||||
|
||||
hwndTarget = fwd.hwnd;
|
||||
}
|
||||
|
||||
// use foregroundwindow as fallback (should be NULL if not using hotkey capture)
|
||||
if (!hwndTarget)
|
||||
|
@ -399,7 +495,10 @@ void GraphicsCaptureSource::AttemptCapture()
|
|||
{
|
||||
OSDebugOut(L"Bad window\n");
|
||||
if (!bUseHotkey && !warningID)
|
||||
{
|
||||
//Log(TEXT("GraphicsCaptureSource::AttemptCapture: Window '%s' [%s] not found."), strWindowClass.Array(), strExecutable.Array());
|
||||
warningID = API->AddStreamInfo(Str("Sources.SoftwareCaptureSource.WindowNotFound"), StreamInfoPriority_High);
|
||||
}
|
||||
|
||||
bCapturing = false;
|
||||
|
||||
|
@ -587,8 +686,16 @@ void GraphicsCaptureSource::AttemptCapture()
|
|||
}
|
||||
}
|
||||
|
||||
//save a copy of the process handle which we injected into, this lets us check for process exit in Tick()
|
||||
if (!hTargetProcess)
|
||||
{
|
||||
if (!DuplicateHandle(GetCurrentProcess(), hProcess, GetCurrentProcess(), &hTargetProcess, 0, FALSE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
Log(TEXT("Warning: Couldn't DuplicateHandle, %d"), GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProcess);
|
||||
hProcess = NULL;
|
||||
|
||||
if (!bCapturing)
|
||||
{
|
||||
|
@ -716,6 +823,9 @@ void GraphicsCaptureSource::Tick(float fSeconds)
|
|||
if(!IsWindow(hwndCapture) && !bUseDWMCapture) {
|
||||
Log(TEXT("Capture window 0x%08lX invalid or changing, terminating capture"), DWORD(hwndCapture));
|
||||
EndCapture();
|
||||
} else if (hTargetProcess && WaitForSingleObject(hTargetProcess, 0) == WAIT_OBJECT_0) {
|
||||
Log(TEXT("Capture process %s exited, terminating capture"), strExecutable.Array());
|
||||
EndCapture();
|
||||
} else if (bUseHotkey && hwndNextTarget && hwndNextTarget != hwndTarget) {
|
||||
Log(TEXT("Capture hotkey triggered for new window, terminating capture"));
|
||||
EndCapture();
|
||||
|
|
|
@ -30,6 +30,7 @@ class GraphicsCaptureSource : public ImageSource
|
|||
bool bUseHotkey;
|
||||
DWORD hotkey, hotkeyID;
|
||||
String strWindowClass;
|
||||
String strExecutable;
|
||||
|
||||
bool bUseDWMCapture;
|
||||
|
||||
|
@ -39,6 +40,7 @@ class GraphicsCaptureSource : public ImageSource
|
|||
HANDLE injectHelperProcess;
|
||||
|
||||
HWND hwndTarget, hwndCapture, hwndNextTarget;
|
||||
HANDLE hTargetProcess;
|
||||
bool bCapturing, bErrorAcquiring, bFlip, bStretch, bIgnoreAspect, bCaptureMouse;
|
||||
UINT captureWaitCount;
|
||||
DWORD targetProcessID;
|
||||
|
|
Loading…
Reference in New Issue