From 5e494bfa3b6c1ced859a846d781760e5c9f3dc30 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Thu, 22 Oct 2026 16:10:34 +0800 Subject: [PATCH] [fix] support selective mouse button release across desktop platforms --- .../src/device_controller/device_controller.h | 3 ++ .../linux/input/mouse/mouse_controller.cpp | 52 ++++++++++++++++--- .../linux/input/mouse/mouse_controller.h | 2 + .../macos/input/mouse/mouse_controller.cpp | 46 ++++++++++++++-- .../macos/input/mouse/mouse_controller.h | 2 + .../windows/input/mouse/mouse_controller.cpp | 15 +++--- .../windows/input/mouse/mouse_controller.h | 2 +- apps/harmony/desktop-port/devices.cpp | 4 ++ apps/harmony/native/input.cpp | 9 +++- apps/harmony/native/input.h | 1 + 10 files changed, 115 insertions(+), 21 deletions(-) diff --git a/apps/desktop/src/device_controller/device_controller.h b/apps/desktop/src/device_controller/device_controller.h index 4c6349af..c844533c 100644 --- a/apps/desktop/src/device_controller/device_controller.h +++ b/apps/desktop/src/device_controller/device_controller.h @@ -32,6 +32,9 @@ class MouseController : public DeviceController { virtual int Destroy() = 0; virtual int SendMouseCommand(RemoteAction remote_action, int display_index) = 0; + // Bits 0/1/2 are left/right/middle. Return the subset confirmed released, + // including buttons already up. Must not require valid display geometry. + virtual unsigned ReleasePressedButtons(unsigned mask = 7) = 0; virtual void UpdateDisplayInfoList( const std::vector&) {} }; diff --git a/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp index 6bf73dbb..41378f2e 100644 --- a/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp +++ b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp @@ -67,6 +67,7 @@ void PlatformMouseController::UpdateDisplayInfoList( } int PlatformMouseController::Destroy() { + ReleasePressedButtons(); CleanupWaylandPortal(); if (display_) { @@ -77,6 +78,28 @@ int PlatformMouseController::Destroy() { return 0; } +unsigned PlatformMouseController::ReleasePressedButtons(unsigned mask) { + mask &= 7; + const unsigned pressed = pressed_buttons_ & mask; + for (unsigned i = 0; i < 3; ++i) { + const unsigned bit = 1u << i; + if (!(pressed & bit)) continue; + bool released = false; + if (use_wayland_portal_) { + RemoteAction up{}; + up.type = ControlType::mouse; + up.m.flag = static_cast(2 + i * 2); + released = SendWaylandMouseCommand(up, 0) == 0; + } else if (display_) { + const unsigned buttons[] = {1, 3, 2}; + released = XTestFakeButtonEvent(display_, buttons[i], False, CurrentTime) != 0; + XFlush(display_); + } + if (released) pressed_buttons_ &= ~bit; + } + return mask & ~pressed_buttons_; +} + int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, int display_index) { if (remote_action.type != ControlType::mouse) { @@ -84,7 +107,16 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, } if (use_wayland_portal_) { - return SendWaylandMouseCommand(remote_action, display_index); + const int ret = SendWaylandMouseCommand(remote_action, display_index); + if (ret == 0) { + const int flag = static_cast(remote_action.m.flag); + if (flag >= 1 && flag <= 6) { + const unsigned bit = 1u << ((flag - 1) / 2); + if (flag & 1) pressed_buttons_ |= bit; + else pressed_buttons_ &= ~bit; + } + } + return ret; } if (!display_) { @@ -112,27 +144,27 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, break; } case MouseFlag::left_down: - XTestFakeButtonEvent(display_, 1, True, CurrentTime); + if (!XTestFakeButtonEvent(display_, 1, True, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::left_up: - XTestFakeButtonEvent(display_, 1, False, CurrentTime); + if (!XTestFakeButtonEvent(display_, 1, False, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::right_down: - XTestFakeButtonEvent(display_, 3, True, CurrentTime); + if (!XTestFakeButtonEvent(display_, 3, True, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::right_up: - XTestFakeButtonEvent(display_, 3, False, CurrentTime); + if (!XTestFakeButtonEvent(display_, 3, False, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::middle_down: - XTestFakeButtonEvent(display_, 2, True, CurrentTime); + if (!XTestFakeButtonEvent(display_, 2, True, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::middle_up: - XTestFakeButtonEvent(display_, 2, False, CurrentTime); + if (!XTestFakeButtonEvent(display_, 2, False, CurrentTime)) return -1; XFlush(display_); break; case MouseFlag::wheel_vertical: { @@ -157,6 +189,12 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, break; } + const int flag = static_cast(remote_action.m.flag); + if (flag >= 1 && flag <= 6) { + const unsigned bit = 1u << ((flag - 1) / 2); + if (flag & 1) pressed_buttons_ |= bit; + else pressed_buttons_ &= ~bit; + } return 0; } diff --git a/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h index fa0cbaff..d1622efb 100644 --- a/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h +++ b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h @@ -32,6 +32,7 @@ class PlatformMouseController final : public MouseController { virtual int Init(std::vector display_info_list); virtual int Destroy(); virtual int SendMouseCommand(RemoteAction remote_action, int display_index); + unsigned ReleasePressedButtons(unsigned mask = 7) override; void UpdateDisplayInfoList(const std::vector& display_info_list); private: @@ -58,6 +59,7 @@ class PlatformMouseController final : public MouseController { int screen_width_ = 0; int screen_height_ = 0; bool use_wayland_portal_ = false; + unsigned pressed_buttons_ = 0; DBusConnection* dbus_connection_ = nullptr; std::string wayland_session_handle_; diff --git a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp index 9bc0007c..148904d5 100644 --- a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp +++ b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp @@ -46,7 +46,37 @@ int PlatformMouseController::Init(std::vector display_info_list) { return 0; } -int PlatformMouseController::Destroy() { return 0; } +int PlatformMouseController::Destroy() { + return ReleasePressedButtons() == 7 ? 0 : -1; +} + +unsigned PlatformMouseController::ReleasePressedButtons(unsigned mask) { + mask &= 7; + const unsigned pressed = pressed_buttons_ & mask; + if (!pressed) return mask; + const auto cursor = CGEventCreate(nullptr); + if (!cursor) return mask & ~pressed; + const auto point = CGEventGetLocation(cursor); + CFRelease(cursor); + const CGEventType types[] = {kCGEventLeftMouseUp, kCGEventRightMouseUp, + kCGEventOtherMouseUp}; + const CGMouseButton buttons[] = {kCGMouseButtonLeft, kCGMouseButtonRight, + kCGMouseButtonCenter}; + for (unsigned i = 0; i < 3; ++i) { + const unsigned bit = 1u << i; + if (!(pressed & bit)) continue; + const auto event = CGEventCreateMouseEvent(nullptr, types[i], point, buttons[i]); + if (!event) continue; + CGEventSetIntegerValueField(event, kCGEventSourceUserData, kCrossDeskInjectedEvent); + CGEventPost(kCGHIDEventTap, event); + CFRelease(event); + pressed_buttons_ &= ~bit; + if (i == 0) { left_dragging_ = false; left_click_tracker_ = {}; } + if (i == 1) { right_dragging_ = false; right_click_tracker_ = {}; } + if (i == 2) middle_click_tracker_ = {}; + } + return mask & ~pressed_buttons_; +} void PlatformMouseController::UpdateDisplayInfoList( const std::vector& display_info_list) { @@ -135,7 +165,6 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, switch (remote_action.m.flag) { case MouseFlag::left_down: mouse_type = kCGEventLeftMouseDown; - left_dragging_ = true; click_state = BeginClick(left_click_tracker_, tracked_mouse_pos_x, tracked_mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, @@ -144,7 +173,6 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, break; case MouseFlag::left_up: mouse_type = kCGEventLeftMouseUp; - left_dragging_ = false; click_state = EndClick(left_click_tracker_, tracked_mouse_pos_x, tracked_mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, @@ -153,7 +181,6 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, break; case MouseFlag::right_down: mouse_type = kCGEventRightMouseDown; - right_dragging_ = true; click_state = BeginClick(right_click_tracker_, tracked_mouse_pos_x, tracked_mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, @@ -162,7 +189,6 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, break; case MouseFlag::right_up: mouse_type = kCGEventRightMouseUp; - right_dragging_ = false; click_state = EndClick(right_click_tracker_, tracked_mouse_pos_x, tracked_mouse_pos_y); mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, @@ -215,6 +241,16 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, kCrossDeskInjectedEvent); CGEventPost(kCGHIDEventTap, mouse_event); CFRelease(mouse_event); + const int flag = static_cast(remote_action.m.flag); + if (flag >= 1 && flag <= 6) { + const unsigned bit = 1u << ((flag - 1) / 2); + if (flag & 1) pressed_buttons_ |= bit; + else pressed_buttons_ &= ~bit; + left_dragging_ = (pressed_buttons_ & 1) != 0; + right_dragging_ = (pressed_buttons_ & 2) != 0; + } + } else { + return -1; } return 0; diff --git a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h index c1842460..7b68a0fc 100644 --- a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h +++ b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h @@ -26,6 +26,7 @@ class PlatformMouseController final : public MouseController { virtual int Init(std::vector display_info_list); virtual int Destroy(); virtual int SendMouseCommand(RemoteAction remote_action, int display_index); + unsigned ReleasePressedButtons(unsigned mask = 7) override; void UpdateDisplayInfoList( const std::vector& display_info_list); @@ -45,6 +46,7 @@ class PlatformMouseController final : public MouseController { std::vector display_info_list_; bool left_dragging_ = false; bool right_dragging_ = false; + unsigned pressed_buttons_ = 0; ClickTracker left_click_tracker_; ClickTracker right_click_tracker_; ClickTracker middle_click_tracker_; diff --git a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp index e84c47b1..6bd5a514 100644 --- a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp +++ b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp @@ -40,11 +40,14 @@ void PlatformMouseController::UpdateDisplayInfoList( display_info_list_.size()); } -int PlatformMouseController::Destroy() { return ReleasePressedButtons(); } +int PlatformMouseController::Destroy() { + return ReleasePressedButtons() == 7 ? 0 : -1; +} -int PlatformMouseController::ReleasePressedButtons() { - const unsigned pressed = pressed_buttons_.load(); - if (!pressed) return 0; +unsigned PlatformMouseController::ReleasePressedButtons(unsigned mask) { + mask &= 7; + const unsigned pressed = pressed_buttons_.load() & mask; + if (!pressed) return mask; INPUT input{}; input.type = INPUT_MOUSE; input.mi.dwExtraInfo = kInjectedMouseInputMarker; @@ -53,10 +56,10 @@ int PlatformMouseController::ReleasePressedButtons() { if (pressed & 4) input.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; if (SendInputOnUserDesktop(input) != 1) { LOG_WARN("Release remote mouse buttons failed, error={}", GetLastError()); - return -1; + return mask & ~pressed; } pressed_buttons_.fetch_and(~pressed); - return 0; + return mask; } int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, diff --git a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h index 08a5ccca..f2ead176 100644 --- a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h +++ b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h @@ -29,7 +29,7 @@ class PlatformMouseController final : public MouseController { virtual int SendMouseCommand(RemoteAction remote_action, int display_index); void UpdateDisplayInfoList( const std::vector& display_info_list) override; - int ReleasePressedButtons(); + unsigned ReleasePressedButtons(unsigned mask = 7) override; private: std::vector display_info_list_; diff --git a/apps/harmony/desktop-port/devices.cpp b/apps/harmony/desktop-port/devices.cpp index b6785d87..c56cd5c6 100644 --- a/apps/harmony/desktop-port/devices.cpp +++ b/apps/harmony/desktop-port/devices.cpp @@ -244,6 +244,10 @@ class HarmonyMouse final : public MouseController { return true; } public: + unsigned ReleasePressedButtons(unsigned mask = 7) override { + std::lock_guard lock(input_mutex); + return input.ReleaseMouseButtons(mask); + } int Init(std::vector displays) override { if (displays.empty()) return -1; std::lock_guard lock(input_mutex); diff --git a/apps/harmony/native/input.cpp b/apps/harmony/native/input.cpp index ebc13ad1..2f26ab06 100644 --- a/apps/harmony/native/input.cpp +++ b/apps/harmony/native/input.cpp @@ -111,12 +111,17 @@ bool Input::Apply(const RemoteAction& action) { bool Input::Release() { for (const auto& change : ReconcileKeyboardState(keys_, {})) Key({change.key_code, change.scan_code, change.extended}, false); - for (int button = 0; button < 3; ++button) - if (buttons_ & (1u << button)) Pointer(2 + button * 2, x_, y_, 0); + ReleaseMouseButtons(7); // Failed releases remain pending so a later cleanup can retry them. have_key_sequence_ = false; return keys_.empty() && buttons_ == 0; } +unsigned Input::ReleaseMouseButtons(unsigned mask) { + mask &= 7; + for (int button = 0; button < 3; ++button) + if (buttons_ & mask & (1u << button)) Pointer(2 + button * 2, x_, y_, 0); + return mask & ~buttons_; +} bool Input::LockWorkstation() { // The public Harmony shortcut is Meta+L. Never carry a prior remote drag or // modifier into it, and retain failed releases for the normal cleanup path. diff --git a/apps/harmony/native/input.h b/apps/harmony/native/input.h index da95353d..d4738f15 100644 --- a/apps/harmony/native/input.h +++ b/apps/harmony/native/input.h @@ -16,6 +16,7 @@ class Input { bool Configure(int width, int height, int display); bool Apply(const RemoteAction& action); bool Release(); + unsigned ReleaseMouseButtons(unsigned mask); bool LockWorkstation(); private: bool Key(const KeyboardKey& key, bool down);