mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-27 12:25:23 -05:00
451 lines
10 KiB
C++
451 lines
10 KiB
C++
#include "../headers/hotkey.hpp"
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <util/platform.h>
|
|
#include <TlHelp32.h>
|
|
#include <Psapi.h>
|
|
#include <locale>
|
|
#include <codecvt>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <QStringList>
|
|
#include <QRegularExpression>
|
|
#include "..\headers\platform-funcs.hpp"
|
|
|
|
#define MAX_SEARCH 1000
|
|
|
|
bool canSimulateKeyPresses = true;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
void GetWindowList(std::vector<std::string> &windows)
|
|
{
|
|
windows.resize(0);
|
|
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
|
|
}
|
|
|
|
void GetWindowList(QStringList &windows)
|
|
{
|
|
windows.clear();
|
|
|
|
std::vector<std::string> w;
|
|
GetWindowList(w);
|
|
for (auto window : w) {
|
|
windows << QString::fromStdString(window);
|
|
}
|
|
|
|
// Add entry for OBS Studio itself, see GetCurrentWindowTitle
|
|
windows << QString("OBS");
|
|
}
|
|
|
|
void GetCurrentWindowTitle(std::string &title)
|
|
{
|
|
HWND window = GetForegroundWindow();
|
|
DWORD pid;
|
|
DWORD thid;
|
|
thid = GetWindowThreadProcessId(window, &pid);
|
|
// GetWindowText will freeze if the control it is reading was created in another thread.
|
|
// It does not directly read the control.
|
|
// Instead it waits for the thread that created the control to process a WM_GETTEXT message.
|
|
// So if that thread is frozen in a WaitFor... call you have a deadlock.
|
|
if (GetCurrentProcessId() == pid) {
|
|
title = "OBS";
|
|
return;
|
|
}
|
|
GetWindowTitle(window, title);
|
|
}
|
|
|
|
std::pair<int, int> getCursorPos()
|
|
{
|
|
std::pair<int, int> pos(0, 0);
|
|
POINT cursorPos;
|
|
if (GetPhysicalCursorPos(&cursorPos)) {
|
|
pos.first = cursorPos.x;
|
|
pos.second = cursorPos.y;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
HWND getHWNDfromTitle(std::string title)
|
|
{
|
|
HWND hwnd = NULL;
|
|
|
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
std::wstring wTitle = converter.from_bytes(title);
|
|
|
|
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle.c_str());
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
HWND foregroundWindow = GetForegroundWindow();
|
|
DWORD processId = 0;
|
|
GetWindowThreadProcessId(foregroundWindow, &processId);
|
|
|
|
HANDLE process = OpenProcess(
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
|
|
if (process == NULL) {
|
|
return false;
|
|
}
|
|
|
|
WCHAR executablePath[600];
|
|
GetModuleFileNameEx(process, 0, executablePath, 600);
|
|
CloseHandle(process);
|
|
|
|
QString file = QString::fromWCharArray(executablePath)
|
|
.split(QRegularExpression("(/|\\\\)"))
|
|
.back();
|
|
|
|
// True if executable switch equals current window
|
|
bool equals = (executable == file);
|
|
// True if executable switch matches current window
|
|
bool matches = file.contains(QRegularExpression(executable));
|
|
|
|
return (equals || matches);
|
|
}
|
|
|
|
static std::unordered_map<HotkeyType, long> keyTable = {
|
|
// Chars
|
|
{HotkeyType::Key_A, 0x41},
|
|
{HotkeyType::Key_B, 0x42},
|
|
{HotkeyType::Key_C, 0x43},
|
|
{HotkeyType::Key_D, 0x44},
|
|
{HotkeyType::Key_E, 0x45},
|
|
{HotkeyType::Key_F, 0x46},
|
|
{HotkeyType::Key_G, 0x47},
|
|
{HotkeyType::Key_H, 0x48},
|
|
{HotkeyType::Key_I, 0x49},
|
|
{HotkeyType::Key_J, 0x4A},
|
|
{HotkeyType::Key_K, 0x4B},
|
|
{HotkeyType::Key_L, 0x4C},
|
|
{HotkeyType::Key_M, 0x4D},
|
|
{HotkeyType::Key_N, 0x4E},
|
|
{HotkeyType::Key_O, 0x4F},
|
|
{HotkeyType::Key_P, 0x50},
|
|
{HotkeyType::Key_Q, 0x51},
|
|
{HotkeyType::Key_R, 0x52},
|
|
{HotkeyType::Key_S, 0x53},
|
|
{HotkeyType::Key_T, 0x54},
|
|
{HotkeyType::Key_U, 0x55},
|
|
{HotkeyType::Key_V, 0x56},
|
|
{HotkeyType::Key_W, 0x57},
|
|
{HotkeyType::Key_X, 0x58},
|
|
{HotkeyType::Key_Y, 0x59},
|
|
{HotkeyType::Key_Z, 0x5A},
|
|
|
|
// Numbers
|
|
{HotkeyType::Key_0, 0x30},
|
|
{HotkeyType::Key_1, 0x31},
|
|
{HotkeyType::Key_2, 0x32},
|
|
{HotkeyType::Key_3, 0x33},
|
|
{HotkeyType::Key_4, 0x34},
|
|
{HotkeyType::Key_5, 0x35},
|
|
{HotkeyType::Key_6, 0x36},
|
|
{HotkeyType::Key_7, 0x37},
|
|
{HotkeyType::Key_8, 0x38},
|
|
{HotkeyType::Key_9, 0x39},
|
|
|
|
{HotkeyType::Key_F1, VK_F1},
|
|
{HotkeyType::Key_F2, VK_F2},
|
|
{HotkeyType::Key_F3, VK_F3},
|
|
{HotkeyType::Key_F4, VK_F4},
|
|
{HotkeyType::Key_F5, VK_F5},
|
|
{HotkeyType::Key_F6, VK_F6},
|
|
{HotkeyType::Key_F7, VK_F7},
|
|
{HotkeyType::Key_F8, VK_F8},
|
|
{HotkeyType::Key_F9, VK_F9},
|
|
{HotkeyType::Key_F10, VK_F10},
|
|
{HotkeyType::Key_F11, VK_F11},
|
|
{HotkeyType::Key_F12, VK_F12},
|
|
{HotkeyType::Key_F13, VK_F13},
|
|
{HotkeyType::Key_F14, VK_F14},
|
|
{HotkeyType::Key_F15, VK_F15},
|
|
{HotkeyType::Key_F16, VK_F16},
|
|
{HotkeyType::Key_F17, VK_F17},
|
|
{HotkeyType::Key_F18, VK_F18},
|
|
{HotkeyType::Key_F19, VK_F19},
|
|
{HotkeyType::Key_F20, VK_F20},
|
|
{HotkeyType::Key_F21, VK_F21},
|
|
{HotkeyType::Key_F22, VK_F22},
|
|
{HotkeyType::Key_F23, VK_F23},
|
|
{HotkeyType::Key_F24, VK_F24},
|
|
|
|
{HotkeyType::Key_Escape, VK_ESCAPE},
|
|
{HotkeyType::Key_Space, VK_SPACE},
|
|
{HotkeyType::Key_Return, VK_RETURN},
|
|
{HotkeyType::Key_Backspace, VK_BACK},
|
|
{HotkeyType::Key_Tab, VK_TAB},
|
|
|
|
{HotkeyType::Key_Shift_L, VK_LSHIFT},
|
|
{HotkeyType::Key_Shift_R, VK_RSHIFT},
|
|
{HotkeyType::Key_Control_L, VK_LCONTROL},
|
|
{HotkeyType::Key_Control_R, VK_RCONTROL},
|
|
{HotkeyType::Key_Alt_L, VK_LMENU},
|
|
{HotkeyType::Key_Alt_R, VK_RMENU},
|
|
{HotkeyType::Key_Win_L, VK_LWIN},
|
|
{HotkeyType::Key_Win_R, VK_RWIN},
|
|
{HotkeyType::Key_Apps, VK_APPS},
|
|
|
|
{HotkeyType::Key_CapsLock, VK_CAPITAL},
|
|
{HotkeyType::Key_NumLock, VK_NUMLOCK},
|
|
{HotkeyType::Key_ScrollLock, VK_SCROLL},
|
|
|
|
{HotkeyType::Key_PrintScreen, VK_SNAPSHOT},
|
|
{HotkeyType::Key_Pause, VK_PAUSE},
|
|
|
|
{HotkeyType::Key_Insert, VK_INSERT},
|
|
{HotkeyType::Key_Delete, VK_DELETE},
|
|
{HotkeyType::Key_PageUP, VK_PRIOR},
|
|
{HotkeyType::Key_PageDown, VK_NEXT},
|
|
{HotkeyType::Key_Home, VK_HOME},
|
|
{HotkeyType::Key_End, VK_END},
|
|
|
|
{HotkeyType::Key_Left, VK_LEFT},
|
|
{HotkeyType::Key_Up, VK_UP},
|
|
{HotkeyType::Key_Right, VK_RIGHT},
|
|
{HotkeyType::Key_Down, VK_DOWN},
|
|
|
|
{HotkeyType::Key_Numpad0, VK_NUMPAD0},
|
|
{HotkeyType::Key_Numpad1, VK_NUMPAD1},
|
|
{HotkeyType::Key_Numpad2, VK_NUMPAD2},
|
|
{HotkeyType::Key_Numpad3, VK_NUMPAD3},
|
|
{HotkeyType::Key_Numpad4, VK_NUMPAD4},
|
|
{HotkeyType::Key_Numpad5, VK_NUMPAD5},
|
|
{HotkeyType::Key_Numpad6, VK_NUMPAD6},
|
|
{HotkeyType::Key_Numpad7, VK_NUMPAD7},
|
|
{HotkeyType::Key_Numpad8, VK_NUMPAD8},
|
|
{HotkeyType::Key_Numpad9, VK_NUMPAD9},
|
|
|
|
{HotkeyType::Key_NumpadAdd, VK_ADD},
|
|
{HotkeyType::Key_NumpadSubtract, VK_SUBTRACT},
|
|
{HotkeyType::Key_NumpadMultiply, VK_MULTIPLY},
|
|
{HotkeyType::Key_NumpadDivide, VK_DIVIDE},
|
|
{HotkeyType::Key_NumpadDecimal, VK_DECIMAL},
|
|
{HotkeyType::Key_NumpadEnter, VK_RETURN},
|
|
};
|
|
|
|
void PressKeys(const std::vector<HotkeyType> keys)
|
|
{
|
|
INPUT ip;
|
|
ip.type = INPUT_KEYBOARD;
|
|
ip.ki.wScan = 0;
|
|
ip.ki.time = 0;
|
|
ip.ki.dwExtraInfo = 0;
|
|
|
|
// Press keys
|
|
ip.ki.dwFlags = 0;
|
|
for (auto &key : keys) {
|
|
auto it = keyTable.find(key);
|
|
if (it == keyTable.end()) {
|
|
continue;
|
|
}
|
|
ip.ki.wVk = it->second;
|
|
SendInput(1, &ip, sizeof(INPUT));
|
|
}
|
|
|
|
// When instantly releasing the key presses OBS might miss them
|
|
Sleep(300);
|
|
|
|
// Release keys
|
|
ip.ki.dwFlags = KEYEVENTF_KEYUP;
|
|
for (auto &key : keys) {
|
|
auto it = keyTable.find(key);
|
|
if (it == keyTable.end()) {
|
|
continue;
|
|
}
|
|
ip.ki.wVk = it->second;
|
|
SendInput(1, &ip, sizeof(INPUT));
|
|
}
|
|
}
|
|
|
|
int getLastInputTime()
|
|
{
|
|
LASTINPUTINFO lastInputInfo;
|
|
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
|
|
if (GetLastInputInfo(&lastInputInfo)) {
|
|
return lastInputInfo.dwTime;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int getTime()
|
|
{
|
|
return GetTickCount();
|
|
}
|
|
|
|
int secondsSinceLastInput()
|
|
{
|
|
return (getTime() - getLastInputTime()) / 1000;
|
|
}
|
|
|
|
void PlatformInit() {}
|
|
|
|
void PlatformCleanup() {}
|