From fea238722da95d1477855e38108d861ccafcbc29 Mon Sep 17 00:00:00 2001 From: kunkundi Date: Fri, 5 Jun 2026 00:25:08 +0800 Subject: [PATCH] [fix] fix macOS remote double-click and triple-click handling, refs #86 --- .../mouse/mac/mouse_controller.cpp | 86 ++++++++++++++++--- .../mouse/mac/mouse_controller.h | 18 +++- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/device_controller/mouse/mac/mouse_controller.cpp b/src/device_controller/mouse/mac/mouse_controller.cpp index ddbf635..5575604 100644 --- a/src/device_controller/mouse/mac/mouse_controller.cpp +++ b/src/device_controller/mouse/mac/mouse_controller.cpp @@ -1,11 +1,36 @@ #include "mouse_controller.h" #include + #include +#include +#include #include "rd_log.h" namespace crossdesk { +namespace { + +constexpr auto kDoubleClickInterval = std::chrono::milliseconds(500); +constexpr int kDoubleClickMaxDistance = 8; +constexpr int kMaxClickState = 3; + +bool IsWithinClickDistance(int x1, int y1, int x2, int y2) { + return std::abs(x1 - x2) <= kDoubleClickMaxDistance && + std::abs(y1 - y2) <= kDoubleClickMaxDistance; +} + +void SetClickState(CGEventRef event, int click_state) { + if (!event) { + return; + } + + CGEventSetIntegerValueField( + event, kCGMouseEventClickState, + std::max(1, std::min(click_state, kMaxClickState))); +} + +} // namespace MouseController::MouseController() {} @@ -19,6 +44,36 @@ int MouseController::Init(std::vector display_info_list) { int MouseController::Destroy() { return 0; } +int MouseController::BeginClick(ClickTracker& tracker, int x, int y) { + const auto now = std::chrono::steady_clock::now(); + const bool continues_previous_click = + tracker.has_last_down && + now - tracker.last_down_time <= kDoubleClickInterval && + IsWithinClickDistance(tracker.last_down_x, tracker.last_down_y, x, y); + + tracker.click_state = continues_previous_click + ? std::min(tracker.click_state + 1, kMaxClickState) + : 1; + tracker.active_click_state = tracker.click_state; + tracker.has_last_down = true; + tracker.last_down_time = now; + tracker.last_down_x = x; + tracker.last_down_y = y; + + return tracker.active_click_state; +} + +int MouseController::EndClick(ClickTracker& tracker, int x, int y) { + const int click_state = tracker.active_click_state; + if (!IsWithinClickDistance(tracker.last_down_x, tracker.last_down_y, x, y)) { + tracker.has_last_down = false; + tracker.click_state = 0; + tracker.active_click_state = 1; + } + + return click_state; +} + int MouseController::SendMouseCommand(RemoteAction remote_action, int display_index) { if (remote_action.type != ControlType::mouse) { @@ -41,58 +96,69 @@ int MouseController::SendMouseCommand(RemoteAction remote_action, const float normalized_x = std::clamp(remote_action.m.x, 0.0f, 1.0f); const float normalized_y = std::clamp(remote_action.m.y, 0.0f, 1.0f); - int mouse_pos_x = - normalized_x * display_info.width + display_info.left; - int mouse_pos_y = - normalized_y * display_info.height + display_info.top; + int mouse_pos_x = normalized_x * display_info.width + display_info.left; + int mouse_pos_y = normalized_y * display_info.height + display_info.top; CGEventRef mouse_event = nullptr; CGEventType mouse_type; CGMouseButton mouse_button; CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y); + int click_state = 1; switch (remote_action.m.flag) { case MouseFlag::left_down: mouse_type = kCGEventLeftMouseDown; left_dragging_ = true; + click_state = BeginClick(left_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonLeft); + SetClickState(mouse_event, click_state); break; case MouseFlag::left_up: mouse_type = kCGEventLeftMouseUp; left_dragging_ = false; + click_state = EndClick(left_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonLeft); + SetClickState(mouse_event, click_state); break; case MouseFlag::right_down: mouse_type = kCGEventRightMouseDown; right_dragging_ = true; + click_state = BeginClick(right_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonRight); + SetClickState(mouse_event, click_state); break; case MouseFlag::right_up: mouse_type = kCGEventRightMouseUp; right_dragging_ = false; + click_state = EndClick(right_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonRight); + SetClickState(mouse_event, click_state); break; case MouseFlag::middle_down: mouse_type = kCGEventOtherMouseDown; + click_state = BeginClick(middle_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonCenter); + SetClickState(mouse_event, click_state); break; case MouseFlag::middle_up: mouse_type = kCGEventOtherMouseUp; + click_state = EndClick(middle_click_tracker_, mouse_pos_x, mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, kCGMouseButtonCenter); + SetClickState(mouse_event, click_state); break; case MouseFlag::wheel_vertical: - mouse_event = CGEventCreateScrollWheelEvent( - NULL, kCGScrollEventUnitLine, 2, remote_action.m.s, 0); + mouse_event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, + 2, remote_action.m.s, 0); break; case MouseFlag::wheel_horizontal: - mouse_event = CGEventCreateScrollWheelEvent( - NULL, kCGScrollEventUnitLine, 2, 0, remote_action.m.s); + mouse_event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, + 2, 0, remote_action.m.s); break; default: if (left_dragging_) { @@ -106,8 +172,8 @@ int MouseController::SendMouseCommand(RemoteAction remote_action, mouse_button = kCGMouseButtonLeft; } - mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, - mouse_button); + mouse_event = + CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, mouse_button); break; } diff --git a/src/device_controller/mouse/mac/mouse_controller.h b/src/device_controller/mouse/mac/mouse_controller.h index f16430f..42f3240 100644 --- a/src/device_controller/mouse/mac/mouse_controller.h +++ b/src/device_controller/mouse/mac/mouse_controller.h @@ -7,6 +7,7 @@ #ifndef _MOUSE_CONTROLLER_H_ #define _MOUSE_CONTROLLER_H_ +#include #include #include "device_controller.h" @@ -24,9 +25,24 @@ class MouseController : public DeviceController { virtual int SendMouseCommand(RemoteAction remote_action, int display_index); private: + struct ClickTracker { + bool has_last_down = false; + std::chrono::steady_clock::time_point last_down_time{}; + int last_down_x = 0; + int last_down_y = 0; + int click_state = 0; + int active_click_state = 1; + }; + + int BeginClick(ClickTracker& tracker, int x, int y); + int EndClick(ClickTracker& tracker, int x, int y); + std::vector display_info_list_; bool left_dragging_ = false; bool right_dragging_ = false; + ClickTracker left_click_tracker_; + ClickTracker right_click_tracker_; + ClickTracker middle_click_tracker_; }; } // namespace crossdesk -#endif \ No newline at end of file +#endif