[fix] support selective mouse button release across desktop platforms

This commit is contained in:
dijunkun
2026-10-22 16:10:34 +08:00
parent f6a2637540
commit 5e494bfa3b
10 changed files with 115 additions and 21 deletions
@@ -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<DisplayInfo>&) {}
};
@@ -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<MouseFlag>(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<int>(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<int>(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;
}
@@ -32,6 +32,7 @@ class PlatformMouseController final : public MouseController {
virtual int Init(std::vector<DisplayInfo> 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<DisplayInfo>& 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_;
@@ -46,7 +46,37 @@ int PlatformMouseController::Init(std::vector<DisplayInfo> 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<DisplayInfo>& 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<int>(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;
@@ -26,6 +26,7 @@ class PlatformMouseController final : public MouseController {
virtual int Init(std::vector<DisplayInfo> 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<DisplayInfo>& display_info_list);
@@ -45,6 +46,7 @@ class PlatformMouseController final : public MouseController {
std::vector<DisplayInfo> 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_;
@@ -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,
@@ -29,7 +29,7 @@ class PlatformMouseController final : public MouseController {
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
void UpdateDisplayInfoList(
const std::vector<DisplayInfo>& display_info_list) override;
int ReleasePressedButtons();
unsigned ReleasePressedButtons(unsigned mask = 7) override;
private:
std::vector<DisplayInfo> display_info_list_;
+4
View File
@@ -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<DisplayInfo> displays) override {
if (displays.empty()) return -1;
std::lock_guard lock(input_mutex);
+7 -2
View File
@@ -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.
+1
View File
@@ -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);