diff --git a/.clang-format b/.clang-format
index 8e5a51c..1a17df3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -11,6 +11,7 @@ AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..e5adf27
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: faaxm
+custom: "https://www.paypal.me/faaxm"
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a301c7d..54df889 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,9 +11,10 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest, ubuntu-20.04, ubuntu-18.04, windows-latest, macos-latest, macos-10.15]
+ os: [ubuntu-latest, ubuntu-20.04, windows-latest, macos-latest, macos-10.15]
build_type: ['Release', 'Debug']
shared_libs: ['ON', 'OFF']
+ qt_version: [[5, 12, 12], [5, 15, 2], [6, 2, 3]]
include:
- os: ubuntu-latest
triplet: 'x64-linux'
@@ -24,9 +25,6 @@ jobs:
- os: ubuntu-20.04
triplet: 'x64-linux'
cmake_flags: '-DCMAKE_CXX_COMPILER=g++'
- - os: ubuntu-18.04
- triplet: 'x64-linux'
- cmake_flags: '-DCMAKE_CXX_COMPILER=g++'
- os: windows-latest
triplet: 'x64-windows'
cmake_flags: '"-DAnyRPC_ROOT=C:/Program Files (x86)/AnyRPC" "-DGTest_ROOT=C:/Program Files (x86)/googletest-distribution"'
@@ -44,8 +42,19 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v2
+ - name: Cache Qt
+ id: cache-qt
+ uses: actions/cache@v1 # not v2!
+ with:
+ path: ../Qt
+ key: ${{ runner.os }}-${{ join(matrix.qt_version, '.') }}-QtCache
+
- name: Install Qt
uses: jurplel/install-qt-action@v2
+ with:
+ aqtversion: '>=1.2.1'
+ version: ${{ join(matrix.qt_version, '.') }}
+ cached: ${{ steps.cache-qt.outputs.cache-hit }}
- name: Install dependencies
env:
@@ -56,7 +65,7 @@ jobs:
- name: "Configure"
run: |
mkdir build
- cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DSPIX_BUILD_TESTS=ON -DSPIX_BUILD_EXAMPLES=ON ${{ matrix.cmake_flags}} -DBUILD_SHARED_LIBS=${{ matrix.shared_libs }} .
+ cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DSPIX_BUILD_TESTS=ON -DSPIX_BUILD_EXAMPLES=ON ${{ matrix.cmake_flags}} -DBUILD_SHARED_LIBS=${{ matrix.shared_libs }} -DSPIX_QT_MAJOR=${{ matrix.qt_version[0] }} .
- name: "Print cmake compile commands"
if: ${{ !contains(matrix.os, 'windows') }}
@@ -68,6 +77,19 @@ jobs:
- name: "Run Tests"
run: cd build && ctest -VV -C ${{ matrix.build_type }} && cd ..
+ - name: "Test GTest Examples (*nix)"
+ # Qt6 on GitHub Actions MacOS is currently bugged: https://bugreports.qt.io/browse/QTIFW-1592
+ if: ${{ !contains(matrix.os, 'windows') && !(contains(matrix.os, 'mac') && matrix.qt_version[0] == 6) }}
+ run: |
+ build/examples/GTest/SpixGTestExample -platform minimal
+ build/examples/RepeaterLoader/SpixRepeaterLoaderExampleGTest -platform minimal
+
+ - name: "Test GTest Examples (win)"
+ if: ${{ contains(matrix.os, 'windows') }}
+ run: |
+ .\build\examples\GTest\${{ matrix.build_type }}\SpixGTestExample.exe -platform minimal
+ .\build\examples\RepeaterLoader\${{ matrix.build_type }}\SpixRepeaterLoaderExampleGTest.exe -platform minimal
+
- name: "Install Spix (*nix)"
if: ${{ !contains(matrix.os, 'windows') }}
run: sudo cmake --install build --config ${{ matrix.build_type }}
@@ -80,7 +102,7 @@ jobs:
run: |
cd examples/BasicStandalone
mkdir build
- cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ${{ matrix.cmake_flags}} .
+ cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSPIX_QT_MAJOR=${{ matrix.qt_version[0] }} ${{ matrix.cmake_flags}} .
cmake --build build --config ${{ matrix.build_type }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff86414..7aea3f6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,11 @@
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.18)
project(Spix LANGUAGES CXX)
option(SPIX_BUILD_EXAMPLES "Build Spix examples." ON)
option(SPIX_BUILD_TESTS "Build Spix unit tests." OFF)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
-set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/modules")
set(CMAKE_CXX_STANDARD 14)
# Hide symbols unless explicitly flagged with SPIX_EXPORT
diff --git a/README.md b/README.md
index 40e31eb..c7f839e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
[](https://github.com/faaxm/spix/actions/workflows/build.yml)
-[](https://www.paypal.me/faaxm)
+
+
+
+
# Spix
Spix is a minimally invasive UI testing library that enables your
@@ -41,27 +44,9 @@ and make sure that it behaves as you expect. However, you can also use Spix as
an easy way to remote control existing Qt/QML applications or to automatically
generate and update screenshots for your documentation.
-## Two modes of operation
-Spix can be used in two ways, which are different in how events are generated and sent
-to your application:
-
-### Generate Qt events directly
-You can use Spix to directly create Qt events, either from C++ as a unit test, or from
-an external script via the network through RPC. Since the Qt events are generated directly inside the
-app, and do not come from the system, the mouse coursor will not actually move and interaction
-with other applications is limited. On the plus side, this mechanism is independent from
-the system your app is running on and can easily be used to control software on an embedded
-device via the network (RPC).
-
-### Generate system events externally
-In this case, Spix is not generating the events itself. Instead, you use a script to query
-Spix for the screen coordinates of qt objects and then generate events on the system level
-through other tools. One option is to use python together with PyAutoGUI for this, as is
-done in the [RemoteCtrl](examples/RemoteCtrl) example.
-
# Requirements
-* Qt
-* AnyRPC
+* Qt (both 5 and 6 supported)
+* [AnyRPC](https://github.com/sgieseking/anyrpc)
# Current Features
* Send mouse events (click, move, drag/drop)
@@ -73,21 +58,131 @@ done in the [RemoteCtrl](examples/RemoteCtrl) example.
* Quit the app
* Remote control, also of embedded devices / iOS
-# Building Spix
+# Setting Up Spix
+
+## Installing AnyRPC
+
+Spix requires [AnyRPC](https://github.com/sgieseking/anyrpc) be installed on the local machine before building. For *nix machines, AnyRPC can be built/installed using CMake:
+```sh
+# in a temporary directory
+git clone https://github.com/sgieseking/anyrpc.git
+cd anyrpc
+mkdir build
+cd build
+cmake -DBUILD_EXAMPLES=OFF -DBUILD_WITH_LOG4CPLUS=OFF -DBUILD_PROTOCOL_MESSAGEPACK=OFF ..
+cmake --build .
+sudo cmake --install .
+```
+
+For non-*nix machines, checkout the [CI install script](ci/install-deps.sh).
+
+## Installing Spix
+
Spix uses cmake and can be build with the standard cmake commands once cloned:
```sh
git clone https://github.com/faaxm/spix
cd spix
mkdir build && cd build
-cmake ..
+cmake -DSPIX_QT_MAJOR=6 ..
cmake --build .
+sudo cmake --install .
```
-You can also have a look at the build scripts in `ci/`, which are run on the
-build server to build and test Spix.
+> Change SPIX_QT_MAJOR to 5 to build against Qt5 instead of Qt6.
-If you installed the dependencies (like AnyRPC) in a non-standard directory
-you can point cmake to it by setting `CMAKE_PREFIX_PATH`, so
+If you installed the dependencies (like AnyRPC) in a non-standard directory you can point cmake to it by setting `CMAKE_PREFIX_PATH`, so
instead of `cmake ..` you run:
```sh
cmake -DCMAKE_PREFIX_PATH=/path/to/libs ..
```
+
+## Including Spix in your Qt project
+
+If using qmake, add the following to your Qt `.pro` file:
+```
+QT += quick
+INCLUDEPATH += /usr/local/include
+LIBS += -lSpix -lanyrpc
+```
+If using CMake, add the following to your `CMakeLists.txt`:
+```
+find_package(Spix REQUIRED)
+```
+
+Update your `main(...)` to start the Spix RPC server:
+```C++
+#include
+#include
+
+int main(...) {
+ ...
+ spix::AnyRpcServer server;
+ auto bot = new spix::QtQmlBot();
+ bot->runTestServer(server);
+ ...
+}
+```
+Finally, if you're using a `QQuickView` as your root window, you'll need to give it an object name in your `main` (otherwise the root window object name will be defined in your QML):
+```C++
+int main(...) {
+ QQuickView view;
+ view.setObjectName("root")
+ ...
+}
+```
+
+# Using Spix
+
+The easiest method of interacting with Spix is using the [XMLRPC client built into python](https://docs.python.org/3/library/xmlrpc.client.html#module-xmlrpc.client):
+```python
+import xmlrpc.client
+
+s = xmlrpc.client.ServerProxy('http://localhost:9000') # default port is 9000
+s.method(, )
+# for example:
+s.mouseClick("root/Button_2")
+resultText = s.getStringProperty("root/results", "text")
+```
+
+You can also use the XMLRPC client to list the available methods. The complete list of methods are also available in the [source](lib/src/AnyRpcServer.cpp).
+```python
+print(s.system.listMethods())
+# ['command', 'enterKey', 'existsAndVisible', 'getBoundingBox', 'getErrors', 'getStringProperty', 'inputText', 'mouseBeginDrag', 'mouseClick', 'mouseDropUrls', 'mouseEndDrag', 'quit', 'setStringProperty', 'system.listMethods', 'system.methodHelp', 'takeScreenshot', 'wait']
+print(s.system.methodHelp('mouseClick'))
+# Click on the object at the given path
+```
+
+Spix uses a slash-separated path format to select Qt objects. Selectors match against `objectName` or `id` if no object name is defined.
+```
+/(/...)
+```
+Spix matches children recursivley, allowing as much flexibility as needed:
+```
+# matches any `button` that is a descendant of `root` (even subchildren)
+'root/button'
+# matches any `button` that is a descendant of `numberpad` which is in turn a descendant of `root`.
+'root/numberpad/button'
+# and so on
+```
+
+More specifically, Spix's matching processes works as follows:
+* `` matches a top-level [`QQuickWindow`](https://doc-snapshots.qt.io/qt6-dev/qquickwindow.html) whose `objectName` (or `id` if `objectName` is empty) matches the specified string. Top-level windows are enumerated by [`QGuiApplication::topLevelWindows`](https://doc.qt.io/qt-6/qguiapplication.html#topLevelWindows).
+* `` matches the first child object whose `objectName` (or `id` if `objectName` is empty) matches the specified string using a recursive search of all children and subchildren of the root. This process repeats for every subsequent child path entry.
+
+
+## Two modes of operation
+In general, Spix can be used in two ways, which are different in how events are generated and sent
+to your application:
+
+### Generate Qt events directly
+You can use Spix to directly create Qt events, either from C++ as a unit test, or from
+an external script via the network through RPC. Since the Qt events are generated directly inside the
+app, and do not come from the system, the mouse coursor will not actually move and interaction
+with other applications is limited. On the plus side, this mechanism is independent from
+the system your app is running on and can easily be used to control software on an embedded
+device via the network (RPC).
+
+### Generate system events externally
+In this case, Spix is not generating the events itself. Instead, you use a script to query
+Spix for the screen coordinates of qt objects and then generate events on the system level
+through other tools. One option is to use python together with PyAutoGUI for this, as is
+done in the [RemoteCtrl](examples/RemoteCtrl) example.
diff --git a/ci/install-deps.sh b/ci/install-deps.sh
index a322562..568f5a1 100644
--- a/ci/install-deps.sh
+++ b/ci/install-deps.sh
@@ -6,7 +6,7 @@ mkdir deps && cd deps
git clone --recursive https://github.com/sgieseking/anyrpc.git
cd anyrpc && mkdir build && cd build
if [[ $RUNNER_OS == "Windows" ]]; then
- cmake -G"Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DBUILD_EXAMPLES=OFF -DBUILD_WITH_LOG4CPLUS=OFF -DANYRPC_LIB_BUILD_SHARED=${SHARED_LIBS} ..
+ cmake -G"Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DBUILD_EXAMPLES=OFF -DBUILD_WITH_LOG4CPLUS=OFF -DANYRPC_LIB_BUILD_SHARED=${SHARED_LIBS} ..
cmake --build . --target install --config $CI_BUILD_TYPE
else
cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DBUILD_EXAMPLES=OFF -DBUILD_WITH_LOG4CPLUS=OFF -DANYRPC_LIB_BUILD_SHARED=${SHARED_LIBS} ..
@@ -18,7 +18,7 @@ cd ../..
git clone --recursive https://github.com/google/googletest.git
cd googletest && mkdir build && cd build
if [[ $RUNNER_OS == "Windows" ]]; then
- cmake -G"Visual Studio 16 2019" -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE ..
+ cmake -G"Visual Studio 17 2022" -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE ..
cmake --build . --target install --config $CI_BUILD_TYPE
else
cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE ..
diff --git a/examples/Basic/CMakeLists.txt b/examples/Basic/CMakeLists.txt
index a308028..23885d3 100644
--- a/examples/Basic/CMakeLists.txt
+++ b/examples/Basic/CMakeLists.txt
@@ -4,8 +4,8 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
add_executable(SpixBasicExample "main.cpp" "qml.qrc")
target_compile_definitions(SpixBasicExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixBasicExample PRIVATE Qt5::Core Qt5::Quick Spix)
+target_link_libraries(SpixBasicExample PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick Spix)
diff --git a/examples/Basic/main.cpp b/examples/Basic/main.cpp
index 5622bae..fd45b83 100644
--- a/examples/Basic/main.cpp
+++ b/examples/Basic/main.cpp
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include
@@ -26,6 +27,8 @@ class MyTests : public spix::TestServer {
wait(std::chrono::milliseconds(500));
mouseClick(spix::ItemPath("mainWindow/Button_1"));
wait(std::chrono::milliseconds(500));
+ mouseClick(spix::ItemPath("mainWindow/Button_1"), spix::MouseButtons::Right);
+ wait(std::chrono::milliseconds(500));
auto result = getStringProperty("mainWindow/results", "text");
std::cout << "-------\nResult:\n-------\n" << result << "\n-------" << std::endl;
diff --git a/examples/Basic/main.qml b/examples/Basic/main.qml
index c13bea1..153f204 100644
--- a/examples/Basic/main.qml
+++ b/examples/Basic/main.qml
@@ -23,12 +23,34 @@ Window {
Button {
objectName: "Button_1"
text: "Press Me"
- onClicked: resultsView.appendText("Button 1 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 1 right clicked")
+ else
+ resultsView.appendText("Button 1 clicked")
+ }
+ }
}
Button {
objectName: "Button_2"
text: "Or Click Me"
- onClicked: resultsView.appendText("Button 2 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 2 right clicked")
+ else
+ resultsView.appendText("Button 2 clicked")
+ }
+ }
}
}
diff --git a/examples/BasicStandalone/CMakeLists.txt b/examples/BasicStandalone/CMakeLists.txt
index 79ac740..13dfeb7 100644
--- a/examples/BasicStandalone/CMakeLists.txt
+++ b/examples/BasicStandalone/CMakeLists.txt
@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.12)
project(SpixStandaloneExample LANGUAGES CXX)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
+
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../cmake/modules")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -14,9 +16,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
#
find_package(Threads REQUIRED)
find_package(AnyRPC REQUIRED)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
find_package(Spix CONFIG REQUIRED)
add_executable(SpixStandaloneExample "main.cpp" "qml.qrc")
target_compile_definitions(SpixStandaloneExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixStandaloneExample PRIVATE Threads::Threads Qt5::Core Qt5::Quick Spix::Spix)
+target_link_libraries(SpixStandaloneExample PRIVATE Threads::Threads Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick Spix::Spix)
diff --git a/examples/BasicStandalone/main.qml b/examples/BasicStandalone/main.qml
index c13bea1..153f204 100644
--- a/examples/BasicStandalone/main.qml
+++ b/examples/BasicStandalone/main.qml
@@ -23,12 +23,34 @@ Window {
Button {
objectName: "Button_1"
text: "Press Me"
- onClicked: resultsView.appendText("Button 1 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 1 right clicked")
+ else
+ resultsView.appendText("Button 1 clicked")
+ }
+ }
}
Button {
objectName: "Button_2"
text: "Or Click Me"
- onClicked: resultsView.appendText("Button 2 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 2 right clicked")
+ else
+ resultsView.appendText("Button 2 clicked")
+ }
+ }
}
}
diff --git a/examples/GTest/CMakeLists.txt b/examples/GTest/CMakeLists.txt
index 4d16159..385bcdf 100644
--- a/examples/GTest/CMakeLists.txt
+++ b/examples/GTest/CMakeLists.txt
@@ -4,9 +4,11 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
+
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
find_package(GTest REQUIRED)
add_executable(SpixGTestExample "main.cpp" "qml.qrc")
target_compile_definitions(SpixGTestExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixGTestExample PRIVATE Qt5::Core Qt5::Quick GTest::GTest Spix)
+target_link_libraries(SpixGTestExample PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick GTest::GTest Spix)
diff --git a/examples/GTest/main.cpp b/examples/GTest/main.cpp
index 72dafdb..0e6f701 100644
--- a/examples/GTest/main.cpp
+++ b/examples/GTest/main.cpp
@@ -16,6 +16,7 @@
#include
#include
+#include
#include
#include
@@ -62,6 +63,8 @@ TEST(GTestExample, BasicUITest)
srv->wait(std::chrono::milliseconds(500));
srv->mouseClick(spix::ItemPath("mainWindow/Button_1"));
srv->wait(std::chrono::milliseconds(500));
+ srv->mouseClick(spix::ItemPath("mainWindow/Button_1"), spix::MouseButtons::Right);
+ srv->wait(std::chrono::milliseconds(500));
auto result = srv->getStringProperty("mainWindow/results", "text");
@@ -70,7 +73,8 @@ Button 2 clicked
Button 2 clicked
Button 1 clicked
Button 2 clicked
-Button 1 clicked)RSLT";
+Button 1 clicked
+Button 1 right clicked)RSLT";
EXPECT_EQ(result, expected_result);
diff --git a/examples/GTest/main.qml b/examples/GTest/main.qml
index c13bea1..153f204 100644
--- a/examples/GTest/main.qml
+++ b/examples/GTest/main.qml
@@ -23,12 +23,34 @@ Window {
Button {
objectName: "Button_1"
text: "Press Me"
- onClicked: resultsView.appendText("Button 1 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 1 right clicked")
+ else
+ resultsView.appendText("Button 1 clicked")
+ }
+ }
}
Button {
objectName: "Button_2"
text: "Or Click Me"
- onClicked: resultsView.appendText("Button 2 clicked")
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+
+ onClicked:
+ {
+ if(mouse.button & Qt.RightButton)
+ resultsView.appendText("Button 2 right clicked")
+ else
+ resultsView.appendText("Button 2 clicked")
+ }
+ }
}
}
diff --git a/examples/ListGridView/CMakeLists.txt b/examples/ListGridView/CMakeLists.txt
index 1dda3e3..a253032 100644
--- a/examples/ListGridView/CMakeLists.txt
+++ b/examples/ListGridView/CMakeLists.txt
@@ -4,8 +4,10 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
+
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
add_executable(SpixListGridViewExample "main.cpp" "qml.qrc")
target_compile_definitions(SpixListGridViewExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixListGridViewExample PRIVATE Qt5::Core Qt5::Quick Spix)
+target_link_libraries(SpixListGridViewExample PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick Spix)
diff --git a/examples/RemoteCtrl/CMakeLists.txt b/examples/RemoteCtrl/CMakeLists.txt
index c2224ad..1675293 100644
--- a/examples/RemoteCtrl/CMakeLists.txt
+++ b/examples/RemoteCtrl/CMakeLists.txt
@@ -4,8 +4,10 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
+
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
add_executable(SpixRemoteCtrlExample "main.cpp" "qml.qrc")
target_compile_definitions(SpixRemoteCtrlExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixRemoteCtrlExample PRIVATE Qt5::Core Qt5::Quick Spix)
+target_link_libraries(SpixRemoteCtrlExample PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick Spix)
diff --git a/examples/RemoteCtrl/script/testScript.py b/examples/RemoteCtrl/script/testScript.py
index 92d3b08..b30fa4c 100644
--- a/examples/RemoteCtrl/script/testScript.py
+++ b/examples/RemoteCtrl/script/testScript.py
@@ -1,8 +1,11 @@
import xmlrpc.client
s = xmlrpc.client.ServerProxy('http://localhost:9000')
-print("Available Methods:")
-print(s.system.listMethods())
+print("[+] Available Methods:")
+for method in s.system.listMethods():
+ print("\t- ","{:20s}".format(method), " : ", end="")
+ print(s.system.methodHelp(method))
+
s.mouseClick("mainWindow/Button_1")
s.wait(200)
s.mouseClick("mainWindow/Button_2")
diff --git a/examples/RepeaterLoader/CMakeLists.txt b/examples/RepeaterLoader/CMakeLists.txt
index 0b7a544..5df7ef5 100644
--- a/examples/RepeaterLoader/CMakeLists.txt
+++ b/examples/RepeaterLoader/CMakeLists.txt
@@ -4,8 +4,14 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against")
+
+find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED)
+find_package(GTest REQUIRED)
add_executable(SpixRepeaterLoaderExample "main.cpp" "qml.qrc")
+add_executable(SpixRepeaterLoaderExampleGTest "main_gtest.cpp" "qml.qrc")
target_compile_definitions(SpixRepeaterLoaderExample PRIVATE $<$,$>:QT_QML_DEBUG>)
-target_link_libraries(SpixRepeaterLoaderExample PRIVATE Qt5::Core Qt5::Quick Spix)
+target_compile_definitions(SpixRepeaterLoaderExampleGTest PRIVATE $<$,$>:QT_QML_DEBUG>)
+target_link_libraries(SpixRepeaterLoaderExample PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick Spix)
+target_link_libraries(SpixRepeaterLoaderExampleGTest PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick GTest::GTest Spix)
diff --git a/examples/RepeaterLoader/main.cpp b/examples/RepeaterLoader/main.cpp
index 878eac6..8d12702 100644
--- a/examples/RepeaterLoader/main.cpp
+++ b/examples/RepeaterLoader/main.cpp
@@ -7,7 +7,7 @@
#include
#include
-#include
+#include
#include
#include
diff --git a/examples/RepeaterLoader/main_gtest.cpp b/examples/RepeaterLoader/main_gtest.cpp
new file mode 100644
index 0000000..7806fbf
--- /dev/null
+++ b/examples/RepeaterLoader/main_gtest.cpp
@@ -0,0 +1,108 @@
+/***
+ * Copyright (C) Falko Axmann. All rights reserved.
+ * Licensed under the MIT license.
+ * See LICENSE.txt file in the project root for full license information.
+ ****/
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+class SpixGTest;
+static SpixGTest* srv;
+
+class SpixGTest : public spix::TestServer {
+public:
+ SpixGTest(int argc, char* argv[])
+ {
+ m_argc = argc;
+ m_argv = argv;
+ }
+
+ int testResult() { return m_result.load(); }
+
+protected:
+ int m_argc;
+ char** m_argv;
+ std::atomic m_result {0};
+
+ void executeTest() override
+ {
+ srv = this;
+ ::testing::InitGoogleTest(&m_argc, m_argv);
+ auto testResult = RUN_ALL_TESTS();
+ m_result.store(testResult);
+ }
+};
+
+TEST(RepeaterLoaderGTest, RepeaterLoaderTest)
+{
+ EXPECT_FALSE(srv->existsAndVisible("mainWindow/ItemDisplayLoader/textItem"))
+ << "Error: Item should not be visible at launch.";
+
+ srv->mouseClick("mainWindow/ItemButtons/Item_0");
+ srv->wait(std::chrono::milliseconds(500));
+
+ EXPECT_TRUE(srv->existsAndVisible("mainWindow/ItemDisplayLoader/textItem"))
+ << "Error: Item should be displayed after button pressed.";
+
+ // 'ItemButtons' is not required in the path,
+ // as 'gridItem_0' is unique within 'mainWindow'
+ srv->mouseClick("mainWindow/Item_1");
+ srv->wait(std::chrono::milliseconds(500));
+
+ EXPECT_STREQ(srv->getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text").c_str(),
+ "I am a view with a pear. Trust me.")
+ << "Error: Wrong item displayed.";
+
+ srv->mouseClick("mainWindow/ItemButtons/Item_2");
+ srv->wait(std::chrono::milliseconds(500));
+ srv->mouseClick("mainWindow/ItemButtons/Item_0");
+ srv->wait(std::chrono::milliseconds(500));
+ srv->mouseClick("mainWindow/ItemButtons/Item_3");
+ srv->wait(std::chrono::milliseconds(100));
+
+ EXPECT_STREQ(srv->getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text").c_str(),
+ "I am a view with a cucumber. Trust me.")
+ << "Error: Wrong item displayed";
+
+ srv->mouseClick("mainWindow/itemCombo");
+ srv->wait(std::chrono::milliseconds(400));
+
+ srv->enterKey("mainWindow/itemCombo", spix::KeyCodes::Down, 0); // Down
+ srv->wait(std::chrono::milliseconds(200));
+ srv->enterKey("mainWindow/itemCombo", spix::KeyCodes::Down, 0); // Down
+ srv->wait(std::chrono::milliseconds(200));
+ srv->enterKey("mainWindow/itemCombo", spix::KeyCodes::Enter, 0); // Enter
+ srv->wait(std::chrono::milliseconds(100));
+
+ EXPECT_STREQ(srv->getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text").c_str(),
+ "I am a view with a banana. Trust me.")
+ << "Error: Wrong item displayed";
+
+ srv->quit();
+}
+
+int main(int argc, char* argv[])
+{
+ // Init Qt Qml Application
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ // Instantiate and run tests
+ SpixGTest tests(argc, argv);
+ auto bot = new spix::QtQmlBot();
+ bot->runTestServer(tests);
+
+ app.exec();
+ return tests.testResult();
+}
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index ccd1859..723de5e 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -8,14 +8,14 @@ include(CMakePackageConfigHelpers)
# Dependencies
#
find_package(Threads REQUIRED)
-find_package(Qt5
+find_package(AnyRPC REQUIRED)
+find_package(Qt${SPIX_QT_MAJOR}
COMPONENTS
Core
Gui
Quick
REQUIRED)
-find_package(AnyRPC REQUIRED)
-
+message("Building Spix for Qt ${SPIX_QT_MAJOR}")
#
# Sources
@@ -106,7 +106,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX source FILES ${SOURCES})
#
# Qt MOC Files
#
-qt5_wrap_cpp(MOC_FILES "include/Spix/QtQmlBot.h")
+cmake_language(CALL "qt${SPIX_QT_MAJOR}_wrap_cpp" MOC_FILES "include/Spix/QtQmlBot.h")
#
@@ -124,9 +124,10 @@ target_include_directories(Spix
target_link_libraries(Spix
PUBLIC
Threads::Threads
- Qt5::Core
- Qt5::Gui
- Qt5::Quick
+ Qt${SPIX_QT_MAJOR}::Core
+ Qt${SPIX_QT_MAJOR}::Gui
+ Qt${SPIX_QT_MAJOR}::Quick
+ PRIVATE
AnyRPC::anyrpc
)
diff --git a/lib/include/Spix/Events/Identifiers.h b/lib/include/Spix/Events/Identifiers.h
new file mode 100644
index 0000000..a5c655d
--- /dev/null
+++ b/lib/include/Spix/Events/Identifiers.h
@@ -0,0 +1,169 @@
+/***
+ * Copyright (C) Falko Axmann. All rights reserved.
+ * Licensed under the MIT license.
+ * See LICENSE.txt file in the project root for full license information.
+ ****/
+
+#pragma once
+
+namespace spix {
+
+struct KeyCodes {
+ enum : int
+ {
+ Escape = 0x01000000,
+ Tab = 0x01000001,
+ Backtab = 0x01000002,
+ Backspace = 0x01000003,
+ Return = 0x01000004,
+ Enter = 0x01000005,
+ Insert = 0x01000006,
+ Delete = 0x01000007,
+ Pause = 0x01000008,
+ Print = 0x01000009,
+ SysReq = 0x0100000a,
+ Clear = 0x0100000b,
+ Home = 0x01000010,
+ End = 0x01000011,
+ Left = 0x01000012,
+ Up = 0x01000013,
+ Right = 0x01000014,
+ Down = 0x01000015,
+ PageUp = 0x01000016,
+ PageDown = 0x01000017,
+ Shift = 0x01000020,
+ Control = 0x01000021,
+ Meta = 0x01000022,
+ Alt = 0x01000023,
+ CapsLock = 0x01000024,
+ NumLock = 0x01000025,
+ ScrollLock = 0x01000026,
+ F1 = 0x01000030,
+ F2 = 0x01000031,
+ F3 = 0x01000032,
+ F4 = 0x01000033,
+ F5 = 0x01000034,
+ F6 = 0x01000035,
+ F7 = 0x01000036,
+ F8 = 0x01000037,
+ F9 = 0x01000038,
+ F10 = 0x01000039,
+ F11 = 0x0100003a,
+ F12 = 0x0100003b,
+ F13 = 0x0100003c,
+ F14 = 0x0100003d,
+ F15 = 0x0100003e,
+ F16 = 0x0100003f,
+ F17 = 0x01000040,
+ F18 = 0x01000041,
+ F19 = 0x01000042,
+ F20 = 0x01000043,
+ F21 = 0x01000044,
+ F22 = 0x01000045,
+ F23 = 0x01000046,
+ F24 = 0x01000047,
+ F25 = 0x01000048,
+ F26 = 0x01000049,
+ F27 = 0x0100004a,
+ F28 = 0x0100004b,
+ F29 = 0x0100004c,
+ F30 = 0x0100004d,
+ F31 = 0x0100004e,
+ F32 = 0x0100004f,
+ F33 = 0x01000050,
+ F34 = 0x01000051,
+ F35 = 0x01000052,
+ Super_L = 0x01000053,
+ Super_R = 0x01000054,
+ Menu = 0x01000055,
+ Hyper_L = 0x01000056,
+ Hyper_R = 0x01000057,
+ Help = 0x01000058,
+ Direction_L = 0x01000059,
+ Direction_R = 0x01000060,
+ Space = 0x20,
+ Char_Exclam = 0x21,
+ Char_QuoteDbl = 0x22,
+ Char_NumberSign = 0x23,
+ Char_Dollar = 0x24,
+ Char_Percent = 0x25,
+ Char_Ampersand = 0x26,
+ Char_Apostrophe = 0x27,
+ Char_ParenLeft = 0x28,
+ Char_ParenRight = 0x29,
+ Char_Asterisk = 0x2a,
+ Char_Plus = 0x2b,
+ Char_Comma = 0x2c,
+ Char_Minus = 0x2d,
+ Char_Period = 0x2e,
+ Char_Slash = 0x2f,
+ Num_0 = 0x30,
+ Num_1 = 0x31,
+ Num_2 = 0x32,
+ Num_3 = 0x33,
+ Num_4 = 0x34,
+ Num_5 = 0x35,
+ Num_6 = 0x36,
+ Num_7 = 0x37,
+ Num_8 = 0x38,
+ Num_9 = 0x39,
+ Char_Colon = 0x3a,
+ Char_Semicolon = 0x3b,
+ Char_Less = 0x3c,
+ Char_Equal = 0x3d,
+ Char_Greater = 0x3e,
+ Char_Question = 0x3f,
+ Char_At = 0x40,
+ Char_A = 0x41,
+ Char_B = 0x42,
+ Char_C = 0x43,
+ Char_D = 0x44,
+ Char_E = 0x45,
+ Char_F = 0x46,
+ Char_G = 0x47,
+ Char_H = 0x48,
+ Char_I = 0x49,
+ Char_J = 0x4a,
+ Char_K = 0x4b,
+ Char_L = 0x4c,
+ Char_M = 0x4d,
+ Char_N = 0x4e,
+ Char_O = 0x4f,
+ Char_P = 0x50,
+ Char_Q = 0x51,
+ Char_R = 0x52,
+ Char_S = 0x53,
+ Char_T = 0x54,
+ Char_U = 0x55,
+ Char_V = 0x56,
+ Char_W = 0x57,
+ Char_X = 0x58,
+ Char_Y = 0x59,
+ Char_Z = 0x5a
+ };
+};
+
+using KeyModifier = unsigned;
+struct KeyModifiers {
+ enum : KeyModifier
+ {
+ None = 0,
+ Shift = 1 << 0,
+ Control = 1 << 1,
+ Alt = 1 << 2,
+ Meta = 1 << 3
+ };
+};
+
+using MouseButton = unsigned;
+struct MouseButtons {
+ enum : MouseButton
+ {
+ None = 0,
+ Left = 1 << 0,
+ Right = 1 << 1,
+ Middle = 1 << 2
+ };
+};
+
+} // namespace spix
diff --git a/lib/include/Spix/Events/KeyCodes.h b/lib/include/Spix/Events/KeyCodes.h
deleted file mode 100644
index 8c4ad40..0000000
--- a/lib/include/Spix/Events/KeyCodes.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/***
- * Copyright (C) Falko Axmann. All rights reserved.
- * Licensed under the MIT license.
- * See LICENSE.txt file in the project root for full license information.
- ****/
-
-#pragma once
-
-namespace spix {
-
-struct KeyCodes {
- static const int Escape = 0x01000000;
- static const int Tab = 0x01000001;
- static const int Backtab = 0x01000002;
- static const int Backspace = 0x01000003;
- static const int Return = 0x01000004;
- static const int Enter = 0x01000005;
- static const int Insert = 0x01000006;
- static const int Delete = 0x01000007;
- static const int Pause = 0x01000008;
- static const int Print = 0x01000009;
- static const int SysReq = 0x0100000a;
- static const int Clear = 0x0100000b;
- static const int Home = 0x01000010;
- static const int End = 0x01000011;
- static const int Left = 0x01000012;
- static const int Up = 0x01000013;
- static const int Right = 0x01000014;
- static const int Down = 0x01000015;
- static const int PageUp = 0x01000016;
- static const int PageDown = 0x01000017;
- static const int Shift = 0x01000020;
- static const int Control = 0x01000021;
- static const int Meta = 0x01000022;
- static const int Alt = 0x01000023;
- static const int CapsLock = 0x01000024;
- static const int NumLock = 0x01000025;
- static const int ScrollLock = 0x01000026;
- static const int F1 = 0x01000030;
- static const int F2 = 0x01000031;
- static const int F3 = 0x01000032;
- static const int F4 = 0x01000033;
- static const int F5 = 0x01000034;
- static const int F6 = 0x01000035;
- static const int F7 = 0x01000036;
- static const int F8 = 0x01000037;
- static const int F9 = 0x01000038;
- static const int F10 = 0x01000039;
- static const int F11 = 0x0100003a;
- static const int F12 = 0x0100003b;
- static const int F13 = 0x0100003c;
- static const int F14 = 0x0100003d;
- static const int F15 = 0x0100003e;
- static const int F16 = 0x0100003f;
- static const int F17 = 0x01000040;
- static const int F18 = 0x01000041;
- static const int F19 = 0x01000042;
- static const int F20 = 0x01000043;
- static const int F21 = 0x01000044;
- static const int F22 = 0x01000045;
- static const int F23 = 0x01000046;
- static const int F24 = 0x01000047;
- static const int F25 = 0x01000048;
- static const int F26 = 0x01000049;
- static const int F27 = 0x0100004a;
- static const int F28 = 0x0100004b;
- static const int F29 = 0x0100004c;
- static const int F30 = 0x0100004d;
- static const int F31 = 0x0100004e;
- static const int F32 = 0x0100004f;
- static const int F33 = 0x01000050;
- static const int F34 = 0x01000051;
- static const int F35 = 0x01000052;
- static const int Super_L = 0x01000053;
- static const int Super_R = 0x01000054;
- static const int Menu = 0x01000055;
- static const int Hyper_L = 0x01000056;
- static const int Hyper_R = 0x01000057;
- static const int Help = 0x01000058;
- static const int Direction_L = 0x01000059;
- static const int Direction_R = 0x01000060;
- static const int Space = 0x20;
- static const int Char_Exclam = 0x21;
- static const int Char_QuoteDbl = 0x22;
- static const int Char_NumberSign = 0x23;
- static const int Char_Dollar = 0x24;
- static const int Char_Percent = 0x25;
- static const int Char_Ampersand = 0x26;
- static const int Char_Apostrophe = 0x27;
- static const int Char_ParenLeft = 0x28;
- static const int Char_ParenRight = 0x29;
- static const int Char_Asterisk = 0x2a;
- static const int Char_Plus = 0x2b;
- static const int Char_Comma = 0x2c;
- static const int Char_Minus = 0x2d;
- static const int Char_Period = 0x2e;
- static const int Char_Slash = 0x2f;
- static const int Num_0 = 0x30;
- static const int Num_1 = 0x31;
- static const int Num_2 = 0x32;
- static const int Num_3 = 0x33;
- static const int Num_4 = 0x34;
- static const int Num_5 = 0x35;
- static const int Num_6 = 0x36;
- static const int Num_7 = 0x37;
- static const int Num_8 = 0x38;
- static const int Num_9 = 0x39;
- static const int Char_Colon = 0x3a;
- static const int Char_Semicolon = 0x3b;
- static const int Char_Less = 0x3c;
- static const int Char_Equal = 0x3d;
- static const int Char_Greater = 0x3e;
- static const int Char_Question = 0x3f;
- static const int Char_At = 0x40;
- static const int Char_A = 0x41;
- static const int Char_B = 0x42;
- static const int Char_C = 0x43;
- static const int Char_D = 0x44;
- static const int Char_E = 0x45;
- static const int Char_F = 0x46;
- static const int Char_G = 0x47;
- static const int Char_H = 0x48;
- static const int Char_I = 0x49;
- static const int Char_J = 0x4a;
- static const int Char_K = 0x4b;
- static const int Char_L = 0x4c;
- static const int Char_M = 0x4d;
- static const int Char_N = 0x4e;
- static const int Char_O = 0x4f;
- static const int Char_P = 0x50;
- static const int Char_Q = 0x51;
- static const int Char_R = 0x52;
- static const int Char_S = 0x53;
- static const int Char_T = 0x54;
- static const int Char_U = 0x55;
- static const int Char_V = 0x56;
- static const int Char_W = 0x57;
- static const int Char_X = 0x58;
- static const int Char_Y = 0x59;
- static const int Char_Z = 0x5a;
-};
-
-} // namespace spix
diff --git a/lib/include/Spix/TestServer.h b/lib/include/Spix/TestServer.h
index b862837..65bd32b 100644
--- a/lib/include/Spix/TestServer.h
+++ b/lib/include/Spix/TestServer.h
@@ -13,6 +13,7 @@
#include
#include
+#include
#include
@@ -45,6 +46,7 @@ class SPIX_EXPORT TestServer {
// Commands
void wait(std::chrono::milliseconds waitTime);
void mouseClick(ItemPath path);
+ void mouseClick(ItemPath path, MouseButton mouseButton);
void mouseBeginDrag(ItemPath path);
void mouseEndDrag(ItemPath path);
void mouseDropUrls(ItemPath path, const std::vector& urls);
diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp
index 735365c..7e275f4 100644
--- a/lib/src/AnyRpcServer.cpp
+++ b/lib/src/AnyRpcServer.cpp
@@ -24,52 +24,79 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort)
anyrpc::MethodManager* methodManager = m_pimpl->server->GetMethodManager();
- utils::AddFunctionToAnyRpc(methodManager, "wait", "Wait given number of milliseconds",
+ utils::AddFunctionToAnyRpc(methodManager, "wait",
+ "Wait given number of milliseconds | wait(int millisecondsToWait)",
[this](int ms) { wait(std::chrono::milliseconds(ms)); });
- utils::AddFunctionToAnyRpc(methodManager, "mouseClick", "Click on the object at the given path",
+ utils::AddFunctionToAnyRpc(methodManager, "mouseClick",
+ "Click on the object at the given path | mouseClick(string path)",
[this](std::string path) { mouseClick(std::move(path)); });
- utils::AddFunctionToAnyRpc(methodManager, "mouseBeginDrag", "Begin a drag with the mouse",
+
+ utils::AddFunctionToAnyRpc(methodManager, "mouseClickWithButton",
+ "Click on the object at the given path with the given mouse button | mouseClickWithButton(string path, int "
+ "mouseButton)",
+ [this](std::string path, int mouseButton) { mouseClick(std::move(path), mouseButton); });
+
+ utils::AddFunctionToAnyRpc(methodManager, "mouseBeginDrag",
+ "Begin a drag with the mouse | mouseBeginDrag(string path)",
[this](std::string path) { mouseBeginDrag(std::move(path)); });
- utils::AddFunctionToAnyRpc(methodManager, "mouseEndDrag", "End a drag with the mouse",
+
+ utils::AddFunctionToAnyRpc(methodManager, "mouseEndDrag",
+ "End a drag with the mouse | mouseEndDrag(string path)",
[this](std::string path) { mouseEndDrag(std::move(path)); });
+
utils::AddFunctionToAnyRpc)>(methodManager, "mouseDropUrls",
- "Drop Urls with mouse",
+ "Drop Urls with mouse | mouseDropUrls(string path, [string url1, ...])",
[this](std::string path, std::vector urls) { mouseDropUrls(std::move(path), urls); });
+
utils::AddFunctionToAnyRpc(methodManager, "inputText",
- "Enter text. Events are sent to the item's window at path.",
+ "Enter text. Events are sent to the item's window at path. | inputText(string path, string text)",
[this](std::string path, std::string text) { inputText(std::move(path), std::move(text)); });
- utils::AddFunctionToAnyRpc(methodManager, "enterKey", "Press and release a key",
+
+ utils::AddFunctionToAnyRpc(methodManager, "enterKey",
+ "Press and release a key | enterKey(string path, int keyCode, unsigned int keyModifier)",
[this](std::string path, int keyCode, unsigned modifiers) { enterKey(std::move(path), keyCode, modifiers); });
utils::AddFunctionToAnyRpc(methodManager, "getStringProperty",
- "Return a property as string", [this](std::string path, std::string property) {
+ "Return a property as string | getStringProperty(string path, string property) : string property_value",
+ [this](std::string path, std::string property) {
return getStringProperty(std::move(path), std::move(property));
});
+
utils::AddFunctionToAnyRpc(methodManager, "setStringProperty",
- "Set the string value of the given property",
+ "Set the string value of the given property | setStringProperty(string path, string property, string "
+ "new_value)",
[this](std::string path, std::string property, std::string value) {
setStringProperty(std::move(path), std::move(property), std::move(value));
});
+
utils::AddFunctionToAnyRpc(std::string)>(methodManager, "getBoundingBox",
- "Return the bounding box of an item in screen coordinates", [this](std::string path) {
+ "Return the bounding box of an item in screen coordinates | getBoundingBox(string path) : (doubles) "
+ "[topLeft.x, topLeft.y , width, height]",
+ [this](std::string path) {
auto bounds = getBoundingBox(std::move(path));
return std::vector {bounds.topLeft.x, bounds.topLeft.y, bounds.size.width, bounds.size.height};
});
+
utils::AddFunctionToAnyRpc(methodManager, "existsAndVisible",
- "Returns true if the given object exists",
+ "Returns true if the given object exists | existsAndVisible(string path) : bool exists_and_visible",
[this](std::string path) { return existsAndVisible(std::move(path)); });
+
utils::AddFunctionToAnyRpc()>(methodManager, "getErrors",
- "Returns internal errors that occurred during test execution", [this]() { return getErrors(); });
+ "Returns internal errors that occurred during test execution | getErrors() : (strings) [error1, ...]",
+ [this]() { return getErrors(); });
utils::AddFunctionToAnyRpc(methodManager, "takeScreenshot",
- "Take a screenshot of the object and save it as a file", [this](std::string targetItem, std::string filePath) {
+ "Take a screenshot of the object and save it as a file | takeScreenshot(string pathToTargetedItem, string "
+ "filePath)",
+ [this](std::string targetItem, std::string filePath) {
return takeScreenshot(std::move(targetItem), std::move(filePath));
});
- utils::AddFunctionToAnyRpc(methodManager, "quit", "Close the app", [this] { quit(); });
+ utils::AddFunctionToAnyRpc(methodManager, "quit", "Close the app | quit()", [this] { quit(); });
- utils::AddFunctionToAnyRpc(methodManager, "command", "Executes a generic command",
+ utils::AddFunctionToAnyRpc(methodManager, "command",
+ "Executes a generic command | command(string command, string payload)",
[this](std::string command, std::string payload) { genericCommand(command, payload); });
m_pimpl->server->BindAndListen(anyrpcPort);
diff --git a/lib/src/Commands/ClickOnItem.cpp b/lib/src/Commands/ClickOnItem.cpp
index 3bafdda..8cbc570 100644
--- a/lib/src/Commands/ClickOnItem.cpp
+++ b/lib/src/Commands/ClickOnItem.cpp
@@ -11,8 +11,9 @@
namespace spix {
namespace cmd {
-ClickOnItem::ClickOnItem(ItemPosition path)
+ClickOnItem::ClickOnItem(ItemPosition path, MouseButton mouseButton)
: m_position(std::move(path))
+, m_mouseButton(mouseButton)
{
}
@@ -28,8 +29,8 @@ void ClickOnItem::execute(CommandEnvironment& env)
auto size = item->size();
auto mousePoint = m_position.positionForItemSize(size);
- env.scene().events().mouseDown(item.get(), mousePoint, Events::MouseButtons::left);
- env.scene().events().mouseUp(item.get(), mousePoint, Events::MouseButtons::left);
+ env.scene().events().mouseDown(item.get(), mousePoint, m_mouseButton);
+ env.scene().events().mouseUp(item.get(), mousePoint, m_mouseButton);
}
} // namespace cmd
diff --git a/lib/src/Commands/ClickOnItem.h b/lib/src/Commands/ClickOnItem.h
index d13c826..c481997 100644
--- a/lib/src/Commands/ClickOnItem.h
+++ b/lib/src/Commands/ClickOnItem.h
@@ -9,6 +9,7 @@
#include
#include "Command.h"
+#include
#include
namespace spix {
@@ -16,12 +17,13 @@ namespace cmd {
class SPIX_EXPORT ClickOnItem : public Command {
public:
- ClickOnItem(ItemPosition path);
+ ClickOnItem(ItemPosition path, MouseButton mouseButton);
void execute(CommandEnvironment& env) override;
private:
ItemPosition m_position;
+ MouseButton m_mouseButton;
};
} // namespace cmd
diff --git a/lib/src/Commands/DragBegin.cpp b/lib/src/Commands/DragBegin.cpp
index ea74b56..f1c4665 100644
--- a/lib/src/Commands/DragBegin.cpp
+++ b/lib/src/Commands/DragBegin.cpp
@@ -27,7 +27,7 @@ void DragBegin::execute(CommandEnvironment& env)
auto size = item->size();
Point midPoint(size.width / 2.0, size.height / 2.0);
- env.scene().events().mouseDown(item.get(), midPoint, Events::MouseButtons::left);
+ env.scene().events().mouseDown(item.get(), midPoint, MouseButtons::Left);
env.scene().events().mouseMove(item.get(), midPoint);
}
diff --git a/lib/src/Commands/DragEnd.cpp b/lib/src/Commands/DragEnd.cpp
index 351734d..f8f1073 100644
--- a/lib/src/Commands/DragEnd.cpp
+++ b/lib/src/Commands/DragEnd.cpp
@@ -28,7 +28,7 @@ void DragEnd::execute(CommandEnvironment& env)
auto size = item->size();
Point midPoint(size.width / 2.0, size.height / 2.0);
env.scene().events().mouseMove(item.get(), midPoint);
- env.scene().events().mouseUp(item.get(), midPoint, Events::MouseButtons::left);
+ env.scene().events().mouseUp(item.get(), midPoint, MouseButtons::Left);
}
} // namespace cmd
diff --git a/lib/src/Commands/EnterKey.cpp b/lib/src/Commands/EnterKey.cpp
index 0b5b782..58ca297 100644
--- a/lib/src/Commands/EnterKey.cpp
+++ b/lib/src/Commands/EnterKey.cpp
@@ -11,7 +11,7 @@
namespace spix {
namespace cmd {
-EnterKey::EnterKey(ItemPath path, int keyCode, Events::KeyModifier mod)
+EnterKey::EnterKey(ItemPath path, int keyCode, KeyModifier mod)
: m_path(std::move(path))
, m_keyCode(keyCode)
, m_mod(mod)
diff --git a/lib/src/Commands/EnterKey.h b/lib/src/Commands/EnterKey.h
index dbe49f4..1c8720e 100644
--- a/lib/src/Commands/EnterKey.h
+++ b/lib/src/Commands/EnterKey.h
@@ -15,14 +15,14 @@ namespace cmd {
class EnterKey : public Command {
public:
- EnterKey(ItemPath path, int keyCode, Events::KeyModifier mod);
+ EnterKey(ItemPath path, int keyCode, KeyModifier mod);
void execute(CommandEnvironment& env) override;
private:
ItemPath m_path;
int m_keyCode;
- Events::KeyModifier m_mod;
+ KeyModifier m_mod;
};
} // namespace cmd
diff --git a/lib/src/Scene/Events.h b/lib/src/Scene/Events.h
index 34df6a0..b37fc91 100644
--- a/lib/src/Scene/Events.h
+++ b/lib/src/Scene/Events.h
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
@@ -16,23 +17,6 @@ namespace spix {
class Events {
public:
- struct MouseButtons {
- static const unsigned none = 0;
- static const unsigned left = 1 << 0;
- static const unsigned right = 1 << 1;
- static const unsigned middle = 1 << 2;
- };
- using MouseButton = unsigned;
-
- struct KeyModifiers {
- static const unsigned none = 0;
- static const unsigned shift = 1 << 0;
- static const unsigned control = 1 << 1;
- static const unsigned alt = 1 << 2;
- static const unsigned meta = 1 << 3;
- };
- using KeyModifier = unsigned;
-
virtual ~Events() = default;
virtual void mouseDown(Item* item, Point loc, MouseButton button) = 0;
diff --git a/lib/src/Scene/Qt/QtEvents.cpp b/lib/src/Scene/Qt/QtEvents.cpp
index 1d17447..449b901 100644
--- a/lib/src/Scene/Qt/QtEvents.cpp
+++ b/lib/src/Scene/Qt/QtEvents.cpp
@@ -37,46 +37,46 @@ QQuickWindow* getWindowAndPositionForItem(Item* item, Point relToItemPos, QPoint
return window;
}
-Qt::MouseButton getQtMouseButtonValue(Events::MouseButton button)
+Qt::MouseButton getQtMouseButtonValue(MouseButton button)
{
unsigned qtButton = Qt::MouseButton::NoButton;
- if (button & Events::MouseButtons::left) {
+ if (button & MouseButtons::Left) {
qtButton |= Qt::MouseButton::LeftButton;
}
- if (button & Events::MouseButtons::right) {
+ if (button & MouseButtons::Right) {
qtButton |= Qt::MouseButton::RightButton;
}
- if (button & Events::MouseButtons::middle) {
+ if (button & MouseButtons::Middle) {
qtButton |= Qt::MouseButton::MiddleButton;
}
return static_cast(qtButton);
}
-Qt::KeyboardModifiers getQtKeyboardModifiers(QtEvents::KeyModifier mod)
+Qt::KeyboardModifiers getQtKeyboardModifiers(KeyModifier mod)
{
Qt::KeyboardModifiers qtmod = Qt::NoModifier;
- if (mod & QtEvents::KeyModifiers::shift) {
+ if (mod & KeyModifiers::Shift) {
qtmod = qtmod | Qt::ShiftModifier;
}
- if (mod & QtEvents::KeyModifiers::control) {
+ if (mod & KeyModifiers::Control) {
qtmod = qtmod | Qt::ControlModifier;
}
- if (mod & QtEvents::KeyModifiers::alt) {
+ if (mod & KeyModifiers::Alt) {
qtmod = qtmod | Qt::AltModifier;
}
- if (mod & QtEvents::KeyModifiers::meta) {
+ if (mod & KeyModifiers::Meta) {
qtmod = qtmod | Qt::MetaModifier;
}
return qtmod;
}
-void sendQtKeyEvent(Item* item, bool press, int keyCode, QtEvents::KeyModifier mod)
+void sendQtKeyEvent(Item* item, bool press, int keyCode, KeyModifier mod)
{
auto qtitem = dynamic_cast(item);
if (!qtitem)
@@ -102,8 +102,8 @@ void QtEvents::mouseDown(Item* item, Point loc, MouseButton button)
Qt::MouseButton eventCausingButton = getQtMouseButtonValue(button);
Qt::MouseButtons activeButtons = getQtMouseButtonValue(m_pressedMouseButtons);
- QMouseEvent* event = new QMouseEvent(
- QEvent::MouseButtonPress, windowLoc, eventCausingButton, activeButtons, Qt::KeyboardModifier());
+ QMouseEvent* event
+ = new QMouseEvent(QEvent::MouseButtonPress, windowLoc, eventCausingButton, activeButtons, Qt::NoModifier);
QGuiApplication::postEvent(window, event);
}
@@ -114,12 +114,20 @@ void QtEvents::mouseUp(Item* item, Point loc, MouseButton button)
if (!window)
return;
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ // Qt6 expects the mouse to be down during the event
+ Qt::MouseButton eventCausingButton = getQtMouseButtonValue(button);
+ Qt::MouseButtons activeButtons = getQtMouseButtonValue(m_pressedMouseButtons);
+ m_pressedMouseButtons ^= button;
+#else
+ // Qt5 expects the mouse to be up during the event
m_pressedMouseButtons ^= button;
Qt::MouseButton eventCausingButton = getQtMouseButtonValue(button);
Qt::MouseButtons activeButtons = getQtMouseButtonValue(m_pressedMouseButtons);
+#endif
- QMouseEvent* event = new QMouseEvent(
- QEvent::MouseButtonRelease, windowLoc, eventCausingButton, activeButtons, Qt::KeyboardModifier());
+ QMouseEvent* event
+ = new QMouseEvent(QEvent::MouseButtonRelease, windowLoc, eventCausingButton, activeButtons, Qt::NoModifier);
QGuiApplication::postEvent(window, event);
}
@@ -134,14 +142,14 @@ void QtEvents::mouseMove(Item* item, Point loc)
// Wiggle the cursor a bit. This is needed to correctly recognize drag events
windowLoc.rx() -= 1;
- QMouseEvent* mouseMoveEvent = new QMouseEvent(
- QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::KeyboardModifier());
+ QMouseEvent* mouseMoveEvent
+ = new QMouseEvent(QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::NoModifier);
QGuiApplication::postEvent(window, mouseMoveEvent);
// Wiggle the cursor a bit. This is needed to correctly recognize drag events
windowLoc.rx() += 1;
- mouseMoveEvent = new QMouseEvent(
- QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::KeyboardModifier());
+ mouseMoveEvent
+ = new QMouseEvent(QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::NoModifier);
QGuiApplication::postEvent(window, mouseMoveEvent);
}
@@ -153,8 +161,7 @@ void QtEvents::stringInput(Item* item, const std::string& text)
auto window = qtitem->qquickitem()->window();
- auto keyDownEvent
- = new QKeyEvent(QEvent::KeyPress, 0 /* key id */, Qt::KeyboardModifier(), QString::fromStdString(text));
+ auto keyDownEvent = new QKeyEvent(QEvent::KeyPress, 0 /* key id */, Qt::NoModifier, QString::fromStdString(text));
QGuiApplication::postEvent(window, keyDownEvent);
}
@@ -189,15 +196,15 @@ void QtEvents::extMouseDrop(Item* item, Point loc, PasteboardContent& content)
mimeData->setUrls(urlList);
auto enter = new QDragEnterEvent(windowLocInt, Qt::CopyAction | Qt::MoveAction | Qt::LinkAction, mimeData,
- Qt::MouseButton::NoButton, Qt::KeyboardModifier());
+ Qt::MouseButton::NoButton, Qt::NoModifier);
QGuiApplication::postEvent(window, enter);
auto move = new QDragMoveEvent(windowLocInt, Qt::CopyAction | Qt::MoveAction | Qt::LinkAction, mimeData,
- Qt::MouseButton::NoButton, Qt::KeyboardModifier());
+ Qt::MouseButton::NoButton, Qt::NoModifier);
QGuiApplication::postEvent(window, move);
auto drop = new QDropEvent(windowLoc, Qt::CopyAction | Qt::MoveAction | Qt::LinkAction, mimeData,
- Qt::MouseButton::NoButton, Qt::KeyboardModifier());
+ Qt::MouseButton::NoButton, Qt::NoModifier);
QGuiApplication::postEvent(window, drop);
}
diff --git a/lib/src/Scene/Qt/QtEvents.h b/lib/src/Scene/Qt/QtEvents.h
index 02bb1e6..9cce57b 100644
--- a/lib/src/Scene/Qt/QtEvents.h
+++ b/lib/src/Scene/Qt/QtEvents.h
@@ -23,7 +23,7 @@ class QtEvents : public Events {
private:
/// Keep track of which buttons are currently pressed
- Events::MouseButton m_pressedMouseButtons = Events::MouseButtons::none;
+ MouseButton m_pressedMouseButtons = MouseButtons::None;
};
} // namespace spix
diff --git a/lib/src/TestServer.cpp b/lib/src/TestServer.cpp
index c2f9ec9..c2ccc84 100644
--- a/lib/src/TestServer.cpp
+++ b/lib/src/TestServer.cpp
@@ -24,6 +24,8 @@
#include
#include
+#include
+
namespace spix {
TestServer::~TestServer()
@@ -63,7 +65,12 @@ void TestServer::wait(std::chrono::milliseconds waitTime)
void TestServer::mouseClick(ItemPath path)
{
- m_cmdExec->enqueueCommand(path);
+ m_cmdExec->enqueueCommand(path, spix::MouseButtons::Left);
+}
+
+void TestServer::mouseClick(ItemPath path, MouseButton mouseButton)
+{
+ m_cmdExec->enqueueCommand(path, mouseButton);
}
void TestServer::mouseBeginDrag(ItemPath path)
diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt
index 98342be..1bc3c25 100644
--- a/lib/tests/CMakeLists.txt
+++ b/lib/tests/CMakeLists.txt
@@ -1,4 +1,5 @@
find_package(GTest REQUIRED)
+find_package(AnyRPC REQUIRED)
set(TEST_SOURCES
unittests_main.cpp
@@ -20,5 +21,5 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX source FILES ${TEST_SOURCES
add_executable(Spix_test ${TEST_SOURCES})
target_include_directories(Spix_test PRIVATE ../src)
-target_link_libraries(Spix_test Spix GTest::GTest)
+target_link_libraries(Spix_test Spix AnyRPC::anyrpc GTest::GTest)
add_test( NAME SpixUnitTests COMMAND Spix_test )
diff --git a/lib/tests/unittests/Commands/ClickOnItem_test.cpp b/lib/tests/unittests/Commands/ClickOnItem_test.cpp
index 6dfea45..cfe845d 100644
--- a/lib/tests/unittests/Commands/ClickOnItem_test.cpp
+++ b/lib/tests/unittests/Commands/ClickOnItem_test.cpp
@@ -9,11 +9,13 @@
#include
#include
#include
+#include
#include
TEST(ClickOnItemTest, ErrorOnMissingItem)
{
- auto command = std::make_unique(spix::ItemPosition("window/some/item"));
+ auto command
+ = std::make_unique(spix::ItemPosition("window/some/item"), spix::MouseButtons::Left);
spix::MockScene scene;
spix::CommandExecuter exec;
@@ -33,7 +35,8 @@ TEST(ClickOnItemTest, ClickItem)
bool didPostClickEvent = false;
bool mouseDown = false;
- auto command = std::make_unique(spix::ItemPosition("window/some/item"));
+ auto command
+ = std::make_unique(spix::ItemPosition("window/some/item"), spix::MouseButtons::Left);
spix::MockScene scene;
scene.addItemAtPath(spix::Size(100.0, 30.0), "window/some/item");