diff --git a/include/mgba/script/input.h b/include/mgba/script/input.h index f81ecda45..3815433f0 100644 --- a/include/mgba/script/input.h +++ b/include/mgba/script/input.h @@ -74,14 +74,14 @@ enum mScriptKeyModifier { mSCRIPT_KMOD_SCROLL_LOCK = 0x400, }; -#define mSCRIPT_KEYBASE 0x200000 +#define mSCRIPT_KEYBASE 0x800000 enum mScriptKey { mSCRIPT_KEY_NONE = 0, mSCRIPT_KEY_BACKSPACE = 0x000008, mSCRIPT_KEY_TAB = 0x000009, - mSCRIPT_KEY_LINE_FEED = 0x00000A, + mSCRIPT_KEY_ENTER = 0x00000A, mSCRIPT_KEY_ESCAPE = 0x00001B, mSCRIPT_KEY_DELETE = 0x00007F, @@ -121,9 +121,10 @@ enum mScriptKey { mSCRIPT_KEY_INSERT, mSCRIPT_KEY_BREAK, mSCRIPT_KEY_CLEAR, - mSCRIPT_KEY_PRNTSCR, + mSCRIPT_KEY_PRINT_SCREEN, mSCRIPT_KEY_SYSRQ, mSCRIPT_KEY_MENU, + mSCRIPT_KEY_HELP, mSCRIPT_KEY_LSHIFT = mSCRIPT_KEYBASE | 0x30, mSCRIPT_KEY_RSHIFT, @@ -140,6 +141,24 @@ enum mScriptKey { mSCRIPT_KEY_CAPS_LOCK, mSCRIPT_KEY_NUM_LOCK, mSCRIPT_KEY_SCROLL_LOCK, + + mSCRIPT_KEY_KP_0 = mSCRIPT_KEYBASE | 0x40, + mSCRIPT_KEY_KP_1, + mSCRIPT_KEY_KP_2, + mSCRIPT_KEY_KP_3, + mSCRIPT_KEY_KP_4, + mSCRIPT_KEY_KP_5, + mSCRIPT_KEY_KP_6, + mSCRIPT_KEY_KP_7, + mSCRIPT_KEY_KP_8, + mSCRIPT_KEY_KP_9, + mSCRIPT_KEY_KP_PLUS, + mSCRIPT_KEY_KP_MINUS, + mSCRIPT_KEY_KP_MULTIPLY, + mSCRIPT_KEY_KP_DIVIDE, + mSCRIPT_KEY_KP_COMMA, + mSCRIPT_KEY_KP_POINT, + mSCRIPT_KEY_KP_ENTER, }; struct mScriptEvent { @@ -151,22 +170,22 @@ struct mScriptEvent { struct mScriptKeyEvent { struct mScriptEvent d; uint8_t state; - uint8_t reserved; uint16_t modifiers; uint32_t key; }; struct mScriptMouseButtonEvent { struct mScriptEvent d; - uint8_t state; uint8_t mouse; + uint8_t context; + uint8_t state; uint8_t button; }; struct mScriptMouseMoveEvent { struct mScriptEvent d; - bool relative; uint8_t mouse; + uint8_t context; int32_t x; int32_t y; }; @@ -174,8 +193,8 @@ struct mScriptMouseMoveEvent { struct mScriptMouseWheelEvent { struct mScriptEvent d; uint8_t mouse; - int32_t x; - int32_t y; + int16_t x; + int16_t y; }; struct mScriptGamepadButtonEvent { diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 7548e674c..704f9463a 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -10,6 +10,7 @@ #include "DisplayGL.h" #include "DisplayQt.h" #include "LogController.h" +#include "utils.h" #include @@ -169,3 +170,32 @@ void QGBA::Display::mouseMoveEvent(QMouseEvent*) { m_mouseTimer.stop(); m_mouseTimer.start(); } + +QPoint QGBA::Display::normalizedPoint(CoreController* controller, const QPoint& localRef) { + QSize screen(controller->screenDimensions()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + QSize newSize((QSizeF(size()) * devicePixelRatioF()).toSize()); +#else + QSize newSize((QSizeF(size()) * devicePixelRatio()).toSize()); +#endif + + if (m_lockAspectRatio) { + QGBA::lockAspectRatio(screen, newSize); + } + + if (m_lockIntegerScaling) { + QGBA::lockIntegerScaling(screen, newSize); + } + + QPointF newPos(localRef); + newPos -= QPointF(width() / 2.0, height() / 2.0); + newPos = QPointF(newPos.x() * screen.width(), newPos.y() * screen.height()); + newPos = QPointF(newPos.x() / newSize.width(), newPos.y() / newSize.height()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + newPos *= devicePixelRatioF(); +#else + newPos *= devicePixelRatio(); +#endif + newPos += QPointF(screen.width() / 2.0, screen.height() / 2.0); + return newPos.toPoint(); +} diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index af3cf7f00..574a0e8e9 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -44,6 +44,8 @@ public: bool isShowOSD() const { return m_showOSD; } bool isShowFrameCounter() const { return m_showFrameCounter; } + QPoint normalizedPoint(CoreController*, const QPoint& localRef); + virtual void attach(std::shared_ptr); virtual void configure(ConfigController*); virtual void startDrawing(std::shared_ptr) = 0; diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp index e81c01b26..ccfcb10d7 100644 --- a/src/platform/qt/ShortcutController.cpp +++ b/src/platform/qt/ShortcutController.cpp @@ -7,7 +7,9 @@ #include "ConfigController.h" #include "input/GamepadButtonEvent.h" +#include "input/GamepadHatEvent.h" #include "InputProfile.h" +#include "scripting/ScriptingController.h" #include #include @@ -32,6 +34,10 @@ void ShortcutController::setActionMapper(ActionMapper* actions) { rebuildItems(); } +void ShortcutController::setScriptingController(ScriptingController* controller) { + m_scripting = controller; +} + void ShortcutController::updateKey(const QString& name, int keySequence) { auto item = m_items[name]; if (!item) { @@ -132,9 +138,14 @@ void ShortcutController::rebuildItems() { onSubitems({}, std::bind(&ShortcutController::generateItem, this, std::placeholders::_1)); } -bool ShortcutController::eventFilter(QObject*, QEvent* event) { +bool ShortcutController::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->event(obj, event); + } +#endif if (keyEvent->isAutoRepeat()) { return false; } @@ -153,6 +164,11 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { } } if (event->type() == GamepadButtonEvent::Down()) { +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->event(obj, event); + } +#endif auto item = m_buttons.find(static_cast(event)->value()); if (item == m_buttons.end()) { return false; @@ -169,6 +185,11 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { return true; } if (event->type() == GamepadButtonEvent::Up()) { +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->event(obj, event); + } +#endif auto item = m_buttons.find(static_cast(event)->value()); if (item == m_buttons.end()) { return false; @@ -201,6 +222,13 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { event->accept(); return true; } +#ifdef ENABLE_SCRIPTING + if (event->type() == GamepadHatEvent::Type()) { + if (m_scripting) { + m_scripting->event(obj, event); + } + } +#endif return false; } diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index c4db4241e..7eed7e1d7 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -21,6 +21,7 @@ namespace QGBA { class ConfigController; class InputProfile; +class ScriptingController; class Shortcut : public QObject { Q_OBJECT @@ -74,6 +75,7 @@ public: void setConfigController(ConfigController* controller); void setActionMapper(ActionMapper* actionMapper); + void setScriptingController(ScriptingController* scriptingController); void setProfile(const QString& profile); @@ -121,6 +123,7 @@ private: QHash> m_heldKeys; ActionMapper* m_actions = nullptr; ConfigController* m_config = nullptr; + ScriptingController* m_scripting = nullptr; QString m_profileName; const InputProfile* m_profile = nullptr; }; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 7e25159eb..ae80b8454 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -627,8 +627,10 @@ void Window::consoleOpen() { void Window::scriptingOpen() { if (!m_scripting) { m_scripting = std::make_unique(); + m_shortcutController->setScriptingController(m_scripting.get()); if (m_controller) { m_scripting->setController(m_controller); + m_display->installEventFilter(m_scripting.get()); } } ScriptingView* view = new ScriptingView(m_scripting.get(), m_config); @@ -2125,6 +2127,12 @@ void Window::attachDisplay() { m_display->attach(m_controller); connect(m_display.get(), &QGBA::Display::drawingStarted, this, &Window::changeRenderer); m_display->startDrawing(m_controller); + +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_display->installEventFilter(m_scripting.get()); + } +#endif } void Window::updateMute() { diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 5e92a81aa..b6932d602 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -5,11 +5,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "scripting/ScriptingController.h" -#include "AudioProcessor.h" +#include +#include +#include +#include + #include "CoreController.h" +#include "Display.h" +#include "input/GamepadButtonEvent.h" +#include "input/GamepadHatEvent.h" #include "scripting/ScriptingTextBuffer.h" #include "scripting/ScriptingTextBufferModel.h" +#include +#include + using namespace QGBA; ScriptingController::ScriptingController(QObject* parent) @@ -120,10 +130,93 @@ void ScriptingController::runCode(const QString& code) { load(vf, "*prompt"); } +bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { + event(obj, ev); + return false; +} + +void ScriptingController::event(QObject* obj, QEvent* event) { + if (!m_controller) { + return; + } + + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: { + struct mScriptKeyEvent ev{mSCRIPT_EV_TYPE_KEY}; + auto keyEvent = static_cast(event); + ev.state = event->type() == QEvent::KeyRelease ? mSCRIPT_INPUT_STATE_UP : + static_cast(event)->isAutoRepeat() ? mSCRIPT_INPUT_STATE_HELD : mSCRIPT_INPUT_STATE_DOWN; + ev.key = qtToScriptingKey(keyEvent); + ev.modifiers = qtToScriptingModifiers(keyEvent->modifiers()); + mScriptContextFireEvent(&m_scriptContext, &ev.d); + return; + } + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: { + struct mScriptMouseButtonEvent ev{mSCRIPT_EV_TYPE_MOUSE_BUTTON}; + auto mouseEvent = static_cast(event); + ev.mouse = 0; + ev.state = event->type() == QEvent::MouseButtonPress ? mSCRIPT_INPUT_STATE_DOWN : mSCRIPT_INPUT_STATE_UP; + ev.button = 31 - clz32(mouseEvent->button()); + mScriptContextFireEvent(&m_scriptContext, &ev.d); + return; + } + case QEvent::MouseMove: { + struct mScriptMouseMoveEvent ev{mSCRIPT_EV_TYPE_MOUSE_MOVE}; + auto mouseEvent = static_cast(event); + QPoint pos = mouseEvent->pos(); + pos = static_cast(obj)->normalizedPoint(m_controller.get(), pos); + ev.mouse = 0; + ev.x = pos.x(); + ev.y = pos.y(); + mScriptContextFireEvent(&m_scriptContext, &ev.d); + return; + } + case QEvent::Wheel: { + struct mScriptMouseWheelEvent ev{mSCRIPT_EV_TYPE_MOUSE_WHEEL}; + auto wheelEvent = static_cast(event); + QPoint adelta = wheelEvent->angleDelta(); + QPoint pdelta = wheelEvent->pixelDelta(); + ev.mouse = 0; + if (!pdelta.isNull()) { + ev.x = pdelta.x(); + ev.y = pdelta.y(); + } else { + ev.x = adelta.x(); + ev.y = adelta.y(); + } + mScriptContextFireEvent(&m_scriptContext, &ev.d); + return; + } + default: + break; + } + + auto type = event->type(); + if (type == GamepadButtonEvent::Down() || type == GamepadButtonEvent::Up()) { + struct mScriptGamepadButtonEvent ev{mSCRIPT_EV_TYPE_GAMEPAD_BUTTON}; + auto gamepadEvent = static_cast(event); + ev.pad = 0; + ev.state = event->type() == GamepadButtonEvent::Down() ? mSCRIPT_INPUT_STATE_DOWN : mSCRIPT_INPUT_STATE_UP; + ev.button = gamepadEvent->value(); + mScriptContextFireEvent(&m_scriptContext, &ev.d); + } + if (type == GamepadHatEvent::Type()) { + struct mScriptGamepadHatEvent ev{mSCRIPT_EV_TYPE_GAMEPAD_HAT}; + auto gamepadEvent = static_cast(event); + ev.pad = 0; + ev.hat = gamepadEvent->hatId(); + ev.direction = gamepadEvent->direction(); + mScriptContextFireEvent(&m_scriptContext, &ev.d); + } +} + void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); mScriptContextAttachSocket(&m_scriptContext); + mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); mScriptContextAttachLogger(&m_scriptContext, &m_logger); @@ -138,3 +231,130 @@ void ScriptingController::init() { m_activeEngine = *m_engines.begin(); } } + +uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { + QString text(event->text()); + int key = event->key(); + + static const QHash keypadMap{ + {'0', mSCRIPT_KEY_KP_0}, + {'1', mSCRIPT_KEY_KP_1}, + {'2', mSCRIPT_KEY_KP_2}, + {'3', mSCRIPT_KEY_KP_3}, + {'4', mSCRIPT_KEY_KP_4}, + {'5', mSCRIPT_KEY_KP_5}, + {'6', mSCRIPT_KEY_KP_6}, + {'7', mSCRIPT_KEY_KP_7}, + {'8', mSCRIPT_KEY_KP_8}, + {'9', mSCRIPT_KEY_KP_9}, + {'+', mSCRIPT_KEY_KP_PLUS}, + {'-', mSCRIPT_KEY_KP_MINUS}, + {'*', mSCRIPT_KEY_KP_MULTIPLY}, + {'/', mSCRIPT_KEY_KP_DIVIDE}, + {',', mSCRIPT_KEY_KP_COMMA}, + {'.', mSCRIPT_KEY_KP_POINT}, + {Qt::Key_Enter, mSCRIPT_KEY_KP_ENTER}, + }; + static const QHash extraKeyMap{ + {Qt::Key_Escape, mSCRIPT_KEY_ESCAPE}, + {Qt::Key_Tab, mSCRIPT_KEY_TAB}, + {Qt::Key_Backtab, mSCRIPT_KEY_BACKSPACE}, + {Qt::Key_Backspace, mSCRIPT_KEY_BACKSPACE}, + {Qt::Key_Return, mSCRIPT_KEY_ENTER}, + {Qt::Key_Enter, mSCRIPT_KEY_ENTER}, + {Qt::Key_Insert, mSCRIPT_KEY_INSERT}, + {Qt::Key_Delete, mSCRIPT_KEY_DELETE}, + {Qt::Key_Pause, mSCRIPT_KEY_BREAK}, + {Qt::Key_Print, mSCRIPT_KEY_PRINT_SCREEN}, + {Qt::Key_SysReq, mSCRIPT_KEY_SYSRQ}, + {Qt::Key_Clear, mSCRIPT_KEY_CLEAR}, + {Qt::Key_Home, mSCRIPT_KEY_HOME}, + {Qt::Key_End, mSCRIPT_KEY_END}, + {Qt::Key_Left, mSCRIPT_KEY_LEFT}, + {Qt::Key_Up, mSCRIPT_KEY_UP}, + {Qt::Key_Right, mSCRIPT_KEY_RIGHT}, + {Qt::Key_Down, mSCRIPT_KEY_DOWN}, + {Qt::Key_PageUp, mSCRIPT_KEY_PAGE_UP}, + {Qt::Key_PageDown, mSCRIPT_KEY_DOWN}, + {Qt::Key_Shift, mSCRIPT_KEY_SHIFT}, + {Qt::Key_Control, mSCRIPT_KEY_CONTROL}, + {Qt::Key_Meta, mSCRIPT_KEY_SUPER}, + {Qt::Key_Alt, mSCRIPT_KEY_ALT}, + {Qt::Key_CapsLock, mSCRIPT_KEY_CAPS_LOCK}, + {Qt::Key_NumLock, mSCRIPT_KEY_NUM_LOCK}, + {Qt::Key_ScrollLock, mSCRIPT_KEY_SCROLL_LOCK}, + {Qt::Key_F1, mSCRIPT_KEY_F1}, + {Qt::Key_F2, mSCRIPT_KEY_F2}, + {Qt::Key_F3, mSCRIPT_KEY_F3}, + {Qt::Key_F4, mSCRIPT_KEY_F4}, + {Qt::Key_F5, mSCRIPT_KEY_F5}, + {Qt::Key_F6, mSCRIPT_KEY_F6}, + {Qt::Key_F7, mSCRIPT_KEY_F7}, + {Qt::Key_F8, mSCRIPT_KEY_F8}, + {Qt::Key_F9, mSCRIPT_KEY_F9}, + {Qt::Key_F10, mSCRIPT_KEY_F10}, + {Qt::Key_F11, mSCRIPT_KEY_F11}, + {Qt::Key_F12, mSCRIPT_KEY_F12}, + {Qt::Key_F13, mSCRIPT_KEY_F13}, + {Qt::Key_F14, mSCRIPT_KEY_F14}, + {Qt::Key_F15, mSCRIPT_KEY_F15}, + {Qt::Key_F16, mSCRIPT_KEY_F16}, + {Qt::Key_F17, mSCRIPT_KEY_F17}, + {Qt::Key_F18, mSCRIPT_KEY_F18}, + {Qt::Key_F19, mSCRIPT_KEY_F19}, + {Qt::Key_F20, mSCRIPT_KEY_F20}, + {Qt::Key_F21, mSCRIPT_KEY_F21}, + {Qt::Key_F22, mSCRIPT_KEY_F22}, + {Qt::Key_F23, mSCRIPT_KEY_F23}, + {Qt::Key_F24, mSCRIPT_KEY_F24}, + {Qt::Key_Menu, mSCRIPT_KEY_MENU}, + {Qt::Key_Super_L, mSCRIPT_KEY_SUPER}, + {Qt::Key_Super_R, mSCRIPT_KEY_SUPER}, + {Qt::Key_Help, mSCRIPT_KEY_HELP}, + {Qt::Key_Hyper_L, mSCRIPT_KEY_SUPER}, + {Qt::Key_Hyper_R, mSCRIPT_KEY_SUPER}, + }; + + if (event->modifiers() & Qt::KeypadModifier && keypadMap.contains(key)) { + return keypadMap[key]; + } + if (key >= 0 && key < 0x100) { + return key; + } + if (key < 0x01000000) { + if (text.isEmpty()) { + return 0; + } + QChar high = text[0]; + if (!high.isSurrogate()) { + return high.unicode(); + } + if (text.size() < 2) { + return 0; + } + return QChar::surrogateToUcs4(high, text[1]); + } + + if (extraKeyMap.contains(key)) { + return extraKeyMap[key]; + } + return 0; +} + + +uint16_t ScriptingController::qtToScriptingModifiers(Qt::KeyboardModifiers modifiers) { + uint16_t mod = 0; + if (modifiers & Qt::ShiftModifier) { + mod |= mSCRIPT_KMOD_SHIFT; + } + if (modifiers & Qt::ControlModifier) { + mod |= mSCRIPT_KMOD_CONTROL; + } + if (modifiers & Qt::AltModifier) { + mod |= mSCRIPT_KMOD_ALT; + } + if (modifiers & Qt::MetaModifier) { + mod |= mSCRIPT_KMOD_SUPER; + } + return mod; +} diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index 03e40f7cf..d0fb73fbc 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -15,6 +15,7 @@ #include +class QKeyEvent; class QTextDocument; namespace QGBA { @@ -35,6 +36,8 @@ public: bool loadFile(const QString& path); bool load(VFileDevice& vf, const QString& name); + void event(QObject* obj, QEvent* ev); + mScriptContext* context() { return &m_scriptContext; } ScriptingTextBufferModel* textBufferModel() const { return m_bufferModel; } @@ -49,10 +52,14 @@ public slots: void reset(); void runCode(const QString& code); +protected: + bool eventFilter(QObject*, QEvent*) override; + private: void init(); - static mScriptTextBuffer* createTextBuffer(void* context); + static uint32_t qtToScriptingKey(const QKeyEvent*); + static uint16_t qtToScriptingModifiers(Qt::KeyboardModifiers); struct Logger : mLogger { ScriptingController* p;