From 5887602d7123834cd1397178f3891d45c99f0291 Mon Sep 17 00:00:00 2001 From: Luca Stancapiano Date: Sat, 24 Oct 2026 00:30:14 +0200 Subject: [PATCH 1/3] fix(macos): stop mouse clicks from being dropped after a cursor warp The default 250ms local-event suppression discards the click and move that follow each warp, while the cursor image still moves. --- src/platform/macos/macos_backend.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/macos/macos_backend.cpp b/src/platform/macos/macos_backend.cpp index 8c027c9..b63e814 100644 --- a/src/platform/macos/macos_backend.cpp +++ b/src/platform/macos/macos_backend.cpp @@ -491,7 +491,13 @@ namespace lvh::detail { source {CGEventSourceCreate(kCGEventSourceStateHIDSystemState)}, keyboard_source {CGEventSourceCreate(kCGEventSourceStatePrivate)}, mouse_event {source ? CGEventCreate(source) : nullptr}, - scroll_lines_per_detent_value {read_scroll_lines_per_detent(scrollwheel_scaling)} {} + scroll_lines_per_detent_value {read_scroll_lines_per_detent(scrollwheel_scaling)} { + // Each move warps the cursor. The default 250ms suppression then drops the + // following click and mouse-moved events. The cursor image still moves. + if (source) { + CGEventSourceSetLocalEventsSuppressionInterval(source, 0); + } + } MacosInputState(const MacosInputState &) = delete; MacosInputState &operator=(const MacosInputState &) = delete; From b896d002208344693b4ba01dd0cc2ac847a18e78 Mon Sep 17 00:00:00 2001 From: Luca Stancapiano Date: Sat, 24 Oct 2026 11:30:37 +0200 Subject: [PATCH 2/3] fix(macos): deliver streamed mouse input without covering the desktop SDL games ignore events posted only to the process. Warp the logical cursor into the captured window, disconnect the visible Mac pointer, and keep the window behind the local user's work. --- src/platform/macos/macos_backend.cpp | 148 ++++++++++++++++-- .../fixtures/macos_backend_test_hooks.hpp | 4 +- tests/fixtures/macos_backend_test_hooks.cpp | 23 ++- tests/unit/test_macos_backend.cpp | 32 ++++ 4 files changed, 192 insertions(+), 15 deletions(-) diff --git a/src/platform/macos/macos_backend.cpp b/src/platform/macos/macos_backend.cpp index b63e814..8965976 100644 --- a/src/platform/macos/macos_backend.cpp +++ b/src/platform/macos/macos_backend.cpp @@ -6,6 +6,7 @@ // standard includes #include #include +#include #include #include #include @@ -525,6 +526,89 @@ namespace lvh::detail { std::mutex keyboard_mutex; ///< Guards shared keyboard modifier state. }; + std::atomic event_target_pid {0}; ///< Process that receives events without being activated. Zero keeps system-wide delivery. + std::atomic event_target_window {0}; ///< Window that should receive posted mouse events. Zero leaves hit testing unchanged. + std::mutex cursor_borrow_mutex; ///< Guards the borrowed system-cursor state used for targeted mouse delivery. + bool cursor_detached {false}; ///< Whether the on-screen cursor was disconnected from mouse motion. + CGPoint saved_cursor {}; ///< On-screen cursor position to restore when targeting ends. + + /** + * @brief Disconnect the visible Mac cursor so warps for a captured app do not drag it across the desktop. + */ + void borrow_cursor_for_target() { + std::lock_guard lock {cursor_borrow_mutex}; + if (cursor_detached) { + return; + } + const auto snapshot = CGEventCreate(nullptr); + if (snapshot != nullptr) { + saved_cursor = CGEventGetLocation(snapshot); + CFRelease(snapshot); + } + CGAssociateMouseAndMouseCursorPosition(false); + cursor_detached = true; + } + + /** + * @brief Put the visible Mac cursor back where the local user left it. + */ + void release_borrowed_cursor() { + std::lock_guard lock {cursor_borrow_mutex}; + if (!cursor_detached) { + return; + } + CGWarpMouseCursorPosition(saved_cursor); + CGAssociateMouseAndMouseCursorPosition(true); + cursor_detached = false; + } + + /** + * @brief Send later CoreGraphics events to one process without activating it. + * + * @param pid Process id, or zero to deliver events to whichever application is active. + */ + void set_event_target_pid(std::int32_t pid) { + const auto next = pid > 0 ? static_cast(pid) : 0; + const auto previous = event_target_pid.exchange(next, std::memory_order_relaxed); + if (previous > 0 && next == 0) { + release_borrowed_cursor(); + } + } + + /** + * @brief Tell later mouse events which window they belong to. + * + * @param window_id Platform window id, or zero to leave hit testing unchanged. + */ + void set_event_target_window(std::uint32_t window_id) { + event_target_window.store(window_id, std::memory_order_relaxed); + } + + /** + * @brief Process currently selected to receive events, or zero for system-wide delivery. + * + * @return Target process id. + */ + pid_t current_event_target_pid() { + return event_target_pid.load(std::memory_order_relaxed); + } + + /** + * @brief Post one event either to the selected process or to the whole session. + * + * @param tap Session tap used when no process is selected. + * @param event Event to post. + */ + void post_cg_event(CGEventTapLocation tap, CGEventRef event) { + const auto pid = current_event_target_pid(); + if (pid > 0) { + CGEventSetIntegerValueField(event, kCGEventTargetUnixProcessID, pid); + CGEventPostToPid(pid, event); + return; + } + CGEventPost(tap, event); + } + /** * @brief Build the CoreGraphics event for one key transition and update the shared modifier state. * @@ -536,7 +620,8 @@ namespace lvh::detail { * @return Event ready to post, or `nullptr` when CoreGraphics cannot create one. The caller releases it. */ inline CGEventRef create_keyboard_event(MacosInputState &state, CGKeyCode key, bool pressed) { - const auto keyboard_event = CGEventCreateKeyboardEvent(state.keyboard_source, key, pressed); + const auto keyboard_source = current_event_target_pid() > 0 ? state.source : state.keyboard_source; + const auto keyboard_event = CGEventCreateKeyboardEvent(keyboard_source, key, pressed); if (!keyboard_event) { return nullptr; } @@ -596,7 +681,7 @@ namespace lvh::detail { return OperationStatus::failure(backend_failure, "create macOS keyboard event"); } - CGEventPost(kCGSessionEventTap, keyboard_event); + post_cg_event(kCGSessionEventTap, keyboard_event); CFRelease(keyboard_event); return OperationStatus::success(); } @@ -631,12 +716,13 @@ namespace lvh::detail { CFStringGetCharacters(text, CFRangeMake(0, length), characters.data()); CFRelease(text); - const auto key_down = CGEventCreateKeyboardEvent(state_->keyboard_source, 0, true); + const auto keyboard_source = current_event_target_pid() > 0 ? state_->source : state_->keyboard_source; + const auto key_down = CGEventCreateKeyboardEvent(keyboard_source, 0, true); if (!key_down) { return OperationStatus::failure(backend_failure, "create macOS keyboard text key-down event"); } - const auto key_up = CGEventCreateKeyboardEvent(state_->keyboard_source, 0, false); + const auto key_up = CGEventCreateKeyboardEvent(keyboard_source, 0, false); if (!key_up) { CFRelease(key_down); return OperationStatus::failure(backend_failure, "create macOS keyboard text key-up event"); @@ -647,8 +733,8 @@ namespace lvh::detail { CGEventKeyboardSetUnicodeString(key_up, characters.size(), characters.data()); CGEventSetFlags(key_down, state_->keyboard_flags); CGEventSetFlags(key_up, state_->keyboard_flags); - CGEventPost(kCGSessionEventTap, key_down); - CGEventPost(kCGSessionEventTap, key_up); + post_cg_event(kCGSessionEventTap, key_down); + post_cg_event(kCGSessionEventTap, key_up); CFRelease(key_down); CFRelease(key_up); return OperationStatus::success(); @@ -809,19 +895,55 @@ namespace lvh::detail { CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count); CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, raw_location.x - previous_location.x); CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, raw_location.y - previous_location.y); + if (const auto window_id = event_target_window.load(std::memory_order_relaxed); window_id != 0) { + CGEventSetIntegerValueField(event, kCGMouseEventWindowUnderMousePointer, static_cast(window_id)); + } { std::lock_guard lock {state_->keyboard_mutex}; CGEventSetFlags(event, state_->keyboard_flags); } - CGEventPost(kCGHIDEventTap, event); - CGWarpMouseCursorPosition(location); + const auto pid = current_event_target_pid(); + if (pid > 0) { + // ScummVM and other SDL apps read the system cursor. Posting only to the process leaves their pointer stuck. + // Disconnect the visible Mac cursor, warp the logical pointer into the captured window, then post normally. + borrow_cursor_for_target(); + CGWarpMouseCursorPosition(location); + CGEventSetIntegerValueField(event, kCGEventTargetUnixProcessID, pid); + CGEventPost(kCGHIDEventTap, event); + injected_location_ = location; + injected_pid_ = pid; + } else { + post_cg_event(kCGHIDEventTap, event); + CGWarpMouseCursorPosition(location); + } return OperationStatus::success(); } + /** + * @brief Location used for the next relative move or click. + * + * A selected process keeps its own location, starting at the center of its window, so the Mac cursor can stay where the local user left it. + * + * @return Pointer location in screen points. + */ + CGPoint pointer_location() const { + const auto pid = current_event_target_pid(); + if (pid > 0) { + if (injected_location_ && injected_pid_ == pid) { + return *injected_location_; + } + return CGPoint { + viewport_bounds_.origin.x + viewport_bounds_.size.width / 2, + viewport_bounds_.origin.y + viewport_bounds_.size.height / 2, + }; + } + return current_location(); + } + OperationStatus submit_relative_motion(std::int32_t delta_x, std::int32_t delta_y) { - const auto current = current_location(); + const auto current = pointer_location(); const auto location = CGPoint {current.x + delta_x, current.y + delta_y}; const auto motion = macos_mouse_motion(mouse_down_); return post_mouse(motion.button, motion.event_type, location, current, 0); @@ -830,7 +952,7 @@ namespace lvh::detail { OperationStatus submit_absolute_motion(const MouseEvent &event) { const auto location = absolute_mouse_location(event, viewport_bounds_); const auto motion = macos_mouse_motion(mouse_down_); - return post_mouse(motion.button, motion.event_type, location, current_location(), 0); + return post_mouse(motion.button, motion.event_type, location, pointer_location(), 0); } OperationStatus submit_button(const MouseEvent &event) { @@ -844,7 +966,7 @@ namespace lvh::detail { mouse_down_[button->index] = event.pressed; const auto now = std::chrono::steady_clock::now(); const auto release_index = event.pressed ? 0U : 1U; - const auto mouse_position = current_location(); + const auto mouse_position = pointer_location(); const auto click_count = now < last_mouse_event_[button->index][release_index] + multi_click_delay ? 2 : 1; last_mouse_event_[button->index][release_index] = now; @@ -866,7 +988,7 @@ namespace lvh::detail { } CGEventSetIntegerValueField(event, kCGScrollWheelEventIsContinuous, 1); - CGEventPost(kCGHIDEventTap, event); + post_cg_event(kCGHIDEventTap, event); CFRelease(event); return OperationStatus::success(); } @@ -875,6 +997,8 @@ namespace lvh::detail { PointerViewport viewport_; ///< Requested screen geometry, with zero dimensions selecting the current main display. CGRect viewport_bounds_ {}; ///< CoreGraphics bounds resolved for the current mouse submission. std::array mouse_down_ {}; + mutable std::optional injected_location_; ///< Last location delivered to a selected process. + mutable pid_t injected_pid_ {0}; ///< Process that owns injected_location_. std::array, 3> last_mouse_event_ {}; std::mutex mutex_; bool open_ = true; diff --git a/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp b/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp index 0540fe4..9cef923 100644 --- a/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp +++ b/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp @@ -49,6 +49,7 @@ namespace lvh::detail::test { OperationStatus status; ///< Backend submission status. std::optional posted_location; ///< Location on the posted event, if any. std::optional warped_location; ///< Cursor warp destination, if any. + std::optional posted_pid; ///< Process that received the event, when delivery bypasses the active application. }; /** @@ -179,7 +180,8 @@ namespace lvh::detail::test { std::vector macos_backend_mouse_submissions( const PointerViewport &viewport, const MacosViewportBounds &initial_display_bounds, - const std::vector &submissions + const std::vector &submissions, + std::int32_t event_target_pid = 0 ); /** diff --git a/tests/fixtures/macos_backend_test_hooks.cpp b/tests/fixtures/macos_backend_test_hooks.cpp index aaf0169..23b5998 100644 --- a/tests/fixtures/macos_backend_test_hooks.cpp +++ b/tests/fixtures/macos_backend_test_hooks.cpp @@ -61,6 +61,7 @@ namespace { lvh::detail::test::MacosPoint cursor_location; std::optional posted_location; std::optional warped_location; + std::optional posted_pid; }; CGDirectDisplayID mouse_main_display_id() { @@ -99,6 +100,16 @@ namespace { return kCGErrorSuccess; } + void mouse_event_post_to_pid(pid_t pid, CGEventRef event) { + if (!mouse_environment()) { + CGEventPostToPid(pid, event); + return; + } + const auto point = CGEventGetLocation(event); + mouse_environment()->posted_location = {.x = point.x, .y = point.y}; + mouse_environment()->posted_pid = static_cast(pid); + } + } // namespace // Load the backend's dependencies before these macros, then compile it in a @@ -108,11 +119,13 @@ namespace { #define CGDisplayBounds mouse_display_bounds #define CGEventGetLocation mouse_event_location #define CGEventPost mouse_event_post +#define CGEventPostToPid mouse_event_post_to_pid #define CGWarpMouseCursorPosition mouse_warp_cursor_position #define create_platform_backend create_platform_backend_for_macos_backend_test_hooks #include "../../src/platform/macos/macos_backend.cpp" #undef create_platform_backend #undef CGWarpMouseCursorPosition +#undef CGEventPostToPid #undef CGEventPost #undef CGEventGetLocation #undef CGDisplayBounds @@ -217,8 +230,13 @@ namespace lvh::detail::test { std::vector macos_backend_mouse_submissions( const PointerViewport &viewport, const MacosViewportBounds &initial_display_bounds, - const std::vector &submissions + const std::vector &submissions, + std::int32_t event_target_pid ) { + macos::set_event_target_pid(event_target_pid); + const auto clear_target = std::shared_ptr {nullptr, [](void *) { + macos::set_event_target_pid(0); + }}; MouseEnvironment environment {initial_display_bounds}; auto backend = create_platform_backend_for_macos_backend_test_hooks(); CreateMouseOptions options; @@ -235,8 +253,9 @@ namespace lvh::detail::test { environment.cursor_location = submission.cursor_location; environment.posted_location.reset(); environment.warped_location.reset(); + environment.posted_pid.reset(); const auto status = created.mouse->submit(submission.event); - results.emplace_back(status, environment.posted_location, environment.warped_location); + results.emplace_back(status, environment.posted_location, environment.warped_location, environment.posted_pid); } return results; } diff --git a/tests/unit/test_macos_backend.cpp b/tests/unit/test_macos_backend.cpp index 908b799..ed2d873 100644 --- a/tests/unit/test_macos_backend.cpp +++ b/tests/unit/test_macos_backend.cpp @@ -382,3 +382,35 @@ TEST_F(MacosBackendTest, ReportsCapabilitiesAndUnsupportedDevices) { EXPECT_EQ(result.trackpad_status.code(), lvh::ErrorCode::unsupported_profile); EXPECT_EQ(result.pen_tablet_status.code(), lvh::ErrorCode::unsupported_profile); } + +TEST_F(MacosBackendTest, PostsMouseToTargetProcessByWarpingTheLogicalCursor) { + const lvh::PointerViewport viewport {.offset_x = 100, .offset_y = 200, .width = 640, .height = 480}; + const lvh::MouseEvent move { + .kind = lvh::MouseEventKind::absolute_motion, + .x = 50, + .y = 50, + .width = 100, + .height = 100, + }; + const lvh::MouseEvent click { + .kind = lvh::MouseEventKind::button, + .button = lvh::MouseButton::left, + .pressed = true, + }; + const std::vector submissions { + {.display_bounds = {0, 0, 1920, 1080}, .cursor_location = {10, 10}, .event = move}, + {.display_bounds = {0, 0, 1920, 1080}, .cursor_location = {10, 10}, .event = click}, + }; + const auto results = lvh::detail::test::macos_backend_mouse_submissions(viewport, {0, 0, 1920, 1080}, submissions, 4242); + + ASSERT_EQ(results.size(), 2U); + for (const auto &result : results) { + ASSERT_TRUE(result.status.ok()) << result.status.message(); + ASSERT_TRUE(result.posted_location); + ASSERT_TRUE(result.warped_location); + EXPECT_DOUBLE_EQ(result.posted_location->x, 420); + EXPECT_DOUBLE_EQ(result.posted_location->y, 440); + EXPECT_DOUBLE_EQ(result.warped_location->x, 420); + EXPECT_DOUBLE_EQ(result.warped_location->y, 440); + } +} From 499620375ae232ea5c2fe3e0380302a3da4c26cd Mon Sep 17 00:00:00 2001 From: Luca Stancapiano Date: Sat, 24 Oct 2026 11:36:43 +0200 Subject: [PATCH 3/3] Revert "fix(macos): deliver streamed mouse input without covering the desktop" This reverts commit b896d002208344693b4ba01dd0cc2ac847a18e78. --- src/platform/macos/macos_backend.cpp | 148 ++---------------- .../fixtures/macos_backend_test_hooks.hpp | 4 +- tests/fixtures/macos_backend_test_hooks.cpp | 23 +-- tests/unit/test_macos_backend.cpp | 32 ---- 4 files changed, 15 insertions(+), 192 deletions(-) diff --git a/src/platform/macos/macos_backend.cpp b/src/platform/macos/macos_backend.cpp index 8965976..b63e814 100644 --- a/src/platform/macos/macos_backend.cpp +++ b/src/platform/macos/macos_backend.cpp @@ -6,7 +6,6 @@ // standard includes #include #include -#include #include #include #include @@ -526,89 +525,6 @@ namespace lvh::detail { std::mutex keyboard_mutex; ///< Guards shared keyboard modifier state. }; - std::atomic event_target_pid {0}; ///< Process that receives events without being activated. Zero keeps system-wide delivery. - std::atomic event_target_window {0}; ///< Window that should receive posted mouse events. Zero leaves hit testing unchanged. - std::mutex cursor_borrow_mutex; ///< Guards the borrowed system-cursor state used for targeted mouse delivery. - bool cursor_detached {false}; ///< Whether the on-screen cursor was disconnected from mouse motion. - CGPoint saved_cursor {}; ///< On-screen cursor position to restore when targeting ends. - - /** - * @brief Disconnect the visible Mac cursor so warps for a captured app do not drag it across the desktop. - */ - void borrow_cursor_for_target() { - std::lock_guard lock {cursor_borrow_mutex}; - if (cursor_detached) { - return; - } - const auto snapshot = CGEventCreate(nullptr); - if (snapshot != nullptr) { - saved_cursor = CGEventGetLocation(snapshot); - CFRelease(snapshot); - } - CGAssociateMouseAndMouseCursorPosition(false); - cursor_detached = true; - } - - /** - * @brief Put the visible Mac cursor back where the local user left it. - */ - void release_borrowed_cursor() { - std::lock_guard lock {cursor_borrow_mutex}; - if (!cursor_detached) { - return; - } - CGWarpMouseCursorPosition(saved_cursor); - CGAssociateMouseAndMouseCursorPosition(true); - cursor_detached = false; - } - - /** - * @brief Send later CoreGraphics events to one process without activating it. - * - * @param pid Process id, or zero to deliver events to whichever application is active. - */ - void set_event_target_pid(std::int32_t pid) { - const auto next = pid > 0 ? static_cast(pid) : 0; - const auto previous = event_target_pid.exchange(next, std::memory_order_relaxed); - if (previous > 0 && next == 0) { - release_borrowed_cursor(); - } - } - - /** - * @brief Tell later mouse events which window they belong to. - * - * @param window_id Platform window id, or zero to leave hit testing unchanged. - */ - void set_event_target_window(std::uint32_t window_id) { - event_target_window.store(window_id, std::memory_order_relaxed); - } - - /** - * @brief Process currently selected to receive events, or zero for system-wide delivery. - * - * @return Target process id. - */ - pid_t current_event_target_pid() { - return event_target_pid.load(std::memory_order_relaxed); - } - - /** - * @brief Post one event either to the selected process or to the whole session. - * - * @param tap Session tap used when no process is selected. - * @param event Event to post. - */ - void post_cg_event(CGEventTapLocation tap, CGEventRef event) { - const auto pid = current_event_target_pid(); - if (pid > 0) { - CGEventSetIntegerValueField(event, kCGEventTargetUnixProcessID, pid); - CGEventPostToPid(pid, event); - return; - } - CGEventPost(tap, event); - } - /** * @brief Build the CoreGraphics event for one key transition and update the shared modifier state. * @@ -620,8 +536,7 @@ namespace lvh::detail { * @return Event ready to post, or `nullptr` when CoreGraphics cannot create one. The caller releases it. */ inline CGEventRef create_keyboard_event(MacosInputState &state, CGKeyCode key, bool pressed) { - const auto keyboard_source = current_event_target_pid() > 0 ? state.source : state.keyboard_source; - const auto keyboard_event = CGEventCreateKeyboardEvent(keyboard_source, key, pressed); + const auto keyboard_event = CGEventCreateKeyboardEvent(state.keyboard_source, key, pressed); if (!keyboard_event) { return nullptr; } @@ -681,7 +596,7 @@ namespace lvh::detail { return OperationStatus::failure(backend_failure, "create macOS keyboard event"); } - post_cg_event(kCGSessionEventTap, keyboard_event); + CGEventPost(kCGSessionEventTap, keyboard_event); CFRelease(keyboard_event); return OperationStatus::success(); } @@ -716,13 +631,12 @@ namespace lvh::detail { CFStringGetCharacters(text, CFRangeMake(0, length), characters.data()); CFRelease(text); - const auto keyboard_source = current_event_target_pid() > 0 ? state_->source : state_->keyboard_source; - const auto key_down = CGEventCreateKeyboardEvent(keyboard_source, 0, true); + const auto key_down = CGEventCreateKeyboardEvent(state_->keyboard_source, 0, true); if (!key_down) { return OperationStatus::failure(backend_failure, "create macOS keyboard text key-down event"); } - const auto key_up = CGEventCreateKeyboardEvent(keyboard_source, 0, false); + const auto key_up = CGEventCreateKeyboardEvent(state_->keyboard_source, 0, false); if (!key_up) { CFRelease(key_down); return OperationStatus::failure(backend_failure, "create macOS keyboard text key-up event"); @@ -733,8 +647,8 @@ namespace lvh::detail { CGEventKeyboardSetUnicodeString(key_up, characters.size(), characters.data()); CGEventSetFlags(key_down, state_->keyboard_flags); CGEventSetFlags(key_up, state_->keyboard_flags); - post_cg_event(kCGSessionEventTap, key_down); - post_cg_event(kCGSessionEventTap, key_up); + CGEventPost(kCGSessionEventTap, key_down); + CGEventPost(kCGSessionEventTap, key_up); CFRelease(key_down); CFRelease(key_up); return OperationStatus::success(); @@ -895,55 +809,19 @@ namespace lvh::detail { CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count); CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, raw_location.x - previous_location.x); CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, raw_location.y - previous_location.y); - if (const auto window_id = event_target_window.load(std::memory_order_relaxed); window_id != 0) { - CGEventSetIntegerValueField(event, kCGMouseEventWindowUnderMousePointer, static_cast(window_id)); - } { std::lock_guard lock {state_->keyboard_mutex}; CGEventSetFlags(event, state_->keyboard_flags); } - const auto pid = current_event_target_pid(); - if (pid > 0) { - // ScummVM and other SDL apps read the system cursor. Posting only to the process leaves their pointer stuck. - // Disconnect the visible Mac cursor, warp the logical pointer into the captured window, then post normally. - borrow_cursor_for_target(); - CGWarpMouseCursorPosition(location); - CGEventSetIntegerValueField(event, kCGEventTargetUnixProcessID, pid); - CGEventPost(kCGHIDEventTap, event); - injected_location_ = location; - injected_pid_ = pid; - } else { - post_cg_event(kCGHIDEventTap, event); - CGWarpMouseCursorPosition(location); - } + CGEventPost(kCGHIDEventTap, event); + CGWarpMouseCursorPosition(location); return OperationStatus::success(); } - /** - * @brief Location used for the next relative move or click. - * - * A selected process keeps its own location, starting at the center of its window, so the Mac cursor can stay where the local user left it. - * - * @return Pointer location in screen points. - */ - CGPoint pointer_location() const { - const auto pid = current_event_target_pid(); - if (pid > 0) { - if (injected_location_ && injected_pid_ == pid) { - return *injected_location_; - } - return CGPoint { - viewport_bounds_.origin.x + viewport_bounds_.size.width / 2, - viewport_bounds_.origin.y + viewport_bounds_.size.height / 2, - }; - } - return current_location(); - } - OperationStatus submit_relative_motion(std::int32_t delta_x, std::int32_t delta_y) { - const auto current = pointer_location(); + const auto current = current_location(); const auto location = CGPoint {current.x + delta_x, current.y + delta_y}; const auto motion = macos_mouse_motion(mouse_down_); return post_mouse(motion.button, motion.event_type, location, current, 0); @@ -952,7 +830,7 @@ namespace lvh::detail { OperationStatus submit_absolute_motion(const MouseEvent &event) { const auto location = absolute_mouse_location(event, viewport_bounds_); const auto motion = macos_mouse_motion(mouse_down_); - return post_mouse(motion.button, motion.event_type, location, pointer_location(), 0); + return post_mouse(motion.button, motion.event_type, location, current_location(), 0); } OperationStatus submit_button(const MouseEvent &event) { @@ -966,7 +844,7 @@ namespace lvh::detail { mouse_down_[button->index] = event.pressed; const auto now = std::chrono::steady_clock::now(); const auto release_index = event.pressed ? 0U : 1U; - const auto mouse_position = pointer_location(); + const auto mouse_position = current_location(); const auto click_count = now < last_mouse_event_[button->index][release_index] + multi_click_delay ? 2 : 1; last_mouse_event_[button->index][release_index] = now; @@ -988,7 +866,7 @@ namespace lvh::detail { } CGEventSetIntegerValueField(event, kCGScrollWheelEventIsContinuous, 1); - post_cg_event(kCGHIDEventTap, event); + CGEventPost(kCGHIDEventTap, event); CFRelease(event); return OperationStatus::success(); } @@ -997,8 +875,6 @@ namespace lvh::detail { PointerViewport viewport_; ///< Requested screen geometry, with zero dimensions selecting the current main display. CGRect viewport_bounds_ {}; ///< CoreGraphics bounds resolved for the current mouse submission. std::array mouse_down_ {}; - mutable std::optional injected_location_; ///< Last location delivered to a selected process. - mutable pid_t injected_pid_ {0}; ///< Process that owns injected_location_. std::array, 3> last_mouse_event_ {}; std::mutex mutex_; bool open_ = true; diff --git a/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp b/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp index 9cef923..0540fe4 100644 --- a/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp +++ b/tests/fixtures/include/fixtures/macos_backend_test_hooks.hpp @@ -49,7 +49,6 @@ namespace lvh::detail::test { OperationStatus status; ///< Backend submission status. std::optional posted_location; ///< Location on the posted event, if any. std::optional warped_location; ///< Cursor warp destination, if any. - std::optional posted_pid; ///< Process that received the event, when delivery bypasses the active application. }; /** @@ -180,8 +179,7 @@ namespace lvh::detail::test { std::vector macos_backend_mouse_submissions( const PointerViewport &viewport, const MacosViewportBounds &initial_display_bounds, - const std::vector &submissions, - std::int32_t event_target_pid = 0 + const std::vector &submissions ); /** diff --git a/tests/fixtures/macos_backend_test_hooks.cpp b/tests/fixtures/macos_backend_test_hooks.cpp index 23b5998..aaf0169 100644 --- a/tests/fixtures/macos_backend_test_hooks.cpp +++ b/tests/fixtures/macos_backend_test_hooks.cpp @@ -61,7 +61,6 @@ namespace { lvh::detail::test::MacosPoint cursor_location; std::optional posted_location; std::optional warped_location; - std::optional posted_pid; }; CGDirectDisplayID mouse_main_display_id() { @@ -100,16 +99,6 @@ namespace { return kCGErrorSuccess; } - void mouse_event_post_to_pid(pid_t pid, CGEventRef event) { - if (!mouse_environment()) { - CGEventPostToPid(pid, event); - return; - } - const auto point = CGEventGetLocation(event); - mouse_environment()->posted_location = {.x = point.x, .y = point.y}; - mouse_environment()->posted_pid = static_cast(pid); - } - } // namespace // Load the backend's dependencies before these macros, then compile it in a @@ -119,13 +108,11 @@ namespace { #define CGDisplayBounds mouse_display_bounds #define CGEventGetLocation mouse_event_location #define CGEventPost mouse_event_post -#define CGEventPostToPid mouse_event_post_to_pid #define CGWarpMouseCursorPosition mouse_warp_cursor_position #define create_platform_backend create_platform_backend_for_macos_backend_test_hooks #include "../../src/platform/macos/macos_backend.cpp" #undef create_platform_backend #undef CGWarpMouseCursorPosition -#undef CGEventPostToPid #undef CGEventPost #undef CGEventGetLocation #undef CGDisplayBounds @@ -230,13 +217,8 @@ namespace lvh::detail::test { std::vector macos_backend_mouse_submissions( const PointerViewport &viewport, const MacosViewportBounds &initial_display_bounds, - const std::vector &submissions, - std::int32_t event_target_pid + const std::vector &submissions ) { - macos::set_event_target_pid(event_target_pid); - const auto clear_target = std::shared_ptr {nullptr, [](void *) { - macos::set_event_target_pid(0); - }}; MouseEnvironment environment {initial_display_bounds}; auto backend = create_platform_backend_for_macos_backend_test_hooks(); CreateMouseOptions options; @@ -253,9 +235,8 @@ namespace lvh::detail::test { environment.cursor_location = submission.cursor_location; environment.posted_location.reset(); environment.warped_location.reset(); - environment.posted_pid.reset(); const auto status = created.mouse->submit(submission.event); - results.emplace_back(status, environment.posted_location, environment.warped_location, environment.posted_pid); + results.emplace_back(status, environment.posted_location, environment.warped_location); } return results; } diff --git a/tests/unit/test_macos_backend.cpp b/tests/unit/test_macos_backend.cpp index ed2d873..908b799 100644 --- a/tests/unit/test_macos_backend.cpp +++ b/tests/unit/test_macos_backend.cpp @@ -382,35 +382,3 @@ TEST_F(MacosBackendTest, ReportsCapabilitiesAndUnsupportedDevices) { EXPECT_EQ(result.trackpad_status.code(), lvh::ErrorCode::unsupported_profile); EXPECT_EQ(result.pen_tablet_status.code(), lvh::ErrorCode::unsupported_profile); } - -TEST_F(MacosBackendTest, PostsMouseToTargetProcessByWarpingTheLogicalCursor) { - const lvh::PointerViewport viewport {.offset_x = 100, .offset_y = 200, .width = 640, .height = 480}; - const lvh::MouseEvent move { - .kind = lvh::MouseEventKind::absolute_motion, - .x = 50, - .y = 50, - .width = 100, - .height = 100, - }; - const lvh::MouseEvent click { - .kind = lvh::MouseEventKind::button, - .button = lvh::MouseButton::left, - .pressed = true, - }; - const std::vector submissions { - {.display_bounds = {0, 0, 1920, 1080}, .cursor_location = {10, 10}, .event = move}, - {.display_bounds = {0, 0, 1920, 1080}, .cursor_location = {10, 10}, .event = click}, - }; - const auto results = lvh::detail::test::macos_backend_mouse_submissions(viewport, {0, 0, 1920, 1080}, submissions, 4242); - - ASSERT_EQ(results.size(), 2U); - for (const auto &result : results) { - ASSERT_TRUE(result.status.ok()) << result.status.message(); - ASSERT_TRUE(result.posted_location); - ASSERT_TRUE(result.warped_location); - EXPECT_DOUBLE_EQ(result.posted_location->x, 420); - EXPECT_DOUBLE_EQ(result.posted_location->y, 440); - EXPECT_DOUBLE_EQ(result.warped_location->x, 420); - EXPECT_DOUBLE_EQ(result.warped_location->y, 440); - } -}