mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 18:04:59 -05:00
388 lines
9.4 KiB
C++
388 lines
9.4 KiB
C++
#include "hotkey-helpers.hpp"
|
|
#include "log-helper.hpp"
|
|
#include "plugin-state-helpers.hpp"
|
|
|
|
#include <obs-frontend-api.h>
|
|
#include <QAbstractEventDispatcher>
|
|
#include <QAbstractNativeEventFilter>
|
|
#include <unordered_map>
|
|
#include <util/platform.h>
|
|
#include <windows.h>
|
|
|
|
namespace advss {
|
|
|
|
static bool canSimulateKeyPresses = true;
|
|
|
|
bool CanSimulateKeyPresses()
|
|
{
|
|
return canSimulateKeyPresses;
|
|
}
|
|
|
|
static const 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, int duration)
|
|
{
|
|
const int repeatInterval = 100;
|
|
|
|
INPUT ip;
|
|
ip.type = INPUT_KEYBOARD;
|
|
ip.ki.wScan = 0;
|
|
ip.ki.time = 0;
|
|
ip.ki.dwExtraInfo = 0;
|
|
|
|
for (int cur = 0; cur < duration; cur += repeatInterval) {
|
|
// Press keys
|
|
ip.ki.dwFlags = 0;
|
|
for (const auto &key : keys) {
|
|
auto it = keyTable.find(key);
|
|
if (it == keyTable.end()) {
|
|
continue;
|
|
}
|
|
ip.ki.wVk = (WORD)it->second;
|
|
SendInput(1, &ip, sizeof(INPUT));
|
|
}
|
|
// When instantly releasing the key presses OBS might miss them
|
|
Sleep(repeatInterval);
|
|
}
|
|
|
|
// Release keys
|
|
ip.ki.dwFlags = KEYEVENTF_KEYUP;
|
|
for (const auto &key : keys) {
|
|
auto it = keyTable.find(key);
|
|
if (it == keyTable.end()) {
|
|
continue;
|
|
}
|
|
ip.ki.wVk = (WORD)it->second;
|
|
SendInput(1, &ip, sizeof(INPUT));
|
|
}
|
|
}
|
|
|
|
static bool windowIsValid(HWND window)
|
|
{
|
|
if (!IsWindowVisible(window)) {
|
|
return false;
|
|
}
|
|
|
|
// Only accept for top level windows and not controls within them
|
|
if (window != GetAncestor(window, GA_ROOT)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::vector<HWND> getHWNDfromTitle(const std::string &title)
|
|
{
|
|
HWND hwnd = nullptr;
|
|
HWND hPrevWnd = nullptr;
|
|
std::vector<HWND> hwnds;
|
|
wchar_t wTitle[512];
|
|
os_utf8_to_wcs(title.c_str(), 0, wTitle, 512);
|
|
while ((hwnd = FindWindowEx(NULL, hPrevWnd, NULL, wTitle)) != nullptr) {
|
|
if (windowIsValid(hwnd)) {
|
|
hwnds.emplace_back(hwnd);
|
|
}
|
|
hPrevWnd = hwnd;
|
|
}
|
|
return hwnds;
|
|
}
|
|
|
|
std::string GetWindowClassByWindowTitle(const std::string &window)
|
|
{
|
|
auto hwnds = getHWNDfromTitle(window);
|
|
if (hwnds.empty()) {
|
|
return "";
|
|
}
|
|
auto hwnd = hwnds.at(0);
|
|
std::wstring wClass;
|
|
wClass.resize(1024);
|
|
if (!GetClassNameW(hwnd, &wClass[0], wClass.capacity())) {
|
|
return "";
|
|
}
|
|
|
|
size_t len = os_wcs_to_utf8(wClass.c_str(), 0, nullptr, 0);
|
|
std::string className;
|
|
className.resize(len);
|
|
os_wcs_to_utf8(wClass.c_str(), 0, &className[0], len + 1);
|
|
return className;
|
|
}
|
|
|
|
void SetFocusWindow(const std::string &title)
|
|
{
|
|
auto hwnds = getHWNDfromTitle(title);
|
|
if (hwnds.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto &handle : hwnds) {
|
|
const bool isMinimized = IsIconic(handle);
|
|
ShowWindow(handle, isMinimized ? SW_RESTORE : SW_SHOW);
|
|
SetForegroundWindow(handle);
|
|
SetFocus(handle);
|
|
SetActiveWindow(handle);
|
|
}
|
|
}
|
|
|
|
void CloseWindow(const std::string &title)
|
|
{
|
|
auto hwnds = getHWNDfromTitle(title);
|
|
if (hwnds.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto &handle : hwnds) {
|
|
SendMessage(handle, WM_CLOSE, 0, 0);
|
|
}
|
|
}
|
|
|
|
void MaximizeWindow(const std::string &title)
|
|
{
|
|
auto hwnds = getHWNDfromTitle(title);
|
|
if (hwnds.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto &handle : hwnds) {
|
|
ShowWindow(handle, SW_MAXIMIZE);
|
|
}
|
|
}
|
|
|
|
void MinimizeWindow(const std::string &title)
|
|
{
|
|
auto hwnds = getHWNDfromTitle(title);
|
|
if (hwnds.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto &handle : hwnds) {
|
|
ShowWindow(handle, SW_MINIMIZE);
|
|
}
|
|
}
|
|
|
|
class RawMouseInputFilter;
|
|
static RawMouseInputFilter *mouseInputFilter;
|
|
static std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};
|
|
static std::chrono::high_resolution_clock::time_point lastMouseMiddleClickTime{};
|
|
static std::chrono::high_resolution_clock::time_point lastMouseRightClickTime{};
|
|
|
|
std::chrono::high_resolution_clock::time_point GetLastMouseLeftClickTime()
|
|
{
|
|
return lastMouseLeftClickTime;
|
|
}
|
|
|
|
std::chrono::high_resolution_clock::time_point GetLastMouseMiddleClickTime()
|
|
{
|
|
return lastMouseMiddleClickTime;
|
|
}
|
|
|
|
std::chrono::high_resolution_clock::time_point GetLastMouseRightClickTime()
|
|
{
|
|
return lastMouseRightClickTime;
|
|
}
|
|
|
|
static void handleRawMouseInput(LPARAM lParam)
|
|
{
|
|
UINT dwSize;
|
|
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize,
|
|
sizeof(RAWINPUTHEADER));
|
|
LPBYTE lpb = new BYTE[dwSize];
|
|
if (lpb == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,
|
|
sizeof(RAWINPUTHEADER)) != dwSize)
|
|
OutputDebugString(TEXT(
|
|
"GetRawInputData does not return correct size !\n"));
|
|
|
|
RAWINPUT *raw = (RAWINPUT *)lpb;
|
|
if (raw->header.dwType == RIM_TYPEMOUSE) {
|
|
switch (raw->data.mouse.usButtonFlags) {
|
|
case RI_MOUSE_BUTTON_1_DOWN:
|
|
lastMouseLeftClickTime =
|
|
std::chrono::high_resolution_clock::now();
|
|
break;
|
|
case RI_MOUSE_BUTTON_3_DOWN:
|
|
lastMouseMiddleClickTime =
|
|
std::chrono::high_resolution_clock::now();
|
|
break;
|
|
case RI_MOUSE_BUTTON_2_DOWN:
|
|
lastMouseRightClickTime =
|
|
std::chrono::high_resolution_clock::now();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
delete[] lpb;
|
|
}
|
|
|
|
class RawMouseInputFilter : public QAbstractNativeEventFilter {
|
|
public:
|
|
virtual bool nativeEventFilter(const QByteArray &eventType,
|
|
void *message, qintptr *) Q_DECL_OVERRIDE
|
|
{
|
|
if (eventType != "windows_generic_MSG") {
|
|
return false;
|
|
}
|
|
MSG *msg = reinterpret_cast<MSG *>(message);
|
|
|
|
if (msg->message != WM_INPUT) {
|
|
return false;
|
|
}
|
|
handleRawMouseInput(msg->lParam);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
static void setupMouseEeventFilter()
|
|
{
|
|
mouseInputFilter = new RawMouseInputFilter;
|
|
QAbstractEventDispatcher::instance()->installNativeEventFilter(
|
|
mouseInputFilter);
|
|
|
|
RAWINPUTDEVICE rid;
|
|
rid.dwFlags = RIDEV_INPUTSINK;
|
|
rid.usUsagePage = 1;
|
|
rid.usUsage = 2;
|
|
rid.hwndTarget = (HWND)obs_frontend_get_main_window_handle();
|
|
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
|
|
blog(LOG_WARNING, "Registering for raw mouse input failed!\n"
|
|
"Mouse click detection not functional!");
|
|
}
|
|
}
|
|
|
|
static bool setup()
|
|
{
|
|
AddPluginInitStep(setupMouseEeventFilter);
|
|
AddPluginCleanupStep([]() {
|
|
if (mouseInputFilter) {
|
|
delete mouseInputFilter;
|
|
mouseInputFilter = nullptr;
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
static bool init = setup();
|
|
|
|
} // namespace advss
|