diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5681797cb76..ec32e6cc630 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -733,19 +733,31 @@ jobs: name: sunshine-macports path: artifacts/ - - name: Fix screen capture permissions - if: ${{ matrix.os_version != 12 }} # macOS-12 is okay - # can be removed if the following is fixed in the runner image - # https://github.com/actions/runner-images/issues/9529 - # https://github.com/actions/runner-images/pull/9530 + - name: Fix permissions run: | # https://apple.stackexchange.com/questions/362865/macos-list-apps-authorized-for-full-disk-access + # https://github.com/actions/runner-images/issues/9529 + # https://github.com/actions/runner-images/pull/9530 - # permissions for screen capture - values="'kTCCServiceScreenCapture','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" + # function to execute sql query for each value + function execute_sql_query { + local value=$1 + local dbPath=$2 + + echo "Executing SQL query for value: $value" + sudo sqlite3 "$dbPath" "INSERT OR IGNORE INTO access VALUES($value);" + } + + # permissions + declare -a values=( + "'kTCCServiceAccessibility','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552" + "'kTCCServiceScreenCapture','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" + ) if [[ "${{ matrix.os_version }}" == "14" ]]; then # TCC access table in Sonoma has extra 4 columns: pid, pid_version, boot_uuid, last_reminded - values="${values},NULL,NULL,'UNUSED',${values##*,}" + for i in "${!values[@]}"; do + values[$i]="${values[$i]},NULL,NULL,'UNUSED',${values[$i]##*,}" + done fi # system and user databases @@ -754,32 +766,31 @@ jobs: "$HOME/Library/Application Support/com.apple.TCC/TCC.db" ) - sqlQuery="INSERT OR IGNORE INTO access VALUES($values);" - - for dbPath in "${dbPaths[@]}"; do - echo "Column names for $dbPath" - echo "-------------------" - sudo sqlite3 "$dbPath" "PRAGMA table_info(access);" - echo "Current permissions for $dbPath" - echo "-------------------" - sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';" - sudo sqlite3 "$dbPath" "$sqlQuery" - echo "Updated permissions for $dbPath" - echo "-------------------" - sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';" + for value in "${values[@]}"; do + for dbPath in "${dbPaths[@]}"; do + echo "Column names for $dbPath" + echo "-------------------" + sudo sqlite3 "$dbPath" "PRAGMA table_info(access);" + echo "Current permissions for $dbPath" + echo "-------------------" + sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';" + execute_sql_query "$value" "$dbPath" + echo "Updated permissions for $dbPath" + echo "-------------------" + sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';" + done done - name: Run tests id: test timeout-minutes: 10 + working-directory: + /opt/local/var/macports/build/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/work/build/tests run: | - sudo port test "Sunshine" - - - name: Test Logs - if: always() - run: | - logfile="/opt/local/var/macports/logs/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/main.log" - cat "$logfile" + sudo port install \ + doxygen \ + graphviz + sudo ./test_sunshine --gtest_color=yes - name: Generate gcov report # any except canceled or skipped diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index e0cc9ef34f6..baf59b36be9 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -77,4 +77,4 @@ test.run yes test.dir ${build.dir}/tests test.target "" test.cmd ./test_sunshine -test.args --gtest_color=yes +test.args --gtest_color=yes --gtest_filter=-*HIDTest.*:-*DeathTest.* diff --git a/src/platform/common.h b/src/platform/common.h index 007f7ece61b..1960d829878 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -672,6 +672,18 @@ namespace platf { input_t input(); + /** + * @brief Gets the current mouse position on screen + * @param input The input_t instance to use. + * @return util::point_t (x, y) + * + * EXAMPLES: + * ```cpp + * auto [x, y] = get_mouse_loc(input); + * ``` + */ + util::point_t + get_mouse_loc(input_t &input); void move_mouse(input_t &input, int deltaX, int deltaY); void diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index 14ee50fe70b..4af7c568eee 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -1098,6 +1098,36 @@ namespace platf { #endif } + util::point_t + get_mouse_loc(input_t &input) { +#ifdef SUNSHINE_BUILD_X11 + Display *xdisplay = ((input_raw_t *) input.get())->display; + if (!xdisplay) { + return util::point_t {}; + } + Window root, root_return, child_return; + root = DefaultRootWindow(xdisplay); + int root_x, root_y; + int win_x, win_y; + unsigned int mask_return; + + if (XQueryPointer(xdisplay, root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return)) { + BOOST_LOG(debug) + << "Pointer is at:"sv << std::endl + << " x: " << root_x << std::endl + << " y: " << root_y << std::endl; + + return util::point_t { (double) root_x, (double) root_y }; + } + else { + BOOST_LOG(debug) << "Unable to query x11 pointer"sv << std::endl; + } +#else + BOOST_LOG(debug) << "Unable to query wayland pointer"sv << std::endl; +#endif + return util::point_t {}; + } + /** * @brief Absolute mouse move. * @param input The input_t instance to use. diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 11c6d228421..ec2822ad356 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -2,6 +2,8 @@ * @file src/platform/macos/input.cpp * @brief todo */ +#include "src/input.h" + #import #include #include @@ -10,6 +12,11 @@ #include "src/platform/common.h" #include "src/utility.h" +#include +#include +#include +#include + /** * @brief Delay for a double click, in milliseconds. * @todo Make this configurable. @@ -315,85 +322,108 @@ const KeyCodeMap kKeyCodesMap[] = { } // returns current mouse location: - inline CGPoint + util::point_t get_mouse_loc(input_t &input) { - return CGEventGetLocation(((macos_input_t *) input.get())->mouse_event); + // Creating a new event every time to avoid any reuse risk + const auto macos_input = static_cast(input.get()); + const auto snapshot_event = CGEventCreate(macos_input->source); + const auto current = CGEventGetLocation(snapshot_event); + CFRelease(snapshot_event); + return util::point_t { + current.x, + current.y + }; } void - post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) { - BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count; - - auto macos_input = (macos_input_t *) input.get(); - auto display = macos_input->display; - auto event = macos_input->mouse_event; + post_mouse( + input_t &input, + const CGMouseButton button, + const CGEventType type, + const util::point_t raw_location, + const util::point_t previous_location, + const int click_count) { + BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << raw_location.x << ":"sv << raw_location.y << " click_count: "sv << click_count; + + const auto macos_input = static_cast(input.get()); + const auto display = macos_input->display; + const auto event = macos_input->mouse_event; // get display bounds for current display - CGRect display_bounds = CGDisplayBounds(display); + const CGRect display_bounds = CGDisplayBounds(display); // limit mouse to current display bounds - location.x = std::clamp(location.x, display_bounds.origin.x, display_bounds.origin.x + display_bounds.size.width - 1); - location.y = std::clamp(location.y, display_bounds.origin.y, display_bounds.origin.y + display_bounds.size.height - 1); + const auto location = CGPoint { + std::clamp(raw_location.x, display_bounds.origin.x, display_bounds.origin.x + display_bounds.size.width - 1), + std::clamp(raw_location.y, display_bounds.origin.y, display_bounds.origin.y + display_bounds.size.height - 1) + }; CGEventSetType(event, type); CGEventSetLocation(event, location); CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button); CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count); - CGEventPost(kCGHIDEventTap, event); + // Include deltas so some 3D applications can consume changes (game cameras, etc) + const double deltaX = raw_location.x - previous_location.x; + const double deltaY = raw_location.y - previous_location.y; + CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX); + CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY); - // For why this is here, see: - // https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx - CGWarpMouseCursorPosition(location); + CGEventPost(kCGHIDEventTap, event); } inline CGEventType event_type_mouse(input_t &input) { - auto macos_input = ((macos_input_t *) input.get()); + const auto macos_input = static_cast(input.get()); if (macos_input->mouse_down[0]) { return kCGEventLeftMouseDragged; } - else if (macos_input->mouse_down[1]) { + if (macos_input->mouse_down[1]) { return kCGEventOtherMouseDragged; } - else if (macos_input->mouse_down[2]) { + if (macos_input->mouse_down[2]) { return kCGEventRightMouseDragged; } - else { - return kCGEventMouseMoved; - } + return kCGEventMouseMoved; } void - move_mouse(input_t &input, int deltaX, int deltaY) { - auto current = get_mouse_loc(input); - - CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY); - - post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0); + move_mouse( + input_t &input, + const int deltaX, + const int deltaY) { + const auto current = get_mouse_loc(input); + + const auto location = util::point_t { current.x + deltaX, current.y + deltaY }; + post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, current, 0); } void - abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { - auto macos_input = static_cast(input.get()); - auto scaling = macos_input->displayScaling; - auto display = macos_input->display; - - CGPoint location = CGPointMake(x * scaling, y * scaling); + abs_mouse( + input_t &input, + const touch_port_t &touch_port, + const float x, + const float y) { + const auto macos_input = static_cast(input.get()); + const auto scaling = macos_input->displayScaling; + const auto display = macos_input->display; + + auto location = util::point_t { x * scaling, y * scaling }; CGRect display_bounds = CGDisplayBounds(display); // in order to get the correct mouse location for capturing display , we need to add the display bounds to the location location.x += display_bounds.origin.x; location.y += display_bounds.origin.y; - post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0); + + post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, get_mouse_loc(input), 0); } void - button_mouse(input_t &input, int button, bool release) { + button_mouse(input_t &input, const int button, const bool release) { CGMouseButton mac_button; CGEventType event; - auto mouse = ((macos_input_t *) input.get()); + const auto macos_input = static_cast(input.get()); switch (button) { case 1: @@ -413,22 +443,24 @@ const KeyCodeMap kKeyCodesMap[] = { return; } - mouse->mouse_down[mac_button] = !release; + macos_input->mouse_down[mac_button] = !release; // if the last mouse down was less than MULTICLICK_DELAY_MS, we send a double click event - auto now = std::chrono::steady_clock::now(); - if (now < mouse->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) { - post_mouse(input, mac_button, event, get_mouse_loc(input), 2); + const auto now = std::chrono::steady_clock::now(); + const auto mouse_position = get_mouse_loc(input); + + if (now < macos_input->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) { + post_mouse(input, mac_button, event, mouse_position, mouse_position, 2); } else { - post_mouse(input, mac_button, event, get_mouse_loc(input), 1); + post_mouse(input, mac_button, event, mouse_position, mouse_position, 1); } - mouse->last_mouse_event[mac_button][release] = now; + macos_input->last_mouse_event[mac_button][release] = now; } void - scroll(input_t &input, int high_res_distance) { + scroll(input_t &input, const int high_res_distance) { CGEventRef upEvent = CGEventCreateScrollWheelEvent( nullptr, kCGScrollEventUnitLine, @@ -509,7 +541,7 @@ const KeyCodeMap kKeyCodesMap[] = { input() { input_t result { new macos_input_t() }; - auto macos_input = (macos_input_t *) result.get(); + const auto macos_input = static_cast(result.get()); // Default to main display macos_input->display = CGMainDisplayID(); @@ -534,7 +566,7 @@ const KeyCodeMap kKeyCodesMap[] = { } // Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor - CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display); + const CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display); macos_input->displayScaling = ((CGFloat) CGDisplayPixelsWide(macos_input->display)) / ((CGFloat) CGDisplayModeGetPixelWidth(mode)); CFRelease(mode); @@ -555,7 +587,7 @@ const KeyCodeMap kKeyCodesMap[] = { void freeInput(void *p) { - auto *input = (macos_input_t *) p; + const auto *input = static_cast(p); CFRelease(input->source); CFRelease(input->kb_event); diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index dfc9852f586..74aba6e09ef 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -542,6 +542,21 @@ namespace platf { send_input(i); } + util::point_t + get_mouse_loc(input_t &input) { + throw std::runtime_error("not implemented yet, has to pass tests"); + // TODO: Tests are failing, something wrong here? + POINT p; + if (!GetCursorPos(&p)) { + return util::point_t { 0.0, 0.0 }; + } + + return util::point_t { + (double) p.x, + (double) p.y + }; + } + void button_mouse(input_t &input, int button, bool release) { INPUT i {}; diff --git a/src/utility.h b/src/utility.h index 3ed321203c2..723986c24d8 100644 --- a/src/utility.h +++ b/src/utility.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -940,6 +941,16 @@ namespace util { return std::string_view((const char *) &data, sizeof(T)); } + struct point_t { + double x; + double y; + + friend std::ostream & + operator<<(std::ostream &os, const point_t &p) { + return (os << "Point(x: " << p.x << ", y: " << p.y << ")"); + } + }; + namespace endian { template struct endianness { diff --git a/tests/unit/test_mouse.cpp b/tests/unit/test_mouse.cpp new file mode 100644 index 00000000000..21a3dd428f9 --- /dev/null +++ b/tests/unit/test_mouse.cpp @@ -0,0 +1,122 @@ +/** + * @file tests/test_mouse.cpp + * @brief Test src/input.*. + */ +#include +#include + +#include + +class MouseHIDTest: public virtual BaseTest, public PlatformInitBase, public ::testing::WithParamInterface { +protected: + void + SetUp() override { + BaseTest::SetUp(); + PlatformInitBase::SetUp(); +#ifdef _WIN32 + // TODO: Windows tests are failing, `get_mouse_loc` seems broken and `platf::abs_mouse` too + // the alternative `platf::abs_mouse` method seem to work better during tests, + // but I'm not sure about real work + GTEST_SKIP_("MouseTest:: skipped for now. TODO Windows"); +#endif + } + + void + TearDown() override { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + PlatformInitBase::TearDown(); + BaseTest::TearDown(); + } +}; +INSTANTIATE_TEST_SUITE_P( + MouseInputs, + MouseHIDTest, + ::testing::Values( + util::point_t { 40, 40 }, + util::point_t { 70, 150 })); +// todo: add tests for hitting screen edges + +TEST_P(MouseHIDTest, MoveInputTest) { + util::point_t mouse_delta = GetParam(); + + std::cout << "MoveInputTest:: got param: " << mouse_delta << std::endl; + platf::input_t input = platf::input(); + std::cout << "MoveInputTest:: init input" << std::endl; + + std::cout << "MoveInputTest:: get current mouse loc" << std::endl; + auto old_loc = platf::get_mouse_loc(input); + std::cout << "MoveInputTest:: got current mouse loc: " << old_loc << std::endl; + + std::cout << "MoveInputTest:: move: " << mouse_delta << std::endl; + platf::move_mouse(input, mouse_delta.x, mouse_delta.y); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::cout << "MoveInputTest:: moved: " << mouse_delta << std::endl; + + std::cout << "MoveInputTest:: get updated mouse loc" << std::endl; + auto new_loc = platf::get_mouse_loc(input); + std::cout << "MoveInputTest:: got updated mouse loc: " << new_loc << std::endl; + + bool has_input_moved = old_loc.x != new_loc.x && old_loc.y != new_loc.y; + + if (!has_input_moved) { + std::cout << "MoveInputTest:: haven't moved" << std::endl; + } + else { + std::cout << "MoveInputTest:: moved" << std::endl; + } + + EXPECT_TRUE(has_input_moved); + + // Verify we moved as much as we requested + EXPECT_EQ(new_loc.x - old_loc.x, mouse_delta.x); + EXPECT_EQ(new_loc.y - old_loc.y, mouse_delta.y); +} + +TEST_P(MouseHIDTest, AbsMoveInputTest) { + util::point_t mouse_pos = GetParam(); + std::cout << "AbsMoveInputTest:: got param: " << mouse_pos << std::endl; + + platf::input_t input = platf::input(); + std::cout << "AbsMoveInputTest:: init input" << std::endl; + + std::cout << "AbsMoveInputTest:: get current mouse loc" << std::endl; + auto old_loc = platf::get_mouse_loc(input); + std::cout << "AbsMoveInputTest:: got current mouse loc: " << old_loc << std::endl; + +#ifdef _WIN32 + platf::touch_port_t abs_port { + 0, 0, + 65535, 65535 + }; +#elif __linux__ + platf::touch_port_t abs_port { + 0, 0, + 19200, 12000 + }; +#else + platf::touch_port_t abs_port {}; +#endif + std::cout << "AbsMoveInputTest:: move: " << mouse_pos << std::endl; + platf::abs_mouse(input, abs_port, mouse_pos.x, mouse_pos.y); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::cout << "AbsMoveInputTest:: moved: " << mouse_pos << std::endl; + + std::cout << "AbsMoveInputTest:: get updated mouse loc" << std::endl; + auto new_loc = platf::get_mouse_loc(input); + std::cout << "AbsMoveInputTest:: got updated mouse loc: " << new_loc << std::endl; + + bool has_input_moved = old_loc.x != new_loc.x || old_loc.y != new_loc.y; + + if (!has_input_moved) { + std::cout << "AbsMoveInputTest:: haven't moved" << std::endl; + } + else { + std::cout << "AbsMoveInputTest:: moved" << std::endl; + } + + EXPECT_TRUE(has_input_moved); + + // Verify we moved to the absolute coordinate + EXPECT_EQ(new_loc.x, mouse_pos.x); + EXPECT_EQ(new_loc.y, mouse_pos.y); +}