#include "McRFPy_Automation.h" #include "McRFPy_API.h" #include "GameEngine.h" #include #include #include #include // Helper function to get game engine GameEngine* McRFPy_Automation::getGameEngine() { return McRFPy_API::game; } // Sleep helper void McRFPy_Automation::sleep_ms(int milliseconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } // Convert string to SFML key code sf::Keyboard::Key McRFPy_Automation::stringToKey(const std::string& keyName) { static const std::unordered_map keyMap = { // Letters {"a", sf::Keyboard::A}, {"b", sf::Keyboard::B}, {"c", sf::Keyboard::C}, {"d", sf::Keyboard::D}, {"e", sf::Keyboard::E}, {"f", sf::Keyboard::F}, {"g", sf::Keyboard::G}, {"h", sf::Keyboard::H}, {"i", sf::Keyboard::I}, {"j", sf::Keyboard::J}, {"k", sf::Keyboard::K}, {"l", sf::Keyboard::L}, {"m", sf::Keyboard::M}, {"n", sf::Keyboard::N}, {"o", sf::Keyboard::O}, {"p", sf::Keyboard::P}, {"q", sf::Keyboard::Q}, {"r", sf::Keyboard::R}, {"s", sf::Keyboard::S}, {"t", sf::Keyboard::T}, {"u", sf::Keyboard::U}, {"v", sf::Keyboard::V}, {"w", sf::Keyboard::W}, {"x", sf::Keyboard::X}, {"y", sf::Keyboard::Y}, {"z", sf::Keyboard::Z}, // Numbers {"0", sf::Keyboard::Num0}, {"1", sf::Keyboard::Num1}, {"2", sf::Keyboard::Num2}, {"3", sf::Keyboard::Num3}, {"4", sf::Keyboard::Num4}, {"5", sf::Keyboard::Num5}, {"6", sf::Keyboard::Num6}, {"7", sf::Keyboard::Num7}, {"8", sf::Keyboard::Num8}, {"9", sf::Keyboard::Num9}, // Function keys {"f1", sf::Keyboard::F1}, {"f2", sf::Keyboard::F2}, {"f3", sf::Keyboard::F3}, {"f4", sf::Keyboard::F4}, {"f5", sf::Keyboard::F5}, {"f6", sf::Keyboard::F6}, {"f7", sf::Keyboard::F7}, {"f8", sf::Keyboard::F8}, {"f9", sf::Keyboard::F9}, {"f10", sf::Keyboard::F10}, {"f11", sf::Keyboard::F11}, {"f12", sf::Keyboard::F12}, {"f13", sf::Keyboard::F13}, {"f14", sf::Keyboard::F14}, {"f15", sf::Keyboard::F15}, // Special keys {"escape", sf::Keyboard::Escape}, {"esc", sf::Keyboard::Escape}, {"enter", sf::Keyboard::Enter}, {"return", sf::Keyboard::Enter}, {"space", sf::Keyboard::Space}, {" ", sf::Keyboard::Space}, {"tab", sf::Keyboard::Tab}, {"\t", sf::Keyboard::Tab}, {"backspace", sf::Keyboard::BackSpace}, {"delete", sf::Keyboard::Delete}, {"del", sf::Keyboard::Delete}, {"insert", sf::Keyboard::Insert}, {"home", sf::Keyboard::Home}, {"end", sf::Keyboard::End}, {"pageup", sf::Keyboard::PageUp}, {"pgup", sf::Keyboard::PageUp}, {"pagedown", sf::Keyboard::PageDown}, {"pgdn", sf::Keyboard::PageDown}, // Arrow keys {"left", sf::Keyboard::Left}, {"right", sf::Keyboard::Right}, {"up", sf::Keyboard::Up}, {"down", sf::Keyboard::Down}, // Modifiers {"ctrl", sf::Keyboard::LControl}, {"ctrlleft", sf::Keyboard::LControl}, {"ctrlright", sf::Keyboard::RControl}, {"alt", sf::Keyboard::LAlt}, {"altleft", sf::Keyboard::LAlt}, {"altright", sf::Keyboard::RAlt}, {"shift", sf::Keyboard::LShift}, {"shiftleft", sf::Keyboard::LShift}, {"shiftright", sf::Keyboard::RShift}, {"win", sf::Keyboard::LSystem}, {"winleft", sf::Keyboard::LSystem}, {"winright", sf::Keyboard::RSystem}, {"command", sf::Keyboard::LSystem}, // Punctuation {",", sf::Keyboard::Comma}, {".", sf::Keyboard::Period}, {"/", sf::Keyboard::Slash}, {"\\", sf::Keyboard::BackSlash}, {";", sf::Keyboard::SemiColon}, {"'", sf::Keyboard::Quote}, {"[", sf::Keyboard::LBracket}, {"]", sf::Keyboard::RBracket}, {"-", sf::Keyboard::Dash}, {"=", sf::Keyboard::Equal}, // Numpad {"num0", sf::Keyboard::Numpad0}, {"num1", sf::Keyboard::Numpad1}, {"num2", sf::Keyboard::Numpad2}, {"num3", sf::Keyboard::Numpad3}, {"num4", sf::Keyboard::Numpad4}, {"num5", sf::Keyboard::Numpad5}, {"num6", sf::Keyboard::Numpad6}, {"num7", sf::Keyboard::Numpad7}, {"num8", sf::Keyboard::Numpad8}, {"num9", sf::Keyboard::Numpad9}, {"add", sf::Keyboard::Add}, {"subtract", sf::Keyboard::Subtract}, {"multiply", sf::Keyboard::Multiply}, {"divide", sf::Keyboard::Divide}, // Other {"pause", sf::Keyboard::Pause}, {"capslock", sf::Keyboard::LControl}, // Note: SFML doesn't have CapsLock {"numlock", sf::Keyboard::LControl}, // Note: SFML doesn't have NumLock {"scrolllock", sf::Keyboard::LControl}, // Note: SFML doesn't have ScrollLock }; auto it = keyMap.find(keyName); if (it != keyMap.end()) { return it->second; } return sf::Keyboard::Unknown; } // Inject mouse event into the game engine void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button) { auto engine = getGameEngine(); if (!engine) return; sf::Event event; event.type = type; switch (type) { case sf::Event::MouseMoved: event.mouseMove.x = x; event.mouseMove.y = y; break; case sf::Event::MouseButtonPressed: case sf::Event::MouseButtonReleased: event.mouseButton.button = button; event.mouseButton.x = x; event.mouseButton.y = y; break; case sf::Event::MouseWheelScrolled: event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel; event.mouseWheelScroll.delta = static_cast(x); // x is used for scroll amount event.mouseWheelScroll.x = x; event.mouseWheelScroll.y = y; break; default: break; } engine->processEvent(event); } // Inject keyboard event into the game engine void McRFPy_Automation::injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key) { auto engine = getGameEngine(); if (!engine) return; sf::Event event; event.type = type; if (type == sf::Event::KeyPressed || type == sf::Event::KeyReleased) { event.key.code = key; event.key.alt = sf::Keyboard::isKeyPressed(sf::Keyboard::LAlt) || sf::Keyboard::isKeyPressed(sf::Keyboard::RAlt); event.key.control = sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl); event.key.shift = sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::RShift); event.key.system = sf::Keyboard::isKeyPressed(sf::Keyboard::LSystem) || sf::Keyboard::isKeyPressed(sf::Keyboard::RSystem); } engine->processEvent(event); } // Inject text event for typing void McRFPy_Automation::injectTextEvent(sf::Uint32 unicode) { auto engine = getGameEngine(); if (!engine) return; sf::Event event; event.type = sf::Event::TextEntered; event.text.unicode = unicode; engine->processEvent(event); } // Screenshot implementation PyObject* McRFPy_Automation::_screenshot(PyObject* self, PyObject* args) { const char* filename; if (!PyArg_ParseTuple(args, "s", &filename)) { return NULL; } auto engine = getGameEngine(); if (!engine) { PyErr_SetString(PyExc_RuntimeError, "Game engine not initialized"); return NULL; } // Get the render target sf::RenderTarget* target = engine->getRenderTargetPtr(); if (!target) { PyErr_SetString(PyExc_RuntimeError, "No render target available"); return NULL; } // For RenderWindow, we can get a screenshot directly if (auto* window = dynamic_cast(target)) { sf::Vector2u windowSize = window->getSize(); sf::Texture texture; texture.create(windowSize.x, windowSize.y); texture.update(*window); if (texture.copyToImage().saveToFile(filename)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } // For RenderTexture (headless mode) else if (auto* renderTexture = dynamic_cast(target)) { if (renderTexture->getTexture().copyToImage().saveToFile(filename)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } PyErr_SetString(PyExc_RuntimeError, "Unknown render target type"); return NULL; } // Get current mouse position PyObject* McRFPy_Automation::_position(PyObject* self, PyObject* args) { auto engine = getGameEngine(); if (!engine || !engine->getRenderTargetPtr()) { return Py_BuildValue("(ii)", 0, 0); } // In headless mode, we'd need to track the simulated mouse position // For now, return the actual mouse position relative to window if available if (auto* window = dynamic_cast(engine->getRenderTargetPtr())) { sf::Vector2i pos = sf::Mouse::getPosition(*window); return Py_BuildValue("(ii)", pos.x, pos.y); } // In headless mode, return simulated position (TODO: track this) return Py_BuildValue("(ii)", 0, 0); } // Get screen size PyObject* McRFPy_Automation::_size(PyObject* self, PyObject* args) { auto engine = getGameEngine(); if (!engine || !engine->getRenderTargetPtr()) { return Py_BuildValue("(ii)", 1024, 768); // Default size } sf::Vector2u size = engine->getRenderTarget().getSize(); return Py_BuildValue("(ii)", size.x, size.y); } // Check if coordinates are on screen PyObject* McRFPy_Automation::_onScreen(PyObject* self, PyObject* args) { int x, y; if (!PyArg_ParseTuple(args, "ii", &x, &y)) { return NULL; } auto engine = getGameEngine(); if (!engine || !engine->getRenderTargetPtr()) { Py_RETURN_FALSE; } sf::Vector2u size = engine->getRenderTarget().getSize(); if (x >= 0 && x < (int)size.x && y >= 0 && y < (int)size.y) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } // Move mouse to position PyObject* McRFPy_Automation::_moveTo(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", "duration", NULL}; int x, y; float duration = 0.0f; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast(kwlist), &x, &y, &duration)) { return NULL; } // TODO: Implement smooth movement with duration injectMouseEvent(sf::Event::MouseMoved, x, y); if (duration > 0) { sleep_ms(static_cast(duration * 1000)); } Py_RETURN_NONE; } // Move mouse relative PyObject* McRFPy_Automation::_moveRel(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"xOffset", "yOffset", "duration", NULL}; int xOffset, yOffset; float duration = 0.0f; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast(kwlist), &xOffset, &yOffset, &duration)) { return NULL; } // Get current position PyObject* pos = _position(self, NULL); if (!pos) return NULL; int currentX, currentY; if (!PyArg_ParseTuple(pos, "ii", ¤tX, ¤tY)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); // Move to new position injectMouseEvent(sf::Event::MouseMoved, currentX + xOffset, currentY + yOffset); if (duration > 0) { sleep_ms(static_cast(duration * 1000)); } Py_RETURN_NONE; } // Click implementation PyObject* McRFPy_Automation::_click(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", "clicks", "interval", "button", NULL}; int x = -1, y = -1; int clicks = 1; float interval = 0.0f; const char* button = "left"; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iiifs", const_cast(kwlist), &x, &y, &clicks, &interval, &button)) { return NULL; } // If no position specified, use current position if (x == -1 || y == -1) { PyObject* pos = _position(self, NULL); if (!pos) return NULL; if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); } // Determine button sf::Mouse::Button sfButton = sf::Mouse::Left; if (strcmp(button, "right") == 0) { sfButton = sf::Mouse::Right; } else if (strcmp(button, "middle") == 0) { sfButton = sf::Mouse::Middle; } // Move to position first injectMouseEvent(sf::Event::MouseMoved, x, y); // Perform clicks for (int i = 0; i < clicks; i++) { if (i > 0 && interval > 0) { sleep_ms(static_cast(interval * 1000)); } injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton); sleep_ms(10); // Small delay between press and release injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton); } Py_RETURN_NONE; } // Right click PyObject* McRFPy_Automation::_rightClick(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", NULL}; int x = -1, y = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { return NULL; } // Build new args with button="right" PyObject* newKwargs = PyDict_New(); PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("right")); if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); PyObject* result = _click(self, PyTuple_New(0), newKwargs); Py_DECREF(newKwargs); return result; } // Double click PyObject* McRFPy_Automation::_doubleClick(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", NULL}; int x = -1, y = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { return NULL; } PyObject* newKwargs = PyDict_New(); PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(2)); PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1)); if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); PyObject* result = _click(self, PyTuple_New(0), newKwargs); Py_DECREF(newKwargs); return result; } // Type text PyObject* McRFPy_Automation::_typewrite(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"message", "interval", NULL}; const char* message; float interval = 0.0f; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|f", const_cast(kwlist), &message, &interval)) { return NULL; } // Type each character for (size_t i = 0; message[i] != '\0'; i++) { if (i > 0 && interval > 0) { sleep_ms(static_cast(interval * 1000)); } char c = message[i]; // Handle special characters if (c == '\n') { injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Enter); injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Enter); } else if (c == '\t') { injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Tab); injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Tab); } else { // For regular characters, send text event injectTextEvent(static_cast(c)); } } Py_RETURN_NONE; } // Press and hold key PyObject* McRFPy_Automation::_keyDown(PyObject* self, PyObject* args) { const char* keyName; if (!PyArg_ParseTuple(args, "s", &keyName)) { return NULL; } sf::Keyboard::Key key = stringToKey(keyName); if (key == sf::Keyboard::Unknown) { PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); return NULL; } injectKeyEvent(sf::Event::KeyPressed, key); Py_RETURN_NONE; } // Release key PyObject* McRFPy_Automation::_keyUp(PyObject* self, PyObject* args) { const char* keyName; if (!PyArg_ParseTuple(args, "s", &keyName)) { return NULL; } sf::Keyboard::Key key = stringToKey(keyName); if (key == sf::Keyboard::Unknown) { PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); return NULL; } injectKeyEvent(sf::Event::KeyReleased, key); Py_RETURN_NONE; } // Hotkey combination PyObject* McRFPy_Automation::_hotkey(PyObject* self, PyObject* args) { // Get all keys as separate arguments Py_ssize_t numKeys = PyTuple_Size(args); if (numKeys == 0) { PyErr_SetString(PyExc_ValueError, "hotkey() requires at least one key"); return NULL; } // Press all keys for (Py_ssize_t i = 0; i < numKeys; i++) { PyObject* keyObj = PyTuple_GetItem(args, i); const char* keyName = PyUnicode_AsUTF8(keyObj); if (!keyName) { return NULL; } sf::Keyboard::Key key = stringToKey(keyName); if (key == sf::Keyboard::Unknown) { PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); return NULL; } injectKeyEvent(sf::Event::KeyPressed, key); sleep_ms(10); // Small delay between key presses } // Release all keys in reverse order for (Py_ssize_t i = numKeys - 1; i >= 0; i--) { PyObject* keyObj = PyTuple_GetItem(args, i); const char* keyName = PyUnicode_AsUTF8(keyObj); sf::Keyboard::Key key = stringToKey(keyName); injectKeyEvent(sf::Event::KeyReleased, key); sleep_ms(10); } Py_RETURN_NONE; } // Scroll wheel PyObject* McRFPy_Automation::_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"clicks", "x", "y", NULL}; int clicks; int x = -1, y = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|ii", const_cast(kwlist), &clicks, &x, &y)) { return NULL; } // If no position specified, use current position if (x == -1 || y == -1) { PyObject* pos = _position(self, NULL); if (!pos) return NULL; if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); } // Inject scroll event injectMouseEvent(sf::Event::MouseWheelScrolled, clicks, y); Py_RETURN_NONE; } // Other click types using the main click function PyObject* McRFPy_Automation::_middleClick(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", NULL}; int x = -1, y = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { return NULL; } PyObject* newKwargs = PyDict_New(); PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("middle")); if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); PyObject* result = _click(self, PyTuple_New(0), newKwargs); Py_DECREF(newKwargs); return result; } PyObject* McRFPy_Automation::_tripleClick(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", NULL}; int x = -1, y = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { return NULL; } PyObject* newKwargs = PyDict_New(); PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(3)); PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1)); if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); PyObject* result = _click(self, PyTuple_New(0), newKwargs); Py_DECREF(newKwargs); return result; } // Mouse button press/release PyObject* McRFPy_Automation::_mouseDown(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", "button", NULL}; int x = -1, y = -1; const char* button = "left"; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast(kwlist), &x, &y, &button)) { return NULL; } // If no position specified, use current position if (x == -1 || y == -1) { PyObject* pos = _position(self, NULL); if (!pos) return NULL; if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); } sf::Mouse::Button sfButton = sf::Mouse::Left; if (strcmp(button, "right") == 0) { sfButton = sf::Mouse::Right; } else if (strcmp(button, "middle") == 0) { sfButton = sf::Mouse::Middle; } injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton); Py_RETURN_NONE; } PyObject* McRFPy_Automation::_mouseUp(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", "button", NULL}; int x = -1, y = -1; const char* button = "left"; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast(kwlist), &x, &y, &button)) { return NULL; } // If no position specified, use current position if (x == -1 || y == -1) { PyObject* pos = _position(self, NULL); if (!pos) return NULL; if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); } sf::Mouse::Button sfButton = sf::Mouse::Left; if (strcmp(button, "right") == 0) { sfButton = sf::Mouse::Right; } else if (strcmp(button, "middle") == 0) { sfButton = sf::Mouse::Middle; } injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton); Py_RETURN_NONE; } // Drag operations PyObject* McRFPy_Automation::_dragTo(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"x", "y", "duration", "button", NULL}; int x, y; float duration = 0.0f; const char* button = "left"; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast(kwlist), &x, &y, &duration, &button)) { return NULL; } // Get current position PyObject* pos = _position(self, NULL); if (!pos) return NULL; int startX, startY; if (!PyArg_ParseTuple(pos, "ii", &startX, &startY)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); // Mouse down at current position PyObject* downArgs = Py_BuildValue("(ii)", startX, startY); PyObject* downKwargs = PyDict_New(); PyDict_SetItemString(downKwargs, "button", PyUnicode_FromString(button)); PyObject* downResult = _mouseDown(self, downArgs, downKwargs); Py_DECREF(downArgs); Py_DECREF(downKwargs); if (!downResult) return NULL; Py_DECREF(downResult); // Move to target position if (duration > 0) { // Smooth movement int steps = static_cast(duration * 60); // 60 FPS for (int i = 1; i <= steps; i++) { int currentX = startX + (x - startX) * i / steps; int currentY = startY + (y - startY) * i / steps; injectMouseEvent(sf::Event::MouseMoved, currentX, currentY); sleep_ms(1000 / 60); // 60 FPS } } else { injectMouseEvent(sf::Event::MouseMoved, x, y); } // Mouse up at target position PyObject* upArgs = Py_BuildValue("(ii)", x, y); PyObject* upKwargs = PyDict_New(); PyDict_SetItemString(upKwargs, "button", PyUnicode_FromString(button)); PyObject* upResult = _mouseUp(self, upArgs, upKwargs); Py_DECREF(upArgs); Py_DECREF(upKwargs); if (!upResult) return NULL; Py_DECREF(upResult); Py_RETURN_NONE; } PyObject* McRFPy_Automation::_dragRel(PyObject* self, PyObject* args, PyObject* kwargs) { static const char* kwlist[] = {"xOffset", "yOffset", "duration", "button", NULL}; int xOffset, yOffset; float duration = 0.0f; const char* button = "left"; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast(kwlist), &xOffset, &yOffset, &duration, &button)) { return NULL; } // Get current position PyObject* pos = _position(self, NULL); if (!pos) return NULL; int currentX, currentY; if (!PyArg_ParseTuple(pos, "ii", ¤tX, ¤tY)) { Py_DECREF(pos); return NULL; } Py_DECREF(pos); // Call dragTo with absolute position PyObject* dragArgs = Py_BuildValue("(ii)", currentX + xOffset, currentY + yOffset); PyObject* dragKwargs = PyDict_New(); PyDict_SetItemString(dragKwargs, "duration", PyFloat_FromDouble(duration)); PyDict_SetItemString(dragKwargs, "button", PyUnicode_FromString(button)); PyObject* result = _dragTo(self, dragArgs, dragKwargs); Py_DECREF(dragArgs); Py_DECREF(dragKwargs); return result; } // Method definitions for the automation module static PyMethodDef automationMethods[] = { {"screenshot", McRFPy_Automation::_screenshot, METH_VARARGS, "screenshot(filename) - Save a screenshot to the specified file"}, {"position", McRFPy_Automation::_position, METH_NOARGS, "position() - Get current mouse position as (x, y) tuple"}, {"size", McRFPy_Automation::_size, METH_NOARGS, "size() - Get screen size as (width, height) tuple"}, {"onScreen", McRFPy_Automation::_onScreen, METH_VARARGS, "onScreen(x, y) - Check if coordinates are within screen bounds"}, {"moveTo", (PyCFunction)McRFPy_Automation::_moveTo, METH_VARARGS | METH_KEYWORDS, "moveTo(x, y, duration=0.0) - Move mouse to absolute position"}, {"moveRel", (PyCFunction)McRFPy_Automation::_moveRel, METH_VARARGS | METH_KEYWORDS, "moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position"}, {"dragTo", (PyCFunction)McRFPy_Automation::_dragTo, METH_VARARGS | METH_KEYWORDS, "dragTo(x, y, duration=0.0, button='left') - Drag mouse to position"}, {"dragRel", (PyCFunction)McRFPy_Automation::_dragRel, METH_VARARGS | METH_KEYWORDS, "dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position"}, {"click", (PyCFunction)McRFPy_Automation::_click, METH_VARARGS | METH_KEYWORDS, "click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position"}, {"rightClick", (PyCFunction)McRFPy_Automation::_rightClick, METH_VARARGS | METH_KEYWORDS, "rightClick(x=None, y=None) - Right click at position"}, {"middleClick", (PyCFunction)McRFPy_Automation::_middleClick, METH_VARARGS | METH_KEYWORDS, "middleClick(x=None, y=None) - Middle click at position"}, {"doubleClick", (PyCFunction)McRFPy_Automation::_doubleClick, METH_VARARGS | METH_KEYWORDS, "doubleClick(x=None, y=None) - Double click at position"}, {"tripleClick", (PyCFunction)McRFPy_Automation::_tripleClick, METH_VARARGS | METH_KEYWORDS, "tripleClick(x=None, y=None) - Triple click at position"}, {"scroll", (PyCFunction)McRFPy_Automation::_scroll, METH_VARARGS | METH_KEYWORDS, "scroll(clicks, x=None, y=None) - Scroll wheel at position"}, {"mouseDown", (PyCFunction)McRFPy_Automation::_mouseDown, METH_VARARGS | METH_KEYWORDS, "mouseDown(x=None, y=None, button='left') - Press mouse button"}, {"mouseUp", (PyCFunction)McRFPy_Automation::_mouseUp, METH_VARARGS | METH_KEYWORDS, "mouseUp(x=None, y=None, button='left') - Release mouse button"}, {"typewrite", (PyCFunction)McRFPy_Automation::_typewrite, METH_VARARGS | METH_KEYWORDS, "typewrite(message, interval=0.0) - Type text with optional interval between keystrokes"}, {"hotkey", McRFPy_Automation::_hotkey, METH_VARARGS, "hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))"}, {"keyDown", McRFPy_Automation::_keyDown, METH_VARARGS, "keyDown(key) - Press and hold a key"}, {"keyUp", McRFPy_Automation::_keyUp, METH_VARARGS, "keyUp(key) - Release a key"}, {NULL, NULL, 0, NULL} }; // Module definition for mcrfpy.automation static PyModuleDef automationModule = { PyModuleDef_HEAD_INIT, "mcrfpy.automation", "Automation API for McRogueFace - PyAutoGUI-compatible interface", -1, automationMethods }; // Initialize automation submodule PyObject* McRFPy_Automation::init_automation_module() { PyObject* module = PyModule_Create(&automationModule); if (module == NULL) { return NULL; } return module; }