File indexing completed on 2024-04-21 16:29:27

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0003 
0004 #include <optional>
0005 
0006 #include <QDebug>
0007 #include <QFile>
0008 #include <QGuiApplication>
0009 #include <QJsonArray>
0010 #include <QJsonDocument>
0011 #include <QJsonObject>
0012 
0013 #include "interaction.h"
0014 
0015 namespace
0016 {
0017 std::optional<wl_keyboard_key_state> typeToKeyState(QStringView type)
0018 {
0019     if (type == QLatin1String("keyDown")) {
0020         return WL_KEYBOARD_KEY_STATE_PRESSED;
0021     }
0022     if (type == QLatin1String("keyUp")) {
0023         return WL_KEYBOARD_KEY_STATE_RELEASED;
0024     }
0025     qWarning() << "unsupported keyboard action type" << type;
0026     return {};
0027 }
0028 } // namespace
0029 
0030 int main(int argc, char **argv)
0031 {
0032     QGuiApplication app(argc, argv);
0033 
0034     const auto actionFilePath = qGuiApp->arguments().at(1);
0035     QFile actionFile(actionFilePath);
0036     if (!actionFile.open(QFile::ReadOnly)) {
0037         qWarning() << "failed to open action file" << actionFilePath;
0038         return 1;
0039     }
0040 
0041     std::vector<BaseAction *> actions;
0042 
0043     s_interface = new FakeInputInterface;
0044 
0045     const auto document = QJsonDocument::fromJson(actionFile.readAll());
0046     const auto jsonObject = document.object();
0047     const auto jsonActions = jsonObject.value(QStringLiteral("actions")).toArray();
0048     for (const auto &jsonActionSet : jsonActions) {
0049         if (auto inputType = jsonActionSet[QLatin1String("type")]; inputType == QLatin1String("key")) {
0050             for (const auto &jsonAction : jsonActionSet[QStringLiteral("actions")].toArray()) {
0051                 const auto hash = jsonAction.toObject().toVariantHash();
0052                 const auto type = hash.value(QStringLiteral("type")).toString();
0053                 BaseAction *action = nullptr;
0054 
0055                 if (type == QLatin1String("pause")) {
0056                     const ulong duration = hash.value(QStringLiteral("duration")).value<ulong>();
0057                     action = new PauseAction(duration);
0058                 } else {
0059                     const auto string = hash.value(QStringLiteral("value")).toString();
0060                     const QChar *character = string.unicode();
0061                     action = new KeyboardAction(*character, typeToKeyState(type).value_or(WL_KEYBOARD_KEY_STATE_RELEASED));
0062                 }
0063 
0064                 actions.emplace_back(action);
0065             }
0066         } else if (inputType == QLatin1String("pointer")) {
0067             /*
0068               https://github.com/SeleniumHQ/selenium/blob/6620bce4e8e9da1fee3ec5a5547afa7dece3f80e/py/selenium/webdriver/common/actions/pointer_input.py#L66
0069                 def encode(self):
0070                     return {"type": self.type, "parameters": {"pointerType": self.kind}, "id": self.name, "actions": self.actions}
0071              */
0072             const QString id = jsonActionSet[QLatin1String("id")].toString(QStringLiteral("Default"));
0073 
0074             PointerAction::PointerKind pointerTypeInt = PointerAction::PointerKind::Mouse;
0075             if (const QString pointerType = jsonActionSet[QLatin1String("parameters")].toObject().toVariantHash()[QStringLiteral("pointerType")].toString();
0076                 pointerType == QLatin1String("touch")) {
0077                 pointerTypeInt = PointerAction::PointerKind::Touch;
0078             } else if (pointerType == QLatin1String("pen")) {
0079                 pointerTypeInt = PointerAction::PointerKind::Pen;
0080             }
0081 
0082             const auto pointerActions = jsonActionSet[QLatin1String("actions")].toArray();
0083             for (const auto &pointerAction : pointerActions) {
0084                 const auto hash = pointerAction.toObject().toVariantHash();
0085                 const auto duration = hash.value(QStringLiteral("duration")).value<ulong>();
0086 
0087                 PointerAction::ActionType actionTypeInt = PointerAction::ActionType::Cancel;
0088                 if (const QString actionType = hash.value(QStringLiteral("type")).toString(); actionType == QLatin1String("pointerDown")) {
0089                     actionTypeInt = PointerAction::ActionType::Down;
0090                 } else if (actionType == QLatin1String("pointerUp")) {
0091                     actionTypeInt = PointerAction::ActionType::Up;
0092                 } else if (actionType == QLatin1String("pointerMove")) {
0093                     actionTypeInt = PointerAction::ActionType::Move;
0094                 } else if (actionType == QLatin1String("pause")) {
0095                     actions.emplace_back(new PauseAction(duration));
0096                     continue;
0097                 }
0098 
0099                 PointerAction::Button button = PointerAction::Button::Left;
0100                 if (pointerTypeInt == PointerAction::PointerKind::Mouse) {
0101                     button = static_cast<PointerAction::Button>(hash.value(QStringLiteral("button")).toInt());
0102                 }
0103 
0104                 auto action = new PointerAction(pointerTypeInt, id, actionTypeInt, button, duration);
0105 
0106                 if (actionTypeInt == PointerAction::ActionType::Move) {
0107                     // Positions relative to elements are ignored since at-spi2 can't report correct element positions.
0108                     PointerAction::Origin originInt = PointerAction::Origin::Viewport;
0109                     if (hash.value(QStringLiteral("origin")).toString() == QLatin1String("pointer")) {
0110                         originInt = PointerAction::Origin::Pointer;
0111                     }
0112 
0113                     const int x = hash.value(QStringLiteral("x")).toInt();
0114                     const int y = hash.value(QStringLiteral("y")).toInt();
0115                     action->setPosition({x, y}, originInt);
0116                 }
0117 
0118                 actions.emplace_back(action);
0119             }
0120             continue;
0121         } else if (inputType == QLatin1String("wheel")) {
0122             const QString id = jsonActionSet[QLatin1String("id")].toString(QStringLiteral("Default"));
0123             const auto wheelActions = jsonActionSet[QLatin1String("actions")].toArray();
0124             for (const auto &pointerAction : wheelActions) {
0125                 const auto hash = pointerAction.toObject().toVariantHash();
0126                 const auto duration = hash.value(QStringLiteral("duration")).value<ulong>();
0127 
0128                 if (const QString actionType = hash.value(QStringLiteral("type")).toString(); actionType == QLatin1String("pause")) {
0129                     actions.emplace_back(new PauseAction(duration));
0130                     continue;
0131                 } else {
0132                     const auto x = hash.value(QStringLiteral("x")).toInt();
0133                     const auto y = hash.value(QStringLiteral("y")).toInt();
0134                     const auto deltaX = hash.value(QStringLiteral("deltaX")).toInt();
0135                     const auto deltaY = hash.value(QStringLiteral("deltaY")).toInt();
0136                     actions.emplace_back(new WheelAction(id, QPoint(x, y), QPoint(deltaX, deltaY), duration));
0137                 }
0138             }
0139         } else {
0140             qWarning() << "unsupported action type" << jsonActionSet;
0141             continue;
0142         }
0143     }
0144 
0145     auto performActions = [actions = std::move(actions)] {
0146         for (auto action : actions) {
0147             action->perform();
0148         }
0149         qDeleteAll(actions);
0150         QCoreApplication::quit();
0151     };
0152 
0153     app.connect(s_interface, &FakeInputInterface::readyChanged, &app, performActions);
0154 
0155     return app.exec();
0156 }