mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 01:44:49 -05:00
The "core" macro conditions and actions have been extracted out to the "base" plugin. The library now mostly contains functionality which is required across all plugins and (e.g. definitions for macro segments). The goal is to reduce the complexity and cross-dependencies and group the source files in a better way. This should relsove the "library limit of 65535 objects exceeded" build issue occuring in some Windows build environments.
456 lines
10 KiB
C++
456 lines
10 KiB
C++
#include "platform-funcs.hpp"
|
|
|
|
#include <windows.h>
|
|
#include <UIAutomation.h>
|
|
#include <util/platform.h>
|
|
#include <TlHelp32.h>
|
|
#include <Psapi.h>
|
|
#include <locale>
|
|
#include <codecvt>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <QStringList>
|
|
#include <QRegularExpression>
|
|
#include <obs-frontend-api.h>
|
|
#include <QApplication>
|
|
#include <QWidget>
|
|
#include <mutex>
|
|
|
|
namespace advss {
|
|
|
|
#define MAX_SEARCH 1000
|
|
|
|
static bool GetWindowTitle(HWND window, std::string &title)
|
|
{
|
|
size_t len = (size_t)GetWindowTextLengthW(window);
|
|
std::wstring wtitle;
|
|
|
|
wtitle.resize(len);
|
|
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1)) {
|
|
return false;
|
|
}
|
|
|
|
len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0);
|
|
title.resize(len);
|
|
os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1);
|
|
return true;
|
|
}
|
|
|
|
static bool WindowValid(HWND window)
|
|
{
|
|
LONG_PTR styles;
|
|
DWORD id;
|
|
|
|
if (!IsWindowVisible(window)) {
|
|
return false;
|
|
}
|
|
GetWindowThreadProcessId(window, &id);
|
|
if (id == GetCurrentProcessId()) {
|
|
return false;
|
|
}
|
|
|
|
styles = GetWindowLongPtr(window, GWL_STYLE);
|
|
|
|
if (styles & WS_CHILD) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static BOOL CALLBACK GetTitleCB(HWND hwnd, LPARAM lParam)
|
|
{
|
|
if (!WindowValid(hwnd)) {
|
|
return TRUE;
|
|
}
|
|
|
|
std::string title;
|
|
GetWindowTitle(hwnd, title);
|
|
if (title.empty()) {
|
|
return TRUE;
|
|
}
|
|
|
|
std::vector<std::string> &titles =
|
|
*reinterpret_cast<std::vector<std::string> *>(lParam);
|
|
titles.push_back(title);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID EnumWindowsWithMetro(__in WNDENUMPROC lpEnumFunc,
|
|
__in LPARAM lParam)
|
|
{
|
|
HWND childWindow = NULL;
|
|
int i = 0;
|
|
|
|
while (i < MAX_SEARCH &&
|
|
(childWindow = FindWindowEx(NULL, childWindow, NULL, NULL))) {
|
|
if (!lpEnumFunc(childWindow, lParam)) {
|
|
return;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Asynchronously updates the OBS window list and returns the state of the last
|
|
// successful update
|
|
const std::vector<std::string> getOBSWindows()
|
|
{
|
|
struct OBSWindowListHelper {
|
|
std::vector<std::string> windows;
|
|
std::atomic_bool done;
|
|
};
|
|
static OBSWindowListHelper obsWindowListHelper1 = {{}, {true}};
|
|
static OBSWindowListHelper obsWindowListHelper2 = {{}, {false}};
|
|
auto getQtWindowList = [](void *param) {
|
|
auto list = reinterpret_cast<OBSWindowListHelper *>(param);
|
|
for (auto w : QApplication::topLevelWidgets()) {
|
|
auto title = w->windowTitle();
|
|
if (!title.isEmpty()) {
|
|
list->windows.emplace_back(title.toStdString());
|
|
}
|
|
}
|
|
list->done = true;
|
|
};
|
|
|
|
static OBSWindowListHelper *lastDoneHelper = &obsWindowListHelper2;
|
|
static OBSWindowListHelper *pendingHelper = &obsWindowListHelper1;
|
|
static std::mutex mutex;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
if (pendingHelper->done) { // Check if swap is needed
|
|
auto temp = lastDoneHelper;
|
|
lastDoneHelper = pendingHelper;
|
|
pendingHelper = temp;
|
|
pendingHelper->done = false;
|
|
pendingHelper->windows.clear();
|
|
obs_queue_task(OBS_TASK_UI, getQtWindowList, pendingHelper,
|
|
false);
|
|
}
|
|
|
|
return lastDoneHelper->windows;
|
|
}
|
|
|
|
void GetWindowList(std::vector<std::string> &windows)
|
|
{
|
|
windows.resize(0);
|
|
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
|
|
|
|
// Also add OBS windows
|
|
for (const auto &window : getOBSWindows()) {
|
|
if (!window.empty()) {
|
|
windows.emplace_back(window);
|
|
}
|
|
}
|
|
|
|
// Add entry for OBS Studio itself - see GetCurrentWindowTitle()
|
|
windows.emplace_back("OBS");
|
|
}
|
|
|
|
void GetWindowList(QStringList &windows)
|
|
{
|
|
windows.clear();
|
|
|
|
std::vector<std::string> w;
|
|
GetWindowList(w);
|
|
for (auto window : w) {
|
|
windows << QString::fromStdString(window);
|
|
}
|
|
}
|
|
|
|
void GetCurrentWindowTitle(std::string &title)
|
|
{
|
|
HWND window = GetForegroundWindow();
|
|
DWORD pid;
|
|
DWORD thid;
|
|
thid = GetWindowThreadProcessId(window, &pid);
|
|
// Calling GetWindowTitle() on the OBS windows might cause a deadlock in
|
|
// the following scenario:
|
|
//
|
|
// The thread using GetWindowTitle() will send a WM_GETTEXT message to
|
|
// the main thread that created the window and wait for it to be
|
|
// processed.
|
|
// The main thread itself might be blocked from processing the message,
|
|
// however, when it itself is waiting for the thread using
|
|
// GetWindowTitle() to return.
|
|
//
|
|
// So instead rely on Qt to get the title of the active window.
|
|
if (GetCurrentProcessId() == pid) {
|
|
auto window = QApplication::activeWindow();
|
|
if (window) {
|
|
title = window->windowTitle().toStdString();
|
|
} else {
|
|
title = "OBS";
|
|
}
|
|
return;
|
|
}
|
|
GetWindowTitle(window, title);
|
|
}
|
|
|
|
static HWND getHWNDfromTitle(const std::string &title)
|
|
{
|
|
HWND hwnd = NULL;
|
|
wchar_t wTitle[512];
|
|
os_utf8_to_wcs(title.c_str(), 0, wTitle, 512);
|
|
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle);
|
|
return hwnd;
|
|
}
|
|
|
|
bool IsMaximized(const std::string &title)
|
|
{
|
|
RECT appBounds;
|
|
MONITORINFO monitorInfo = {0};
|
|
HWND hwnd = NULL;
|
|
|
|
hwnd = getHWNDfromTitle(title);
|
|
if (!hwnd) {
|
|
return false;
|
|
}
|
|
|
|
monitorInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
|
|
&monitorInfo);
|
|
|
|
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
|
|
if (IsZoomed(hwnd)) {
|
|
return true;
|
|
}
|
|
GetWindowRect(hwnd, &appBounds);
|
|
if (monitorInfo.rcMonitor.bottom == appBounds.bottom &&
|
|
monitorInfo.rcMonitor.top == appBounds.top &&
|
|
monitorInfo.rcMonitor.left == appBounds.left &&
|
|
monitorInfo.rcMonitor.right == appBounds.right) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static std::wstring GetControlText(HWND hwnd, IUIAutomationElement *element)
|
|
{
|
|
VARIANT var;
|
|
std::wstring text = L"";
|
|
HRESULT hr = element->GetCurrentPropertyValue(UIA_NamePropertyId, &var);
|
|
if (SUCCEEDED(hr)) {
|
|
if (var.vt == VT_BSTR && var.bstrVal) {
|
|
text = var.bstrVal;
|
|
}
|
|
VariantClear(&var);
|
|
}
|
|
|
|
hr = element->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &var);
|
|
if (SUCCEEDED(hr)) {
|
|
if (var.vt == VT_BSTR && var.bstrVal) {
|
|
if (!text.empty()) {
|
|
text += L"\n";
|
|
}
|
|
text += +var.bstrVal;
|
|
}
|
|
VariantClear(&var);
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
std::optional<std::string> GetTextInWindow(const std::string &window)
|
|
{
|
|
HWND hwnd = getHWNDfromTitle(window);
|
|
if (!hwnd) {
|
|
return {};
|
|
}
|
|
|
|
IUIAutomation *automation = nullptr;
|
|
auto hr = CoCreateInstance(__uuidof(CUIAutomation), nullptr,
|
|
CLSCTX_INPROC_SERVER,
|
|
__uuidof(IUIAutomation),
|
|
reinterpret_cast<void **>(&automation));
|
|
if (FAILED(hr)) {
|
|
return {};
|
|
}
|
|
|
|
IUIAutomationElement *rootElement = nullptr;
|
|
hr = automation->ElementFromHandle(hwnd, &rootElement);
|
|
if (FAILED(hr)) {
|
|
automation->Release();
|
|
return {};
|
|
}
|
|
|
|
IUIAutomationTreeWalker *walker = nullptr;
|
|
hr = automation->get_ControlViewWalker(&walker);
|
|
if (FAILED(hr)) {
|
|
rootElement->Release();
|
|
automation->Release();
|
|
return {};
|
|
}
|
|
|
|
IUIAutomationElement *element = nullptr;
|
|
std::wstring result;
|
|
|
|
hr = walker->GetFirstChildElement(rootElement, &element);
|
|
while (SUCCEEDED(hr) && element != nullptr) {
|
|
auto text = GetControlText(hwnd, element);
|
|
if (!text.empty()) {
|
|
result += text + L"\n";
|
|
}
|
|
|
|
IUIAutomationElement *nextElement = nullptr;
|
|
hr = walker->GetNextSiblingElement(element, &nextElement);
|
|
element->Release();
|
|
element = nextElement;
|
|
}
|
|
|
|
walker->Release();
|
|
rootElement->Release();
|
|
automation->Release();
|
|
|
|
// Convert to std::string
|
|
int len = os_wcs_to_utf8(result.c_str(), 0, nullptr, 0);
|
|
std::string tmp;
|
|
tmp.resize(len);
|
|
os_wcs_to_utf8(result.c_str(), 0, &tmp[0], len + 1);
|
|
return tmp;
|
|
}
|
|
|
|
bool IsFullscreen(const std::string &title)
|
|
{
|
|
RECT appBounds;
|
|
MONITORINFO monitorInfo = {0};
|
|
|
|
HWND hwnd = NULL;
|
|
hwnd = getHWNDfromTitle(title);
|
|
|
|
if (!hwnd) {
|
|
return false;
|
|
}
|
|
|
|
monitorInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
|
|
&monitorInfo);
|
|
|
|
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
|
|
GetWindowRect(hwnd, &appBounds);
|
|
if (monitorInfo.rcMonitor.bottom == appBounds.bottom &&
|
|
monitorInfo.rcMonitor.top == appBounds.top &&
|
|
monitorInfo.rcMonitor.left == appBounds.left &&
|
|
monitorInfo.rcMonitor.right == appBounds.right) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GetProcessList(QStringList &processes)
|
|
{
|
|
|
|
HANDLE procSnapshot;
|
|
PROCESSENTRY32 procEntry;
|
|
|
|
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (procSnapshot == INVALID_HANDLE_VALUE) {
|
|
return;
|
|
}
|
|
|
|
procEntry.dwSize = sizeof(PROCESSENTRY32);
|
|
|
|
if (!Process32First(procSnapshot, &procEntry)) {
|
|
CloseHandle(procSnapshot);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
QString tempexe = QString::fromWCharArray(procEntry.szExeFile);
|
|
if (tempexe == "System") {
|
|
continue;
|
|
}
|
|
if (tempexe == "[System Process]") {
|
|
continue;
|
|
}
|
|
if (processes.contains(tempexe)) {
|
|
continue;
|
|
}
|
|
processes.append(tempexe);
|
|
} while (Process32Next(procSnapshot, &procEntry));
|
|
|
|
CloseHandle(procSnapshot);
|
|
}
|
|
|
|
static void GetForegroundProcessName(QString &proc)
|
|
{
|
|
// only checks if the current foreground window is from the same executable,
|
|
// may return true for any window from a program
|
|
HWND foregroundWindow = GetForegroundWindow();
|
|
DWORD processId = 0;
|
|
GetWindowThreadProcessId(foregroundWindow, &processId);
|
|
|
|
HANDLE process = OpenProcess(
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
|
|
if (process == NULL) {
|
|
return;
|
|
}
|
|
|
|
WCHAR executablePath[600];
|
|
GetModuleFileNameEx(process, 0, executablePath, 600);
|
|
CloseHandle(process);
|
|
|
|
proc = QString::fromWCharArray(executablePath)
|
|
.split(QRegularExpression("(/|\\\\)"))
|
|
.back();
|
|
}
|
|
|
|
void GetForegroundProcessName(std::string &proc)
|
|
{
|
|
QString temp;
|
|
GetForegroundProcessName(temp);
|
|
proc = temp.toStdString();
|
|
}
|
|
|
|
bool IsInFocus(const QString &executable)
|
|
{
|
|
// only checks if the current foreground window is from the same executable,
|
|
// may return true for any window from a program
|
|
QString foregroundProc;
|
|
GetForegroundProcessName(foregroundProc);
|
|
|
|
// True if executable switch equals current window
|
|
bool equals = (executable == foregroundProc);
|
|
// True if executable switch matches current window
|
|
bool matches = foregroundProc.contains(QRegularExpression(executable));
|
|
|
|
return (equals || matches);
|
|
}
|
|
|
|
static int getLastInputTime()
|
|
{
|
|
LASTINPUTINFO lastInputInfo;
|
|
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
|
|
if (GetLastInputInfo(&lastInputInfo)) {
|
|
return lastInputInfo.dwTime;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int getTime()
|
|
{
|
|
return GetTickCount();
|
|
}
|
|
|
|
int SecondsSinceLastInput()
|
|
{
|
|
return (getTime() - getLastInputTime()) / 1000;
|
|
}
|
|
|
|
void PlatformInit()
|
|
{
|
|
CoInitialize(NULL);
|
|
}
|
|
|
|
void PlatformCleanup()
|
|
{
|
|
CoUninitialize();
|
|
}
|
|
|
|
} // namespace advss
|