From ae4e861f7fef13266853adef0a5e0909db1cccc3 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 13 Jun 2021 16:21:23 -0700 Subject: [PATCH] Add macro action "hotkey" (#226) --- .github/workflows/build.yml | 1 + CMakeLists.txt | 3 + data/locale/en-US.ini | 11 + src/advanced-scene-switcher.cpp | 4 + src/headers/hotkey.hpp | 125 +++++++ src/headers/macro-action-hotkey.hpp | 78 +++++ src/headers/platform-funcs.hpp | 4 + src/hotkey.cpp | 3 +- src/linux/advanced-scene-switcher-nix.cpp | 192 ++++++++++- src/macro-action-hotkey.cpp | 377 ++++++++++++++++++++++ src/osx/advanced-scene-switcher-osx.mm | 219 +++++++++++++ src/win/advanced-scene-switcher-win.cpp | 163 ++++++++++ 12 files changed, 1174 insertions(+), 6 deletions(-) create mode 100644 src/headers/hotkey.hpp create mode 100644 src/headers/macro-action-hotkey.hpp create mode 100644 src/macro-action-hotkey.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a902d71..cb613404 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -160,6 +160,7 @@ jobs: libxcb-xinerama0-dev \ libxcomposite-dev \ libxinerama-dev \ + libxtst-dev \ libmbedtls-dev \ pkg-config \ python3-dev \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c8d376f..41e38954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ set(advanced-scene-switcher_HEADERS src/headers/macro-action-edit.hpp src/headers/macro-action-audio.hpp src/headers/macro-action-filter.hpp + src/headers/macro-action-hotkey.hpp src/headers/macro-action-macro.hpp src/headers/macro-action-media.hpp src/headers/macro-action-plugin-state.hpp @@ -115,6 +116,7 @@ set(advanced-scene-switcher_HEADERS src/headers/macro.hpp src/headers/macro-selection.hpp src/headers/curl-helper.hpp + src/headers/hotkey.hpp src/headers/screenshot-helper.hpp src/headers/name-dialog.hpp src/headers/duration-control.hpp @@ -153,6 +155,7 @@ set(advanced-scene-switcher_SOURCES src/macro-action-edit.cpp src/macro-action-audio.cpp src/macro-action-filter.cpp + src/macro-action-hotkey.cpp src/macro-action-macro.cpp src/macro-action-media.cpp src/macro-action-plugin-state.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 830baae7..03de1d1a 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -229,6 +229,17 @@ AdvSceneSwitcher.action.virtualCamera="Virtual camera" AdvSceneSwitcher.action.virtualCamera.type.stop="Stop virtual camera" AdvSceneSwitcher.action.virtualCamera.type.start="Start virtual camera" AdvSceneSwitcher.action.virtualCamera.entry="{{actions}}" +AdvSceneSwitcher.action.hotkey="Hotkey" +AdvSceneSwitcher.action.hotkey.leftShift="Left Shift" +AdvSceneSwitcher.action.hotkey.rightShift="Right Shift" +AdvSceneSwitcher.action.hotkey.leftCtrl="Left Ctrl" +AdvSceneSwitcher.action.hotkey.rightCtrl="Right Ctrl" +AdvSceneSwitcher.action.hotkey.leftAlt="Left Alt" +AdvSceneSwitcher.action.hotkey.rightAlt="Right Alt" +AdvSceneSwitcher.action.hotkey.leftMeta="Left Meta" +AdvSceneSwitcher.action.hotkey.rightMeta="Right Meta" +AdvSceneSwitcher.action.hotkey.disabled="Cannot simulate key presses - functionality disabled!" +AdvSceneSwitcher.action.hotkey.entry="Press {{keys}}" ; Transition Tab AdvSceneSwitcher.transitionTab.title="Transition" diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index f87dea31..87dd391e 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -433,6 +433,8 @@ extern "C" void FreeSceneSwitcher() loaded_curl_lib = nullptr; } + PlatformCleanup(); + delete switcher; switcher = nullptr; } @@ -546,6 +548,8 @@ extern "C" void InitSceneSwitcher() switcher->curl = f_curl_init(); } + PlatformInit(); + auto cb = []() { QMainWindow *window = (QMainWindow *)obs_frontend_get_main_window(); diff --git a/src/headers/hotkey.hpp b/src/headers/hotkey.hpp new file mode 100644 index 00000000..f337f9b0 --- /dev/null +++ b/src/headers/hotkey.hpp @@ -0,0 +1,125 @@ +#pragma once + +extern bool canSimulateKeyPresses; + +enum class HotkeyType { + Key_NoKey = 0, + + Key_A, + Key_B, + Key_C, + Key_D, + Key_E, + Key_F, + Key_G, + Key_H, + Key_I, + Key_J, + Key_K, + Key_L, + Key_M, + Key_N, + Key_O, + Key_P, + Key_Q, + Key_R, + Key_S, + Key_T, + Key_U, + Key_V, + Key_W, + Key_X, + Key_Y, + Key_Z, + + Key_0, + Key_1, + Key_2, + Key_3, + Key_4, + Key_5, + Key_6, + Key_7, + Key_8, + Key_9, + + Key_F1, + Key_F2, + Key_F3, + Key_F4, + Key_F5, + Key_F6, + Key_F7, + Key_F8, + Key_F9, + Key_F10, + Key_F11, + Key_F12, + Key_F13, + Key_F14, + Key_F15, + Key_F16, + Key_F17, + Key_F18, + Key_F19, + Key_F20, + Key_F21, + Key_F22, + Key_F23, + Key_F24, + + Key_Escape, + Key_Space, + Key_Return, + Key_Backspace, + Key_Tab, + + Key_Shift_L, + Key_Shift_R, + Key_Control_L, + Key_Control_R, + Key_Alt_L, + Key_Alt_R, + Key_Win_L, + Key_Win_R, + Key_Apps, + + Key_CapsLock, + Key_NumLock, + Key_ScrollLock, + + Key_PrintScreen, + Key_Pause, + + Key_Insert, + Key_Delete, + Key_PageUP, + Key_PageDown, + Key_Home, + Key_End, + + Key_Left, + Key_Right, + Key_Up, + Key_Down, + + Key_Numpad0, + Key_Numpad1, + Key_Numpad2, + Key_Numpad3, + Key_Numpad4, + Key_Numpad5, + Key_Numpad6, + Key_Numpad7, + Key_Numpad8, + Key_Numpad9, + + Key_NumpadAdd, + Key_NumpadSubtract, + Key_NumpadMultiply, + Key_NumpadDivide, + Key_NumpadDecimal, + Key_NumpadEnter +}; + +void registerHotkeys(); diff --git a/src/headers/macro-action-hotkey.hpp b/src/headers/macro-action-hotkey.hpp new file mode 100644 index 00000000..3662855d --- /dev/null +++ b/src/headers/macro-action-hotkey.hpp @@ -0,0 +1,78 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "hotkey.hpp" + +#include + +class MacroActionHotkey : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _HotkeySource; + HotkeyType _key = HotkeyType::Key_NoKey; + bool _leftShift = false; + bool _rightShift = false; + bool _leftCtrl = false; + bool _rightCtrl = false; + bool _leftAlt = false; + bool _rightAlt = false; + bool _leftMeta = false; + bool _rightMeta = false; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionHotkeyEdit : public QWidget { + Q_OBJECT + +public: + MacroActionHotkeyEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionHotkeyEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void KeyChanged(int key); + void LShiftChanged(int state); + void RShiftChanged(int state); + void LCtrlChanged(int state); + void RCtrlChanged(int state); + void LAltChanged(int state); + void RAltChanged(int state); + void LMetaChanged(int state); + void RMetaChanged(int state); + +protected: + QComboBox *_keys; + QCheckBox *_leftShift; + QCheckBox *_rightShift; + QCheckBox *_leftCtrl; + QCheckBox *_rightCtrl; + QCheckBox *_leftAlt; + QCheckBox *_rightAlt; + QCheckBox *_leftMeta; + QCheckBox *_rightMeta; + + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/platform-funcs.hpp b/src/headers/platform-funcs.hpp index d6164901..d890235a 100644 --- a/src/headers/platform-funcs.hpp +++ b/src/headers/platform-funcs.hpp @@ -1,4 +1,5 @@ #pragma once +#include "hotkey.hpp" void GetWindowList(std::vector &windows); void GetWindowList(QStringList &windows); @@ -9,3 +10,6 @@ std::pair getCursorPos(); int secondsSinceLastInput(); void GetProcessList(QStringList &processes); bool isInFocus(const QString &executable); +void PressKeys(const std::vector keys); +void PlatformInit(); +void PlatformCleanup(); diff --git a/src/hotkey.cpp b/src/hotkey.cpp index fc6c2ecf..9ad554fd 100644 --- a/src/hotkey.cpp +++ b/src/hotkey.cpp @@ -1,7 +1,8 @@ +#include "headers/hotkey.hpp" +#include "headers/advanced-scene-switcher.hpp" #include #include #include -#include "headers/advanced-scene-switcher.hpp" void startHotkeyFunc(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) diff --git a/src/linux/advanced-scene-switcher-nix.cpp b/src/linux/advanced-scene-switcher-nix.cpp index 297ea2b3..7b59a115 100644 --- a/src/linux/advanced-scene-switcher-nix.cpp +++ b/src/linux/advanced-scene-switcher-nix.cpp @@ -1,7 +1,11 @@ +#include "../headers/hotkey.hpp" + #include #include #include +#include #include +#include #undef Bool #undef CursorShape #undef Expose @@ -15,11 +19,18 @@ #undef Unsorted #include #include +#include #include #include +#include static Display *xdisplay = 0; +static QLibrary *libXtstHandle = nullptr; +typedef int (*keyPressFunc)(Display *, unsigned int, bool, unsigned long); +static keyPressFunc pressFunc = nullptr; +bool canSimulateKeyPresses = false; + Display *disp() { if (!xdisplay) @@ -242,14 +253,13 @@ std::pair getCursorPos() int win_y; unsigned int mask; - dpy = XOpenDisplay(NULL); + dpy = disp(); root = XDefaultRootWindow(dpy); if (XQueryPointer(dpy, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask)) { pos = std::pair(root_x, root_y); } - XCloseDisplay(dpy); return pos; } @@ -382,14 +392,186 @@ int secondsSinceLastInput() mit_info = XScreenSaverAllocInfo(); - if ((display = XOpenDisplay(NULL)) == NULL) { - return (-1); + if ((display = disp()) == NULL) { + return -1; } screen = DefaultScreen(display); XScreenSaverQueryInfo(display, RootWindow(display, screen), mit_info); idle_time = (mit_info->idle) / 1000; XFree(mit_info); - XCloseDisplay(display); return idle_time; } + +static std::unordered_map keyTable = { + // Chars + {HotkeyType::Key_A, XK_A}, + {HotkeyType::Key_B, XK_B}, + {HotkeyType::Key_C, XK_C}, + {HotkeyType::Key_D, XK_D}, + {HotkeyType::Key_E, XK_E}, + {HotkeyType::Key_F, XK_F}, + {HotkeyType::Key_G, XK_G}, + {HotkeyType::Key_H, XK_H}, + {HotkeyType::Key_I, XK_I}, + {HotkeyType::Key_J, XK_J}, + {HotkeyType::Key_K, XK_K}, + {HotkeyType::Key_L, XK_L}, + {HotkeyType::Key_M, XK_M}, + {HotkeyType::Key_N, XK_N}, + {HotkeyType::Key_O, XK_O}, + {HotkeyType::Key_P, XK_P}, + {HotkeyType::Key_Q, XK_Q}, + {HotkeyType::Key_R, XK_R}, + {HotkeyType::Key_S, XK_S}, + {HotkeyType::Key_T, XK_T}, + {HotkeyType::Key_U, XK_U}, + {HotkeyType::Key_V, XK_V}, + {HotkeyType::Key_W, XK_W}, + {HotkeyType::Key_X, XK_X}, + {HotkeyType::Key_Y, XK_Y}, + {HotkeyType::Key_Z, XK_Z}, + + // Numbers + {HotkeyType::Key_0, XK_0}, + {HotkeyType::Key_1, XK_1}, + {HotkeyType::Key_2, XK_2}, + {HotkeyType::Key_3, XK_3}, + {HotkeyType::Key_4, XK_4}, + {HotkeyType::Key_5, XK_5}, + {HotkeyType::Key_6, XK_6}, + {HotkeyType::Key_7, XK_7}, + {HotkeyType::Key_8, XK_8}, + {HotkeyType::Key_9, XK_9}, + + {HotkeyType::Key_F1, XK_F1}, + {HotkeyType::Key_F2, XK_F2}, + {HotkeyType::Key_F3, XK_F3}, + {HotkeyType::Key_F4, XK_F4}, + {HotkeyType::Key_F5, XK_F5}, + {HotkeyType::Key_F6, XK_F6}, + {HotkeyType::Key_F7, XK_F7}, + {HotkeyType::Key_F8, XK_F8}, + {HotkeyType::Key_F9, XK_F9}, + {HotkeyType::Key_F10, XK_F10}, + {HotkeyType::Key_F11, XK_F11}, + {HotkeyType::Key_F12, XK_F12}, + {HotkeyType::Key_F13, XK_F13}, + {HotkeyType::Key_F14, XK_F14}, + {HotkeyType::Key_F15, XK_F15}, + {HotkeyType::Key_F16, XK_F16}, + {HotkeyType::Key_F17, XK_F17}, + {HotkeyType::Key_F18, XK_F18}, + {HotkeyType::Key_F19, XK_F19}, + {HotkeyType::Key_F20, XK_F20}, + {HotkeyType::Key_F21, XK_F21}, + {HotkeyType::Key_F22, XK_F22}, + {HotkeyType::Key_F23, XK_F23}, + {HotkeyType::Key_F24, XK_F24}, + + {HotkeyType::Key_Escape, XK_Escape}, + {HotkeyType::Key_Space, XK_space}, + {HotkeyType::Key_Return, XK_Return}, + {HotkeyType::Key_Backspace, XK_BackSpace}, + {HotkeyType::Key_Tab, XK_Tab}, + + {HotkeyType::Key_Shift_L, XK_Shift_L}, + {HotkeyType::Key_Shift_R, XK_Shift_R}, + {HotkeyType::Key_Control_L, XK_Control_L}, + {HotkeyType::Key_Control_R, XK_Control_R}, + {HotkeyType::Key_Alt_L, XK_Alt_L}, + {HotkeyType::Key_Alt_R, XK_Alt_R}, + {HotkeyType::Key_Win_L, XK_Super_L}, + {HotkeyType::Key_Win_R, XK_Super_R}, + {HotkeyType::Key_Apps, XK_Hyper_L}, + + {HotkeyType::Key_CapsLock, XK_Caps_Lock}, + {HotkeyType::Key_NumLock, XK_Num_Lock}, + {HotkeyType::Key_ScrollLock, XK_Scroll_Lock}, + + {HotkeyType::Key_PrintScreen, XK_Print}, + {HotkeyType::Key_Pause, XK_Pause}, + + {HotkeyType::Key_Insert, XK_Insert}, + {HotkeyType::Key_Delete, XK_Delete}, + {HotkeyType::Key_PageUP, XK_Page_Up}, + {HotkeyType::Key_PageDown, XK_Page_Down}, + {HotkeyType::Key_Home, XK_Home}, + {HotkeyType::Key_End, XK_End}, + + {HotkeyType::Key_Left, XK_Left}, + {HotkeyType::Key_Up, XK_Up}, + {HotkeyType::Key_Right, XK_Right}, + {HotkeyType::Key_Down, XK_Down}, + + {HotkeyType::Key_Numpad0, XK_KP_0}, + {HotkeyType::Key_Numpad1, XK_KP_1}, + {HotkeyType::Key_Numpad2, XK_KP_2}, + {HotkeyType::Key_Numpad3, XK_KP_3}, + {HotkeyType::Key_Numpad4, XK_KP_4}, + {HotkeyType::Key_Numpad5, XK_KP_5}, + {HotkeyType::Key_Numpad6, XK_KP_6}, + {HotkeyType::Key_Numpad7, XK_KP_7}, + {HotkeyType::Key_Numpad8, XK_KP_8}, + {HotkeyType::Key_Numpad9, XK_KP_9}, + + {HotkeyType::Key_NumpadAdd, XK_KP_Add}, + {HotkeyType::Key_NumpadSubtract, XK_KP_Subtract}, + {HotkeyType::Key_NumpadMultiply, XK_KP_Multiply}, + {HotkeyType::Key_NumpadDivide, XK_KP_Divide}, + {HotkeyType::Key_NumpadDecimal, XK_KP_Decimal}, + {HotkeyType::Key_NumpadEnter, XK_KP_Enter}, +}; + +void PressKeys(const std::vector keys) +{ + if (!canSimulateKeyPresses) { + return; + } + + Display *display = disp(); + if (display == NULL) + return; + + // Press keys + for (auto &key : keys) { + auto it = keyTable.find(key); + if (it == keyTable.end()) { + continue; + } + pressFunc(display, XKeysymToKeycode(display, it->second), true, + CurrentTime); + } + XFlush(display); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // Release keys + for (auto &key : keys) { + auto it = keyTable.find(key); + if (it == keyTable.end()) { + continue; + } + pressFunc(display, XKeysymToKeycode(display, it->second), false, + CurrentTime); + } + + XFlush(display); +} + +void PlatformInit() +{ + libXtstHandle = new QLibrary("libXtst.so", nullptr); + pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent"); + int _; + canSimulateKeyPresses = pressFunc && + !XQueryExtension(disp(), "XTEST", &_, &_, &_); +} + +void PlatformCleanup() +{ + delete libXtstHandle; + libXtstHandle = nullptr; + + cleanupDisplay(); +} diff --git a/src/macro-action-hotkey.cpp b/src/macro-action-hotkey.cpp new file mode 100644 index 00000000..4e3c6b22 --- /dev/null +++ b/src/macro-action-hotkey.cpp @@ -0,0 +1,377 @@ +#include "headers/macro-action-hotkey.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionHotkey::id = "hotkey"; + +// TODO: +// I can't seem to get this to work so drop support for this functionality +// on MacOS +#if !__APPLE__ +bool MacroActionHotkey::_registered = MacroActionFactory::Register( + MacroActionHotkey::id, + {MacroActionHotkey::Create, MacroActionHotkeyEdit::Create, + "AdvSceneSwitcher.action.hotkey"}); +#endif + +bool MacroActionHotkey::PerformAction() +{ + std::vector keys; + if (_leftShift) { + keys.push_back(HotkeyType::Key_Shift_L); + } + if (_rightShift) { + keys.push_back(HotkeyType::Key_Shift_R); + } + if (_leftCtrl) { + keys.push_back(HotkeyType::Key_Control_L); + } + if (_rightCtrl) { + keys.push_back(HotkeyType::Key_Control_R); + } + if (_leftAlt) { + keys.push_back(HotkeyType::Key_Alt_L); + } + if (_rightAlt) { + keys.push_back(HotkeyType::Key_Alt_R); + } + if (_key != HotkeyType::Key_NoKey) { + keys.push_back(_key); + } + + if (!keys.empty()) { + std::thread t([keys]() { PressKeys(keys); }); + t.detach(); + } + + return true; +} + +void MacroActionHotkey::LogAction() +{ + vblog(LOG_INFO, "sent hotkey"); +} + +bool MacroActionHotkey::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_int(obj, "key", static_cast(_key)); + obs_data_set_bool(obj, "left_shift", _leftShift); + obs_data_set_bool(obj, "right_shift", _rightShift); + obs_data_set_bool(obj, "left_ctrl", _leftCtrl); + obs_data_set_bool(obj, "right_ctrl", _rightCtrl); + obs_data_set_bool(obj, "left_alt", _leftAlt); + obs_data_set_bool(obj, "right_alt", _rightAlt); + obs_data_set_bool(obj, "left_meta", _leftMeta); + obs_data_set_bool(obj, "right_meta", _rightMeta); + return true; +} + +bool MacroActionHotkey::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _key = static_cast(obs_data_get_int(obj, "key")); + _leftShift = obs_data_get_bool(obj, "left_shift"); + _rightShift = obs_data_get_bool(obj, "right_shift"); + _leftCtrl = obs_data_get_bool(obj, "left_ctrl"); + _rightCtrl = obs_data_get_bool(obj, "right_ctrl"); + _leftAlt = obs_data_get_bool(obj, "left_alt"); + _rightAlt = obs_data_get_bool(obj, "right_alt"); + _leftMeta = obs_data_get_bool(obj, "left_meta"); + _rightMeta = obs_data_get_bool(obj, "right_meta"); + return true; +} + +static inline void populateKeySelection(QComboBox *list) +{ + list->addItem("No key"); + list->addItem("A"); + list->addItem("B"); + list->addItem("C"); + list->addItem("D"); + list->addItem("E"); + list->addItem("F"); + list->addItem("G"); + list->addItem("H"); + list->addItem("I"); + list->addItem("J"); + list->addItem("K"); + list->addItem("L"); + list->addItem("M"); + list->addItem("N"); + list->addItem("O"); + list->addItem("P"); + list->addItem("Q"); + list->addItem("R"); + list->addItem("S"); + list->addItem("T"); + list->addItem("U"); + list->addItem("V"); + list->addItem("W"); + list->addItem("X"); + list->addItem("Y"); + list->addItem("Z"); + list->addItem("0"); + list->addItem("1"); + list->addItem("2"); + list->addItem("3"); + list->addItem("4"); + list->addItem("5"); + list->addItem("6"); + list->addItem("7"); + list->addItem("8"); + list->addItem("9"); + list->addItem("F1"); + list->addItem("F2"); + list->addItem("F3"); + list->addItem("F4"); + list->addItem("F5"); + list->addItem("F6"); + list->addItem("F7"); + list->addItem("F8"); + list->addItem("F9"); + list->addItem("F10"); + list->addItem("F11"); + list->addItem("F12"); + list->addItem("F13"); + list->addItem("F14"); + list->addItem("F15"); + list->addItem("F16"); + list->addItem("F17"); + list->addItem("F18"); + list->addItem("F19"); + list->addItem("F20"); + list->addItem("F21"); + list->addItem("F22"); + list->addItem("F23"); + list->addItem("F24"); + list->addItem("Escape"); + list->addItem("Space"); + list->addItem("Return"); + list->addItem("Backspace"); + list->addItem("Tab"); + list->addItem("Shift_L"); + list->addItem("Shift_R"); + list->addItem("Control_L"); + list->addItem("Control_R"); + list->addItem("Alt_L"); + list->addItem("Alt_R"); + list->addItem("Win_L"); + list->addItem("Win_R"); + list->addItem("Apps"); + list->addItem("CapsLock"); + list->addItem("NumLock"); + list->addItem("ScrollLock"); + list->addItem("PrintScreen"); + list->addItem("Pause"); + list->addItem("Insert"); + list->addItem("Delete"); + list->addItem("PageUP"); + list->addItem("PageDown"); + list->addItem("Home"); + list->addItem("End"); + list->addItem("Left"); + list->addItem("Right"); + list->addItem("Up"); + list->addItem("Down"); + list->addItem("Numpad0"); + list->addItem("Numpad1"); + list->addItem("Numpad2"); + list->addItem("Numpad3"); + list->addItem("Numpad4"); + list->addItem("Numpad5"); + list->addItem("Numpad6"); + list->addItem("Numpad7"); + list->addItem("Numpad8"); + list->addItem("Numpad9"); + list->addItem("NumpadAdd"); + list->addItem("NumpadSubtract"); + list->addItem("NumpadMultiply"); + list->addItem("NumpadDivide"); + list->addItem("NumpadDecimal"); + list->addItem("NumpadEnter"); +} + +MacroActionHotkeyEdit::MacroActionHotkeyEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _keys = new QComboBox(); + _leftShift = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.leftShift")); + _rightShift = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.rightShift")); + _leftCtrl = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.leftCtrl")); + _rightCtrl = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.rightCtrl")); + _leftAlt = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.leftAlt")); + _rightAlt = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.rightAlt")); + _leftMeta = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.leftMeta")); + _rightMeta = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.hotkey.rightMeta")); + + populateKeySelection(_keys); + + QWidget::connect(_keys, SIGNAL(currentIndexChanged(int)), this, + SLOT(KeyChanged(int))); + QWidget::connect(_leftShift, SIGNAL(stateChanged(int)), this, + SLOT(LShiftChanged(int))); + QWidget::connect(_rightShift, SIGNAL(stateChanged(int)), this, + SLOT(RShiftChanged(int))); + QWidget::connect(_leftCtrl, SIGNAL(stateChanged(int)), this, + SLOT(LCtrlChanged(int))); + QWidget::connect(_rightCtrl, SIGNAL(stateChanged(int)), this, + SLOT(RCtrlChanged(int))); + QWidget::connect(_leftAlt, SIGNAL(stateChanged(int)), this, + SLOT(LAltChanged(int))); + QWidget::connect(_rightAlt, SIGNAL(stateChanged(int)), this, + SLOT(RAltChanged(int))); + QWidget::connect(_leftMeta, SIGNAL(stateChanged(int)), this, + SLOT(LMetaChanged(int))); + QWidget::connect(_rightMeta, SIGNAL(stateChanged(int)), this, + SLOT(RMetaChanged(int))); + + QHBoxLayout *line1Layout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{keys}}", _keys}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.hotkey.entry"), + line1Layout, widgetPlaceholders); + + QHBoxLayout *line2Layout = new QHBoxLayout; + line2Layout->addWidget(_leftShift); + line2Layout->addWidget(_rightShift); + line2Layout->addWidget(_leftCtrl); + line2Layout->addWidget(_rightCtrl); + line2Layout->addWidget(_leftAlt); + line2Layout->addWidget(_rightAlt); + line2Layout->addWidget(_leftMeta); + line2Layout->addWidget(_rightMeta); + line2Layout->addStretch(); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(line1Layout); + mainLayout->addLayout(line2Layout); + + if (!canSimulateKeyPresses) { + mainLayout->addWidget(new QLabel(obs_module_text( + "AdvSceneSwitcher.action.hotkey.disabled"))); + } + + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionHotkeyEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _keys->setCurrentIndex(static_cast(_entryData->_key)); + _leftShift->setChecked(_entryData->_leftShift); + _rightShift->setChecked(_entryData->_rightShift); + _leftCtrl->setChecked(_entryData->_leftCtrl); + _rightCtrl->setChecked(_entryData->_rightCtrl); + _leftAlt->setChecked(_entryData->_leftAlt); + _rightAlt->setChecked(_entryData->_rightAlt); + _leftMeta->setChecked(_entryData->_leftMeta); + _rightMeta->setChecked(_entryData->_rightMeta); +} + +void MacroActionHotkeyEdit::LShiftChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_leftShift = state; +} + +void MacroActionHotkeyEdit::RShiftChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_rightShift = state; +} + +void MacroActionHotkeyEdit::LCtrlChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_leftCtrl = state; +} + +void MacroActionHotkeyEdit::RCtrlChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_rightCtrl = state; +} + +void MacroActionHotkeyEdit::LAltChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_leftAlt = state; +} + +void MacroActionHotkeyEdit::RAltChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_rightAlt = state; +} + +void MacroActionHotkeyEdit::LMetaChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_leftMeta = state; +} + +void MacroActionHotkeyEdit::RMetaChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_rightMeta = state; +} + +void MacroActionHotkeyEdit::KeyChanged(int key) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_key = static_cast(key); +} diff --git a/src/osx/advanced-scene-switcher-osx.mm b/src/osx/advanced-scene-switcher-osx.mm index 9cb9ddd8..12533391 100644 --- a/src/osx/advanced-scene-switcher-osx.mm +++ b/src/osx/advanced-scene-switcher-osx.mm @@ -1,3 +1,5 @@ +#include "../headers/hotkey.hpp" + #import #import #import @@ -7,6 +9,10 @@ #include #include #include +#include +#include + +bool canSimulateKeyPresses = true; void GetWindowList(std::vector &windows) { @@ -281,3 +287,216 @@ bool isInFocus(const QString &executable) return (equals || matches); } + +static std::map keyTable = { + // Chars + {HotkeyType::Key_A, kVK_ANSI_A}, + {HotkeyType::Key_B, kVK_ANSI_B}, + {HotkeyType::Key_C, kVK_ANSI_C}, + {HotkeyType::Key_D, kVK_ANSI_D}, + {HotkeyType::Key_E, kVK_ANSI_E}, + {HotkeyType::Key_F, kVK_ANSI_F}, + {HotkeyType::Key_G, kVK_ANSI_G}, + {HotkeyType::Key_H, kVK_ANSI_H}, + {HotkeyType::Key_I, kVK_ANSI_I}, + {HotkeyType::Key_J, kVK_ANSI_J}, + {HotkeyType::Key_K, kVK_ANSI_K}, + {HotkeyType::Key_L, kVK_ANSI_L}, + {HotkeyType::Key_M, kVK_ANSI_M}, + {HotkeyType::Key_N, kVK_ANSI_N}, + {HotkeyType::Key_O, kVK_ANSI_O}, + {HotkeyType::Key_P, kVK_ANSI_P}, + {HotkeyType::Key_Q, kVK_ANSI_Q}, + {HotkeyType::Key_R, kVK_ANSI_R}, + {HotkeyType::Key_S, kVK_ANSI_S}, + {HotkeyType::Key_T, kVK_ANSI_T}, + {HotkeyType::Key_U, kVK_ANSI_U}, + {HotkeyType::Key_V, kVK_ANSI_V}, + {HotkeyType::Key_W, kVK_ANSI_W}, + {HotkeyType::Key_X, kVK_ANSI_X}, + {HotkeyType::Key_Y, kVK_ANSI_Y}, + {HotkeyType::Key_Z, kVK_ANSI_Z}, + + // Numbers + {HotkeyType::Key_0, kVK_ANSI_0}, + {HotkeyType::Key_1, kVK_ANSI_1}, + {HotkeyType::Key_2, kVK_ANSI_2}, + {HotkeyType::Key_3, kVK_ANSI_3}, + {HotkeyType::Key_4, kVK_ANSI_4}, + {HotkeyType::Key_5, kVK_ANSI_5}, + {HotkeyType::Key_6, kVK_ANSI_6}, + {HotkeyType::Key_7, kVK_ANSI_7}, + {HotkeyType::Key_8, kVK_ANSI_8}, + {HotkeyType::Key_9, kVK_ANSI_9}, + + {HotkeyType::Key_F1, kVK_F1}, + {HotkeyType::Key_F2, kVK_F2}, + {HotkeyType::Key_F3, kVK_F3}, + {HotkeyType::Key_F4, kVK_F4}, + {HotkeyType::Key_F5, kVK_F5}, + {HotkeyType::Key_F6, kVK_F6}, + {HotkeyType::Key_F7, kVK_F7}, + {HotkeyType::Key_F8, kVK_F8}, + {HotkeyType::Key_F9, kVK_F9}, + {HotkeyType::Key_F10, kVK_F10}, + {HotkeyType::Key_F11, kVK_F11}, + {HotkeyType::Key_F12, kVK_F12}, + {HotkeyType::Key_F13, kVK_F13}, + {HotkeyType::Key_F14, kVK_F14}, + {HotkeyType::Key_F15, kVK_F15}, + {HotkeyType::Key_F16, kVK_F16}, + {HotkeyType::Key_F17, kVK_F17}, + {HotkeyType::Key_F18, kVK_F18}, + {HotkeyType::Key_F19, kVK_F19}, + {HotkeyType::Key_F20, kVK_F20}, + // F21-F24 does not exist on MacOS + + {HotkeyType::Key_Escape, kVK_Escape}, + {HotkeyType::Key_Space, kVK_Space}, + {HotkeyType::Key_Return, kVK_Return}, + {HotkeyType::Key_Backspace, kVK_Delete}, + {HotkeyType::Key_Tab, kVK_Tab}, + + {HotkeyType::Key_Shift_L, kVK_Shift}, + {HotkeyType::Key_Shift_R, kVK_RightShift}, + {HotkeyType::Key_Control_L, kVK_Control}, + {HotkeyType::Key_Control_R, kVK_RightControl}, + {HotkeyType::Key_Alt_L, kVK_Option}, + {HotkeyType::Key_Alt_R, kVK_RightOption}, + {HotkeyType::Key_Win_L, kVK_Command}, + {HotkeyType::Key_Win_R, kVK_RightCommand}, + // Not sure what Key_Apps would correspond to + + {HotkeyType::Key_CapsLock, kVK_CapsLock}, + {HotkeyType::Key_NumLock, kVK_ANSI_KeypadClear}, + {HotkeyType::Key_ScrollLock, kVK_F14}, + + {HotkeyType::Key_PrintScreen, kVK_F13}, + {HotkeyType::Key_Pause, kVK_F15}, + + {HotkeyType::Key_Insert, kVK_Help}, + {HotkeyType::Key_Delete, kVK_ForwardDelete}, + {HotkeyType::Key_PageUP, kVK_PageUp}, + {HotkeyType::Key_PageDown, kVK_PageDown}, + {HotkeyType::Key_Home, kVK_Home}, + {HotkeyType::Key_End, kVK_End}, + + {HotkeyType::Key_Left, kVK_LeftArrow}, + {HotkeyType::Key_Up, kVK_UpArrow}, + {HotkeyType::Key_Right, kVK_RightArrow}, + {HotkeyType::Key_Down, kVK_DownArrow}, + + {HotkeyType::Key_Numpad0, kVK_ANSI_Keypad0}, + {HotkeyType::Key_Numpad1, kVK_ANSI_Keypad1}, + {HotkeyType::Key_Numpad2, kVK_ANSI_Keypad2}, + {HotkeyType::Key_Numpad3, kVK_ANSI_Keypad3}, + {HotkeyType::Key_Numpad4, kVK_ANSI_Keypad4}, + {HotkeyType::Key_Numpad5, kVK_ANSI_Keypad5}, + {HotkeyType::Key_Numpad6, kVK_ANSI_Keypad6}, + {HotkeyType::Key_Numpad7, kVK_ANSI_Keypad7}, + {HotkeyType::Key_Numpad8, kVK_ANSI_Keypad8}, + {HotkeyType::Key_Numpad9, kVK_ANSI_Keypad9}, + + {HotkeyType::Key_NumpadAdd, kVK_ANSI_KeypadPlus}, + {HotkeyType::Key_NumpadSubtract, kVK_ANSI_KeypadMinus}, + {HotkeyType::Key_NumpadMultiply, kVK_ANSI_KeypadMultiply}, + {HotkeyType::Key_NumpadDivide, kVK_ANSI_KeypadDivide}, + {HotkeyType::Key_NumpadDecimal, kVK_ANSI_KeypadDecimal}, + {HotkeyType::Key_NumpadEnter, kVK_ANSI_KeypadEnter}, +}; + +void PressKeys(const std::vector keys) +{ + // TODO: + // I can't seem to get this to work so drop support for this functionality + // on MacOS + canSimulateKeyPresses = false; + return; + + // Check premissions + NSDictionary *options = + @{(__bridge id)kAXTrustedCheckOptionPrompt: @NO}; + if (!AXIsProcessTrustedWithOptions((CFDictionaryRef)options)) { + NSString *urlString = + @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"; + [[NSWorkspace sharedWorkspace] + openURL:[NSURL URLWithString:urlString]]; + canSimulateKeyPresses = false; + return; + } + + long modifierFlags = 0; + auto source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + if (!source) { + canSimulateKeyPresses = false; + return; + } + + // Press keys + for (auto &key : keys) { + auto it = keyTable.find(key); + if (it == keyTable.end()) { + continue; + } + + CGKeyCode inputKeyCode = it->second; + CGEventRef keyPress = + CGEventCreateKeyboardEvent(source, inputKeyCode, true); + if (!keyPress) { + canSimulateKeyPresses = false; + CFRelease(source); + return; + } + CGEventSetFlags(keyPress, modifierFlags); + CGEventPost(kCGAnnotatedSessionEventTap, keyPress); + CFRelease(keyPress); + + switch (it->first) { + case HotkeyType::Key_Shift_L: + case HotkeyType::Key_Shift_R: + modifierFlags |= NX_SHIFTMASK; + break; + case HotkeyType::Key_Control_L: + case HotkeyType::Key_Control_R: + modifierFlags |= NX_CONTROLMASK; + break; + case HotkeyType::Key_Alt_L: + case HotkeyType::Key_Alt_R: + modifierFlags |= NX_ALTERNATEMASK; + break; + case HotkeyType::Key_Win_L: + case HotkeyType::Key_Win_R: + modifierFlags |= NX_COMMANDMASK; + break; + default: + break; + } + } + + // When instantly releasing the key presses OBS might miss them + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // Release keys + for (auto &key : keys) { + auto it = keyTable.find(key); + if (it == keyTable.end()) { + continue; + } + CGKeyCode inputKeyCode = it->second; + CGEventRef keyRelease = + CGEventCreateKeyboardEvent(source, inputKeyCode, false); + if (!keyRelease) { + canSimulateKeyPresses = false; + CFRelease(source); + return; + } + CGEventPost(kCGAnnotatedSessionEventTap, keyRelease); + CFRelease(keyRelease); + } + + CFRelease(source); +} + +void PlatformCleanup() {} + +void PlatformInit() {} diff --git a/src/win/advanced-scene-switcher-win.cpp b/src/win/advanced-scene-switcher-win.cpp index 13f4c247..e1dda957 100644 --- a/src/win/advanced-scene-switcher-win.cpp +++ b/src/win/advanced-scene-switcher-win.cpp @@ -1,3 +1,5 @@ +#include "../headers/hotkey.hpp" + #define WIN32_LEAN_AND_MEAN #include #include @@ -9,9 +11,12 @@ #include #include #include +#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); @@ -266,6 +271,160 @@ bool isInFocus(const QString &executable) return (equals || matches); } +static std::unordered_map 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 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; @@ -285,3 +444,7 @@ int secondsSinceLastInput() { return (getTime() - getLastInputTime()) / 1000; } + +void PlatformInit() {} + +void PlatformCleanup() {}