From 43712b7c8056b3fa397c6c583fbc635c168908c4 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:23:27 +0200 Subject: [PATCH 01/11] fix(core): image.cpp initialize vars before using them --- libs/core/src/image.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/core/src/image.cpp b/libs/core/src/image.cpp index 095afbc3..026b38ad 100644 --- a/libs/core/src/image.cpp +++ b/libs/core/src/image.cpp @@ -41,7 +41,9 @@ Image::Image(std::vector image_data, std::uint32_t width, std::uint32 Image::Image(const std::filesystem::path &filename) { - int w, h, channels; + int w{}; + int h{}; + int channels{}; auto *data = stbi_load(filename.c_str(), &w, &h, &channels, 0); if (data != nullptr) { From ed33ed9b267ce32358e8c33eadbe185c9f645cad Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:04:12 +0200 Subject: [PATCH 02/11] build(deps): update to latest stdexec --- CMakeLists.txt | 2 +- CMakePresets.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d3a3ec5c..5f1c26bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ cpmaddpackage( cpmaddpackage("gh:fmtlib/fmt#11.2.0") cpmaddpackage("gh:odygrd/quill@10.1.0") cpmaddpackage(URI "gh:skypjack/entt@3.15.0" NAME "EnTT" FIND_PACKAGE_ARGUMENTS "CONFIG") -cpmaddpackage(URI "gh:NVIDIA/stdexec#daf12bcc46438f88b3c78212363f7b6531338780" NAME "stdexec" VERSION 0.11 FIND_PACKAGE_ARGUMENTS "CONFIG" OPTIONS "STDEXEC_BUILD_TESTS OFF" "STDEXEC_BUILD_EXAMPLES OFF" "STDEXEC_ENABLE_ASIO ON" +cpmaddpackage(URI "gh:NVIDIA/stdexec#770f7959142c2446319f642c82aed8b07aea3f86" NAME "stdexec" VERSION 0.11 FIND_PACKAGE_ARGUMENTS "CONFIG" OPTIONS "STDEXEC_BUILD_TESTS OFF" "STDEXEC_BUILD_EXAMPLES OFF" "STDEXEC_ENABLE_ASIO ON" "CMAKE_SKIP_INSTALL_RULES ${CMAKE_SKIP_INSTALL_RULES}" ) cpmaddpackage(URI "gh:Tradias/asio-grpc@3.5.0" FIND_PACKAGE_ARGUMENTS "CONFIG" VERSION 3.4.0) cpmaddpackage(URI "gh:nlohmann/json@3.12.0" FIND_PACKAGE_ARGUMENTS "CONFIG" NAME "nlohmann_json") diff --git a/CMakePresets.json b/CMakePresets.json index 8c5fcf94..003c58e0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -53,8 +53,8 @@ "debug-build" ], "cacheVariables": { + "QUITE_INSTALL": false, "BUILD_TESTING": true, - "CMAKE_SKIP_INSTALL_RULES": true, "SANITIZE_ADDRESS": false, "SANITIZE_UNDEFINED": false } From ab6b94f6d01aa0044f81ba7704161b9c5cb7cbb5 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:04:36 +0200 Subject: [PATCH 03/11] fix(probeqt): adapt qt scheduler to latest stdexec concepts --- libs/probeqt/impl/qtstdexec.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libs/probeqt/impl/qtstdexec.hpp b/libs/probeqt/impl/qtstdexec.hpp index e553cb4c..f08ec3f3 100644 --- a/libs/probeqt/impl/qtstdexec.hpp +++ b/libs/probeqt/impl/qtstdexec.hpp @@ -21,6 +21,8 @@ class QThreadOperationState; class QThreadScheduler { public: + using scheduler_concept = stdexec::scheduler_t; + explicit QThreadScheduler(QThread *thread) : thread_(thread) {} @@ -34,7 +36,7 @@ class QThreadScheduler { QThread *thread; template - auto query(stdexec::get_completion_scheduler_t) const noexcept + auto query(stdexec::get_completion_scheduler_t) const noexcept -> QThreadScheduler { return QThreadScheduler{thread}; } @@ -53,7 +55,7 @@ class QThreadScheduler class QThreadSender { public: - using is_sender = void; + using sender_concept = stdexec::sender_t; using completion_signatures = stdexec::completion_signatures; @@ -66,15 +68,15 @@ class QThreadScheduler return thread_; } - DefaultEnv query(stdexec::get_env_t) const noexcept + auto get_env() const noexcept -> DefaultEnv { return {thread_}; } - template - QThreadOperationState connect(Recv &&receiver) + template + QThreadOperationState connect(Receiver receiver) { - return QThreadOperationState(std::forward(receiver), thread()); + return QThreadOperationState(std::move(receiver), thread()); } private: @@ -137,13 +139,13 @@ class QObjectSender } }; - DefaultEnv query([[maybe_unused]] stdexec::get_env_t env) noexcept + auto get_env() const noexcept -> DefaultEnv { return {obj_->thread()}; } public: - using is_sender = void; + using sender_concept = stdexec::sender_t; using completion_signatures = stdexec::completion_signatures; From e175d0457e5f3799d63100ef253b17f63c82fa3e Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:10:48 +0200 Subject: [PATCH 04/11] build(presets): don't install on ci build --- CMakePresets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 003c58e0..30275680 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -96,13 +96,13 @@ "common" ], "cacheVariables": { + "QUITE_INSTALL": false, "CMAKE_BUILD_TYPE": "RelWithDebInfo", "BUILD_TESTING": true, "CMAKE_C_COMPILER": "gcc-13", "CMAKE_CXX_COMPILER": "g++-13", "CMAKE_C_COMPILER_LAUNCHER": "ccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", - "CMAKE_SKIP_INSTALL_RULES": true + "CMAKE_CXX_COMPILER_LAUNCHER": "ccache" }, "environment": { "WAYLAND_DISPLAY": "wl-test-env" From 77c052e67f667fe8fcf5b70b6c20a546e37747f2 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:42:57 +0200 Subject: [PATCH 05/11] feat(core): move mouse and keyboard into own headers in core --- libs/core/CMakeLists.txt | 2 ++ .../quite/injectors/mouse_injector.hpp | 3 +- libs/core/include/quite/keyboard.hpp | 16 +++++++++ .../quite/{injectors/keys.hpp => mouse.hpp} | 13 ++------ libs/testing/include/quite/test/mouse.hpp | 33 +++++++++++++++++++ 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 libs/core/include/quite/keyboard.hpp rename libs/core/include/quite/{injectors/keys.hpp => mouse.hpp} (69%) create mode 100644 libs/testing/include/quite/test/mouse.hpp diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index 303f3edb..55c94f31 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -42,6 +42,8 @@ target_sources( TYPE HEADERS BASE_DIRS include FILES + include/quite/mouse.hpp + include/quite/keyboard.hpp include/quite/asio_context.hpp include/quite/async_result.hpp include/quite/disable_copy_move.hpp diff --git a/libs/core/include/quite/injectors/mouse_injector.hpp b/libs/core/include/quite/injectors/mouse_injector.hpp index 446420b4..5ef62f39 100644 --- a/libs/core/include/quite/injectors/mouse_injector.hpp +++ b/libs/core/include/quite/injectors/mouse_injector.hpp @@ -3,9 +3,10 @@ // SPDX-License-Identifier: MIT #pragma once -#include "keys.hpp" #include "quite/async_result.hpp" #include "quite/geometry.hpp" +#include "quite/keyboard.hpp" +#include "quite/mouse.hpp" #include "quite/quite_core_export.hpp" #include "quite/value/object_id.hpp" diff --git a/libs/core/include/quite/keyboard.hpp b/libs/core/include/quite/keyboard.hpp new file mode 100644 index 00000000..27d2cfbc --- /dev/null +++ b/libs/core/include/quite/keyboard.hpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#pragma once +namespace quite +{ +enum class KeyboardModifier +{ + none, + shift, + control, + alt, + meta, +}; +} // namespace quite diff --git a/libs/core/include/quite/injectors/keys.hpp b/libs/core/include/quite/mouse.hpp similarity index 69% rename from libs/core/include/quite/injectors/keys.hpp rename to libs/core/include/quite/mouse.hpp index ffed281d..b4c9826c 100644 --- a/libs/core/include/quite/injectors/keys.hpp +++ b/libs/core/include/quite/mouse.hpp @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT #pragma once -namespace quite::core +namespace quite { enum class MouseTrigger { @@ -24,13 +24,4 @@ enum class MouseButton forward, back, }; - -enum class KeyboardModifier -{ - none, - shift, - control, - alt, - meta, -}; -} // namespace quite::core +} // namespace quite diff --git a/libs/testing/include/quite/test/mouse.hpp b/libs/testing/include/quite/test/mouse.hpp new file mode 100644 index 00000000..c43571b7 --- /dev/null +++ b/libs/testing/include/quite/test/mouse.hpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#pragma once +#include +#include +#include +namespace quite::test +{ +class RemoteObject; + +class MouseDragBuilder +{ + MouseDragBuilder move_to(RemoteObject object); + MouseDragBuilder drop_at(RemoteObject object); +}; + +class MouseBuilder +{ + MouseBuilder up(MouseButton button); + MouseBuilder down(MouseButton button); + MouseBuilder modifier(KeyboardModifier modifier); + MouseDragBuilder drag(MouseButton button = MouseButton::left); + MouseBuilder click(MouseButton button = MouseButton::left, + std::chrono::milliseconds delay = std::chrono::milliseconds{0}); + MouseBuilder double_click(MouseButton button = MouseButton::left, + std::chrono::milliseconds delay = std::chrono::milliseconds{0}); + MouseBuilder wheel(int delta_x, int delta_y); +}; + +MouseBuilder mouse(RemoteObject object); +} // namespace quite::test From 92beefa28d751987b3ff16eaacd35dfccd8bda11 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:28:13 +0200 Subject: [PATCH 06/11] build(deps): update pre-commit --- .pre-commit-config.yaml | 12 ++++++------ python/test/test_probe.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5938409..f1b51652 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: stages: [commit-msg] args: [--no-color] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -19,23 +19,23 @@ repos: - id: check-added-large-files - repo: https://github.com/BlankSpruce/gersemi - rev: 0.21.0 + rev: 0.22.3 hooks: - id: gersemi name: CMake linting - repo: https://github.com/psf/black - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v20.1.8 + rev: v21.1.2 hooks: - id: clang-format types_or: [c++, c, proto] # Check linting and style issues - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.12.5" + rev: "v0.14.0" hooks: # Run the linter. - id: ruff-check @@ -51,7 +51,7 @@ repos: exclude: ^(pyproject.toml) - repo: https://github.com/fsfe/reuse-tool - rev: v5.0.2 + rev: v6.1.2 hooks: - id: reuse-lint-file diff --git a/python/test/test_probe.py b/python/test/test_probe.py index 005dfcb5..7d91c6bd 100644 --- a/python/test/test_probe.py +++ b/python/test/test_probe.py @@ -104,3 +104,24 @@ def test_expect_screenshot(probe_manager: ProbeManager, hello_btn_query): ) btn = app.find_object(hello_btn_query) expect(object=btn).screenshot(name="test1") + + +# +# def test_mouse_sketch(probe_manager: ProbeManager, hello_btn_query): +# app = probe_manager.launch_qt_probe_application( +# name="tester", path_to_application=APP_PATH +# ) +# btn = app.find_object(hello_btn_query) +# +# mouse(btn).modifier(KeyboardModifier.ctrl).click() +# mouse(btn).click() +# mouse(btn).click(button=MouseButton.Left, delay=100ms) +# mouse(btn).double_click(button=MouseButton.Left, delay=100ms) +# mouse(btn).down(button) +# mouse(btn).up(button) +# +# mouse(btn).drag().drop_at(drag_target) +# +# mouse(btn).drag(button=MouseButton.Left).move_to(some_other_target).drop_at(drag_target) +# +# mouse(btn).wheel(delta_x, delty_y) From dddcb6e20d17b49a6c16fe17e1ff436ac073f77a Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:37:26 +0200 Subject: [PATCH 07/11] fix(client): fix mouse type namespace adjustments --- libs/client/src/grpc_impl/grpc_remote_object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/client/src/grpc_impl/grpc_remote_object.cpp b/libs/client/src/grpc_impl/grpc_remote_object.cpp index 35178789..ea3e70b1 100644 --- a/libs/client/src/grpc_impl/grpc_remote_object.cpp +++ b/libs/client/src/grpc_impl/grpc_remote_object.cpp @@ -76,7 +76,7 @@ AsyncResult GrpcRemoteObject::mouse_action() { LOG_DEBUG(grpc_remote_object_logger(), "mouse_action for object={}", id()); co_return co_await client_->mouse_injector().single_action( - id(), core::MouseAction{.button = core::MouseButton::left, .trigger = core::MouseTrigger::click}); + id(), core::MouseAction{.button = MouseButton::left, .trigger = MouseTrigger::click}); } AsyncResult GrpcRemoteObject::take_snapshot() From 657ecb177037412bc4daf7198bfb76a25a32978d Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:42:33 +0200 Subject: [PATCH 08/11] fix: rename remaining mouse/keyboard namespaces --- libs/probeqt/impl/injector/mouse_injector.cpp | 12 +++---- .../src/probe/rpc_mouse_injection.cpp | 34 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/libs/probeqt/impl/injector/mouse_injector.cpp b/libs/probeqt/impl/injector/mouse_injector.cpp index 355e1011..5663a20c 100644 --- a/libs/probeqt/impl/injector/mouse_injector.cpp +++ b/libs/probeqt/impl/injector/mouse_injector.cpp @@ -31,9 +31,9 @@ AsyncResult MouseInjector::single_action(ObjectId target_id, core::MouseAc std::unique_ptr event; switch (action.trigger) { - case core::MouseTrigger::none: + case MouseTrigger::none: break; - case core::MouseTrigger::click: + case MouseTrigger::click: dispatch_mouse_event(target.value(), std::make_unique(QMouseEvent::Type::MouseButtonPress, QPointF{action.position.x, action.position.y}, @@ -51,7 +51,7 @@ AsyncResult MouseInjector::single_action(ObjectId target_id, core::MouseAc Qt::KeyboardModifiers{}, &mouse_)); break; - case core::MouseTrigger::double_click: + case MouseTrigger::double_click: dispatch_mouse_event(target.value(), std::make_unique(QMouseEvent::Type::MouseButtonDblClick, QPointF{action.position.x, action.position.y}, @@ -61,7 +61,7 @@ AsyncResult MouseInjector::single_action(ObjectId target_id, core::MouseAc Qt::KeyboardModifiers{}, &mouse_)); break; - case core::MouseTrigger::press: + case MouseTrigger::press: dispatch_mouse_event(target.value(), std::make_unique(QMouseEvent::Type::MouseButtonPress, QPointF{action.position.x, action.position.y}, @@ -71,7 +71,7 @@ AsyncResult MouseInjector::single_action(ObjectId target_id, core::MouseAc Qt::KeyboardModifiers{}, &mouse_)); break; - case core::MouseTrigger::release: + case MouseTrigger::release: dispatch_mouse_event(target.value(), std::make_unique(QMouseEvent::Type::MouseButtonRelease, QPointF{action.position.x, action.position.y}, @@ -81,7 +81,7 @@ AsyncResult MouseInjector::single_action(ObjectId target_id, core::MouseAc Qt::KeyboardModifiers{}, &mouse_)); break; - case core::MouseTrigger::move: + case MouseTrigger::move: dispatch_mouse_event(target.value(), std::make_unique(QMouseEvent::Type::MouseMove, QPointF{action.position.x, action.position.y}, diff --git a/libs/protocol/src/probe/rpc_mouse_injection.cpp b/libs/protocol/src/probe/rpc_mouse_injection.cpp index b22b4946..4743163c 100644 --- a/libs/protocol/src/probe/rpc_mouse_injection.cpp +++ b/libs/protocol/src/probe/rpc_mouse_injection.cpp @@ -22,59 +22,59 @@ quite::core::MouseAction mouse_action_from_request(const quite::proto::MouseActi switch (btn) { case quite::proto::left_button: - return quite::core::MouseButton::left; + return quite::MouseButton::left; case quite::proto::right_button: - return quite::core::MouseButton::right; + return quite::MouseButton::right; case quite::proto::middle_button: - return quite::core::MouseButton::middle; + return quite::MouseButton::middle; case quite::proto::MouseButton_INT_MIN_SENTINEL_DO_NOT_USE_: case quite::proto::MouseButton_INT_MAX_SENTINEL_DO_NOT_USE_: break; } - return quite::core::MouseButton::none; + return quite::MouseButton::none; }(), .trigger = [trigger = request.mouse_action()]() { switch (trigger) { case quite::proto::none: - return quite::core::MouseTrigger::none; + return quite::MouseTrigger::none; case quite::proto::click: - return quite::core::MouseTrigger::click; + return quite::MouseTrigger::click; case quite::proto::double_click: - return quite::core::MouseTrigger::double_click; + return quite::MouseTrigger::double_click; case quite::proto::press: - return quite::core::MouseTrigger::press; + return quite::MouseTrigger::press; case quite::proto::release: - return quite::core::MouseTrigger::release; + return quite::MouseTrigger::release; case quite::proto::move: - return quite::core::MouseTrigger::move; + return quite::MouseTrigger::move; case quite::proto::MouseAction_INT_MIN_SENTINEL_DO_NOT_USE_: case quite::proto::MouseAction_INT_MAX_SENTINEL_DO_NOT_USE_: break; } - return quite::core::MouseTrigger::none; + return quite::MouseTrigger::none; }(), .modifier = [modifier = request.modifier_key()]() { switch (modifier) { case quite::proto::no_mod: - return quite::core::KeyboardModifier::none; + return quite::KeyboardModifier::none; case quite::proto::shift: - return quite::core::KeyboardModifier::shift; + return quite::KeyboardModifier::shift; case quite::proto::crtl: - return quite::core::KeyboardModifier::control; + return quite::KeyboardModifier::control; case quite::proto::alt: - return quite::core::KeyboardModifier::alt; + return quite::KeyboardModifier::alt; case quite::proto::meta: - return quite::core::KeyboardModifier::meta; + return quite::KeyboardModifier::meta; case quite::proto::keypad: case quite::proto::KeyboardModifierKey_INT_MIN_SENTINEL_DO_NOT_USE_: case quite::proto::KeyboardModifierKey_INT_MAX_SENTINEL_DO_NOT_USE_: break; } - return quite::core::KeyboardModifier::none; + return quite::KeyboardModifier::none; }(), }; } From de9303f2803f108711bf41970c241e2d975e69ff Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:53:41 +0200 Subject: [PATCH 09/11] build(testing): remove explicit target include dir --- libs/testing/CMakeLists.txt | 28 +++++++++++++---------- libs/testing/include/quite/test/mouse.hpp | 3 ++- libs/testing/src/CMakeLists.txt | 18 --------------- libs/testing/src/mouse.cpp | 12 ++++++++++ 4 files changed, 30 insertions(+), 31 deletions(-) delete mode 100644 libs/testing/src/CMakeLists.txt create mode 100644 libs/testing/src/mouse.cpp diff --git a/libs/testing/CMakeLists.txt b/libs/testing/CMakeLists.txt index 4e483575..a35b4f28 100644 --- a/libs/testing/CMakeLists.txt +++ b/libs/testing/CMakeLists.txt @@ -21,24 +21,28 @@ target_sources( TYPE HEADERS BASE_DIRS include FILES + include/quite/test/exceptions.hpp + include/quite/test/expect.hpp + include/quite/test/mouse.hpp include/quite/test/probe_manager.hpp include/quite/test/probe.hpp - include/quite/test/exceptions.hpp include/quite/test/property.hpp include/quite/test/remote_object.hpp FILE_SET export_config TYPE HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR} FILES ${CMAKE_CURRENT_BINARY_DIR}/quite/quite_test_export.hpp + PRIVATE + src/exceptions.cpp + src/expect_screenshot.cpp + src/expect_screenshot.hpp + src/expect.cpp + src/mouse.cpp + src/probe_manager.cpp + src/probe.cpp + src/property.cpp + src/remote_object.cpp + src/throw_unexpected.hpp + src/value_convert.cpp + src/value_convert.hpp ) - -target_include_directories( - quite_test - PUBLIC - "$" - "$" - "$" - PRIVATE "$" -) - -add_subdirectory(src) diff --git a/libs/testing/include/quite/test/mouse.hpp b/libs/testing/include/quite/test/mouse.hpp index c43571b7..a2d103aa 100644 --- a/libs/testing/include/quite/test/mouse.hpp +++ b/libs/testing/include/quite/test/mouse.hpp @@ -6,6 +6,7 @@ #include #include #include +#include "quite/quite_test_export.hpp" namespace quite::test { class RemoteObject; @@ -29,5 +30,5 @@ class MouseBuilder MouseBuilder wheel(int delta_x, int delta_y); }; -MouseBuilder mouse(RemoteObject object); +[[nodiscard]] QUITE_TEST_EXPORT MouseBuilder mouse(RemoteObject object); } // namespace quite::test diff --git a/libs/testing/src/CMakeLists.txt b/libs/testing/src/CMakeLists.txt deleted file mode 100644 index cf4b11a3..00000000 --- a/libs/testing/src/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Mathis Logemann -# -# SPDX-License-Identifier: MIT - -target_sources( - quite_test - PRIVATE - probe_manager.cpp - probe.cpp - exceptions.cpp - property.cpp - remote_object.cpp - expect_screenshot.cpp - expect_screenshot.hpp - expect.cpp - value_convert.cpp - value_convert.hpp -) diff --git a/libs/testing/src/mouse.cpp b/libs/testing/src/mouse.cpp new file mode 100644 index 00000000..4dc13cc6 --- /dev/null +++ b/libs/testing/src/mouse.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#include "quite/test/mouse.hpp" +#include "quite/test/remote_object.hpp" + +namespace quite::test +{ +MouseBuilder mouse(RemoteObject object) +{} +} // namespace quite::test From 3f05a14b81207a864e4507f72f5529c00264f7a5 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:49:03 +0200 Subject: [PATCH 10/11] feat(core): add BitFlags --- libs/core/CMakeLists.txt | 3 + libs/core/include/quite/core/bit.hpp | 15 ++ libs/core/include/quite/core/bit_flags.hpp | 156 ++++++++++++++++ .../core/include/quite/core/bit_flags_fmt.hpp | 34 ++++ libs/core/include/quite/keyboard.hpp | 12 +- libs/core/test/CMakeLists.txt | 2 +- libs/core/test/test_bit_flags.cpp | 172 ++++++++++++++++++ 7 files changed, 388 insertions(+), 6 deletions(-) create mode 100644 libs/core/include/quite/core/bit.hpp create mode 100644 libs/core/include/quite/core/bit_flags.hpp create mode 100644 libs/core/include/quite/core/bit_flags_fmt.hpp create mode 100644 libs/core/test/test_bit_flags.cpp diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index 55c94f31..8fea20c9 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -63,6 +63,9 @@ target_sources( include/quite/value/generic_value_class.hpp include/quite/value/object_query.hpp include/quite/injectors/mouse_injector.hpp + include/quite/core/bit.hpp + include/quite/core/bit_flags.hpp + include/quite/core/bit_flags_fmt.hpp FILE_SET export_config TYPE HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR} diff --git a/libs/core/include/quite/core/bit.hpp b/libs/core/include/quite/core/bit.hpp new file mode 100644 index 00000000..f1528023 --- /dev/null +++ b/libs/core/include/quite/core/bit.hpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#pragma once +#include + +namespace quite +{ +template +constexpr T bit(std::size_t position) +{ + return static_cast(1) << position; +} +} // namespace quite diff --git a/libs/core/include/quite/core/bit_flags.hpp b/libs/core/include/quite/core/bit_flags.hpp new file mode 100644 index 00000000..19b017dd --- /dev/null +++ b/libs/core/include/quite/core/bit_flags.hpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#pragma once +#include +#include +namespace quite +{ +template +class BitFlags +{ + using underlying_t = std::underlying_type_t; + + public: + constexpr BitFlags() + : flags_(static_cast(0)) + {} + + constexpr explicit BitFlags(T v) + : flags_(to_underlying(v)) + {} + + constexpr BitFlags(std::initializer_list vs) + : BitFlags() + { + for (T v : vs) + { + flags_ |= to_underlying(v); + } + } + + constexpr bool is_set(T v) const + { + return (flags_ & to_underlying(v)) == to_underlying(v); + } + + constexpr void set(T v) + { + flags_ |= to_underlying(v); + } + + constexpr void unset(T v) + { + flags_ &= ~to_underlying(v); + } + + constexpr void clear() + { + flags_ = static_cast(0); + } + + constexpr operator bool() const + { + return flags_ != static_cast(0); + } + + friend constexpr BitFlags operator|(BitFlags lhs, T rhs) + { + return BitFlags(lhs.flags_ | to_underlying(rhs)); + } + + friend constexpr BitFlags operator|(BitFlags lhs, BitFlags rhs) + { + return BitFlags(lhs.flags_ | rhs.flags_); + } + + friend constexpr BitFlags operator&(BitFlags lhs, T rhs) + { + return BitFlags(lhs.flags_ & to_underlying(rhs)); + } + + friend constexpr BitFlags operator&(BitFlags lhs, BitFlags rhs) + { + return BitFlags(lhs.flags_ & rhs.flags_); + } + + friend constexpr BitFlags operator^(BitFlags lhs, T rhs) + { + return BitFlags(lhs.flags_ ^ to_underlying(rhs)); + } + + friend constexpr BitFlags operator^(BitFlags lhs, BitFlags rhs) + { + return BitFlags(lhs.flags_ ^ rhs.flags_); + } + + friend constexpr BitFlags &operator|=(BitFlags &lhs, T rhs) + { + lhs.flags_ |= to_underlying(rhs); + return lhs; + } + friend constexpr BitFlags &operator|=(BitFlags &lhs, BitFlags rhs) + { + lhs.flags_ |= rhs.flags_; + return lhs; + } + friend constexpr BitFlags &operator&=(BitFlags &lhs, T rhs) + { + lhs.flags_ &= to_underlying(rhs); + return lhs; + } + friend constexpr BitFlags &operator&=(BitFlags &lhs, BitFlags rhs) + { + lhs.flags_ &= rhs.flags_; + return lhs; + } + friend constexpr BitFlags &operator^=(BitFlags &lhs, T rhs) + { + lhs.flags_ ^= to_underlying(rhs); + return lhs; + } + friend constexpr BitFlags &operator^=(BitFlags &lhs, BitFlags rhs) + { + lhs.flags_ ^= rhs.flags_; + return lhs; + } + + friend constexpr BitFlags operator~(const BitFlags &bf) + { + return BitFlags(~bf.flags_); + } + + friend constexpr bool operator==(const BitFlags &lhs, const BitFlags &rhs) + { + return lhs.flags_ == rhs.flags_; + } + + friend constexpr bool operator!=(const BitFlags &lhs, const BitFlags &rhs) + { + return lhs.flags_ != rhs.flags_; + } + + // Construct BitFlags from raw values. + static constexpr BitFlags from_raw(underlying_t flags) + { + return BitFlags(flags); + } + + // Retrieve the raw underlying flags. + constexpr underlying_t to_raw() const + { + return flags_; + } + + private: + constexpr explicit BitFlags(underlying_t flags) + : flags_(flags) + {} + static constexpr underlying_t to_underlying(T v) + { + return static_cast(v); + } + underlying_t flags_; +}; +} // namespace quite diff --git a/libs/core/include/quite/core/bit_flags_fmt.hpp b/libs/core/include/quite/core/bit_flags_fmt.hpp new file mode 100644 index 00000000..f1c36744 --- /dev/null +++ b/libs/core/include/quite/core/bit_flags_fmt.hpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#pragma once +#include +#include "quite/core/bit_flags.hpp" + +template +struct std::formatter> +{ + constexpr auto parse(std::format_parse_context &ctx) + { + return ctx.begin(); + } + + auto format(const quite::BitFlags &bf, std::format_context &ctx) const + { + using underlying_t = std::underlying_type_t; + constexpr size_t kNumBits = sizeof(underlying_t) * 8; + auto val = bf.to_raw(); + + // Format as binary string + std::string binary; + binary.reserve(kNumBits); + for (size_t i = 0; i < kNumBits; ++i) + { + binary = ((val & 1) ? '1' : '0') + binary; + val >>= 1; + } + + return std::format_to(ctx.out(), "{}", binary); + } +}; diff --git a/libs/core/include/quite/keyboard.hpp b/libs/core/include/quite/keyboard.hpp index 27d2cfbc..80285bde 100644 --- a/libs/core/include/quite/keyboard.hpp +++ b/libs/core/include/quite/keyboard.hpp @@ -3,14 +3,16 @@ // SPDX-License-Identifier: MIT #pragma once +#include "quite/core/bit.hpp" + namespace quite { enum class KeyboardModifier { - none, - shift, - control, - alt, - meta, + none = 0, + shift = bit(0), + control = bit(1), + alt = bit(2), + meta = bit(3), }; } // namespace quite diff --git a/libs/core/test/CMakeLists.txt b/libs/core/test/CMakeLists.txt index 5aebc073..de12cea4 100644 --- a/libs/core/test/CMakeLists.txt +++ b/libs/core/test/CMakeLists.txt @@ -7,4 +7,4 @@ add_sanitizers(core_test) target_link_libraries(core_test PRIVATE quite::core Boost::ut) add_test(NAME core_test COMMAND core_test) -target_sources(core_test PRIVATE test_error.cpp) +target_sources(core_test PRIVATE test_error.cpp test_bit_flags.cpp) diff --git a/libs/core/test/test_bit_flags.cpp b/libs/core/test/test_bit_flags.cpp new file mode 100644 index 00000000..94f521db --- /dev/null +++ b/libs/core/test/test_bit_flags.cpp @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2025 +// SPDX-FileCopyrightText: 2025 Mathis Logemann +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +using namespace boost::ut; +using namespace quite; +using namespace std::literals::string_view_literals; + +enum class TestFlags : uint8_t +{ + none = 0, + read = bit(0), + write = bit(1), + execute = bit(2), + admin = bit(3), +}; + +static suite<"BitFlags"> _ = [] { + "default_constructor"_test = [] { + BitFlags flags; + expect(!flags) << "default constructed flags should be false"; + expect(that % flags.to_raw() == 0U); + }; + + "single_flag_constructor"_test = [] { + BitFlags flags(TestFlags::read); + expect(flags) << "flags with value should be true"; + expect(flags.is_set(TestFlags::read)); + expect(!flags.is_set(TestFlags::write)); + }; + + "initializer_list_constructor"_test = [] { + BitFlags flags{TestFlags::read, TestFlags::write}; + expect(flags.is_set(TestFlags::read)); + expect(flags.is_set(TestFlags::write)); + expect(!flags.is_set(TestFlags::execute)); + }; + + "set_and_unset"_test = [] { + BitFlags flags; + flags.set(TestFlags::read); + expect(flags.is_set(TestFlags::read)); + + flags.set(TestFlags::write); + expect(flags.is_set(TestFlags::read)); + expect(flags.is_set(TestFlags::write)); + + flags.unset(TestFlags::read); + expect(!flags.is_set(TestFlags::read)); + expect(flags.is_set(TestFlags::write)); + }; + + "clear"_test = [] { + BitFlags flags{TestFlags::read, TestFlags::write, TestFlags::execute}; + expect(flags); + + flags.clear(); + expect(!flags); + expect(!flags.is_set(TestFlags::read)); + expect(!flags.is_set(TestFlags::write)); + expect(!flags.is_set(TestFlags::execute)); + }; + + "or_operator"_test = [] { + BitFlags flags1(TestFlags::read); + BitFlags flags2(TestFlags::write); + + auto result = flags1 | flags2; + expect(result.is_set(TestFlags::read)); + expect(result.is_set(TestFlags::write)); + + auto result2 = flags1 | TestFlags::execute; + expect(result2.is_set(TestFlags::read)); + expect(result2.is_set(TestFlags::execute)); + }; + + "and_operator"_test = [] { + BitFlags flags1{TestFlags::read, TestFlags::write}; + BitFlags flags2{TestFlags::write, TestFlags::execute}; + + auto result = flags1 & flags2; + expect(!result.is_set(TestFlags::read)); + expect(result.is_set(TestFlags::write)); + expect(!result.is_set(TestFlags::execute)); + }; + + "xor_operator"_test = [] { + BitFlags flags1{TestFlags::read, TestFlags::write}; + BitFlags flags2{TestFlags::write, TestFlags::execute}; + + auto result = flags1 ^ flags2; + expect(result.is_set(TestFlags::read)); + expect(!result.is_set(TestFlags::write)); + expect(result.is_set(TestFlags::execute)); + }; + + "compound_assignment_operators"_test = [] { + BitFlags flags(TestFlags::read); + + flags |= TestFlags::write; + expect(flags.is_set(TestFlags::read)); + expect(flags.is_set(TestFlags::write)); + + flags &= TestFlags::read; + expect(flags.is_set(TestFlags::read)); + expect(!flags.is_set(TestFlags::write)); + + flags |= TestFlags::execute; + flags ^= TestFlags::read; + expect(!flags.is_set(TestFlags::read)); + expect(flags.is_set(TestFlags::execute)); + }; + + "not_operator"_test = [] { + BitFlags flags(TestFlags::read); + auto inverted = ~flags; + + expect(!inverted.is_set(TestFlags::read)); + expect(inverted.is_set(TestFlags::write)); + expect(inverted.is_set(TestFlags::execute)); + expect(inverted.is_set(TestFlags::admin)); + }; + + "equality_operators"_test = [] { + BitFlags flags1{TestFlags::read, TestFlags::write}; + BitFlags flags2{TestFlags::read, TestFlags::write}; + BitFlags flags3(TestFlags::read); + + expect(flags1 == flags2); + expect(flags1 != flags3); + }; + + "from_raw_to_raw"_test = [] { + constexpr uint8_t raw_value = 0b0101; + auto flags = BitFlags::from_raw(raw_value); + + expect(that % flags.to_raw() == raw_value); + expect(flags.is_set(TestFlags::read)); + expect(!flags.is_set(TestFlags::write)); + expect(flags.is_set(TestFlags::execute)); + }; + + "formatting"_test = [] { + BitFlags flags{TestFlags::read, TestFlags::execute}; + auto formatted = std::format("{}", flags); + + expect(that % formatted == "00000101"sv) << "should format as binary string"; + }; + + "bool_conversion"_test = [] { + BitFlags empty_flags; + BitFlags set_flags(TestFlags::read); + + expect(!static_cast(empty_flags)); + expect(static_cast(set_flags)); + + if (set_flags) + { + expect(true); + } + else + { + expect(false) << "should be truthy when flags are set"; + } + }; +}; From af279812cf2b5c789d8d4e930cc4a8fcf97759e2 Mon Sep 17 00:00:00 2001 From: Mathis Logemann <13556116+mathisloge@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:30:18 +0200 Subject: [PATCH 11/11] feat(testing): mouse extensions --- libs/testing/include/quite/test/mouse.hpp | 30 ++++++++++++++--- libs/testing/src/mouse.cpp | 39 ++++++++++++++++++++++- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/libs/testing/include/quite/test/mouse.hpp b/libs/testing/include/quite/test/mouse.hpp index a2d103aa..cf98d551 100644 --- a/libs/testing/include/quite/test/mouse.hpp +++ b/libs/testing/include/quite/test/mouse.hpp @@ -4,21 +4,33 @@ #pragma once #include +#include #include #include #include "quite/quite_test_export.hpp" +#include "quite/test/remote_object.hpp" + namespace quite::test { -class RemoteObject; - -class MouseDragBuilder +class QUITE_TEST_EXPORT MouseDragBuilder { MouseDragBuilder move_to(RemoteObject object); MouseDragBuilder drop_at(RemoteObject object); + + private: + RemoteObject source_; }; -class MouseBuilder +class QUITE_TEST_EXPORT MouseBuilder { + public: + MouseBuilder() = delete; + MouseBuilder(const MouseBuilder &other) = default; + MouseBuilder(MouseBuilder &&other) noexcept = default; + MouseBuilder &operator=(const MouseBuilder &other) = default; + MouseBuilder &operator=(MouseBuilder &&other) = default; + ~MouseBuilder() = default; + MouseBuilder up(MouseButton button); MouseBuilder down(MouseButton button); MouseBuilder modifier(KeyboardModifier modifier); @@ -28,7 +40,15 @@ class MouseBuilder MouseBuilder double_click(MouseButton button = MouseButton::left, std::chrono::milliseconds delay = std::chrono::milliseconds{0}); MouseBuilder wheel(int delta_x, int delta_y); + + private: + MouseBuilder(RemoteObject target); + friend MouseBuilder mouse(RemoteObject target); + + private: + RemoteObject target_; + BitFlags modifiers_; }; -[[nodiscard]] QUITE_TEST_EXPORT MouseBuilder mouse(RemoteObject object); +[[nodiscard]] QUITE_TEST_EXPORT MouseBuilder mouse(RemoteObject target); } // namespace quite::test diff --git a/libs/testing/src/mouse.cpp b/libs/testing/src/mouse.cpp index 4dc13cc6..1b210773 100644 --- a/libs/testing/src/mouse.cpp +++ b/libs/testing/src/mouse.cpp @@ -7,6 +7,43 @@ namespace quite::test { -MouseBuilder mouse(RemoteObject object) + +MouseBuilder::MouseBuilder(RemoteObject target) + : target_{std::move(target)} +{} + +MouseDragBuilder MouseDragBuilder::move_to(RemoteObject object) {} + +MouseDragBuilder MouseDragBuilder::drop_at(RemoteObject object) +{} + +MouseBuilder MouseBuilder::up(MouseButton button) +{} + +MouseBuilder MouseBuilder::down(MouseButton button) +{} + +MouseBuilder MouseBuilder::modifier(KeyboardModifier modifier) +{ + modifiers_.set(modifier); + return *this; +} + +MouseDragBuilder MouseBuilder::drag(MouseButton button) +{} + +MouseBuilder MouseBuilder::click(MouseButton button, std::chrono::milliseconds delay) +{} + +MouseBuilder MouseBuilder::double_click(MouseButton button, std::chrono::milliseconds delay) +{} + +MouseBuilder MouseBuilder::wheel(int delta_x, int delta_y) +{} + +MouseBuilder mouse(RemoteObject target) +{ + return MouseBuilder{std::move(target)}; +} } // namespace quite::test