Skip to content

Commit

Permalink
patches(iOS): support ignoring indirect touch events
Browse files Browse the repository at this point in the history
Due to the way prefersPointerLocked works, if the pointer is currently over
a SwiftUI button, it will trigger every time the mouse is clicked. We
swizzle in a new property that allows us to ignore all indirect touch events
when active.

Fixes #4843
  • Loading branch information
osy committed Apr 18, 2023
1 parent 97c7fef commit 46342e6
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 11 deletions.
6 changes: 0 additions & 6 deletions Platform/iOS/Display/VMDisplayMetalViewController+Pointer.m
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ - (void)mouseDidBecomeCurrent:(NSNotification *)notification API_AVAILABLE(ios(1
};
}
// no handler to the gcmouse scroll event, gestureScroll works fine.
dispatch_async(dispatch_get_main_queue(), ^{
self.prefersPointerLocked = YES;
});
}

- (void)mouseDidStopBeingCurrent:(NSNotification *)notification API_AVAILABLE(ios(14)) {
Expand All @@ -109,9 +106,6 @@ - (void)mouseDidStopBeingCurrent:(NSNotification *)notification API_AVAILABLE(io
for (int i = 0; i < MIN(4, mouse.mouseInput.auxiliaryButtons.count); i++) {
mouse.mouseInput.auxiliaryButtons[i].pressedChangedHandler = nil;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.prefersPointerLocked = NO;
});
}

#pragma mark - UIPointerInteractionDelegate
Expand Down
5 changes: 0 additions & 5 deletions Platform/iOS/Display/VMDisplayMetalViewController+Touch.m
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,6 @@ - (BOOL)switchMouseType:(VMMouseType)type {
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (!self.delegate.qemuInputLegacy) {
for (UITouch *touch in touches) {
if (@available(iOS 14, *)) {
if (self.prefersPointerLocked && (touch.type == UITouchTypeIndirect || touch.type == UITouchTypeIndirectPointer)) {
continue; // skip indirect touches if we are capturing mouse input
}
}
VMMouseType type = [self touchTypeToMouseType:touch.type];
if ([self switchMouseType:type]) {
[self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES]; // reset drag
Expand Down
2 changes: 2 additions & 0 deletions Platform/iOS/Display/VMDisplayMetalViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIVi
- (void)enterSuspendedWithIsBusy:(BOOL)busy {
[super enterSuspendedWithIsBusy:busy];
self.prefersPointerLocked = NO;
self.view.window.isIndirectPointerTouchIgnored = NO;
if (!busy) {
if (self.delegate.qemuHasClipboardSharing) {
[[UTMPasteboard generalPasteboard] releasePollingModeForObject:self];
Expand All @@ -152,6 +153,7 @@ - (void)enterSuspendedWithIsBusy:(BOOL)busy {
- (void)enterLive {
[super enterLive];
self.prefersPointerLocked = YES;
self.view.window.isIndirectPointerTouchIgnored = YES;
if (self.delegate.qemuDisplayIsDynamicResolution) {
[self displayResize:self.view.bounds.size];
}
Expand Down
42 changes: 42 additions & 0 deletions Platform/iOS/UTMPatches.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ final class UTMPatches {
static func patchAll() {
UIViewController.patchViewController()
UIPress.patchPress()
UIWindow.patchWindow()
}
}

Expand Down Expand Up @@ -98,3 +99,44 @@ extension UIPress {
}
}
}

private var IndirectPointerTouchIgnoredHandle: Int = 0

/// Patch to allow ignoring indirect touch when capturing pointer
extension UIWindow {
/// When true, `sendEvent(_:)` will ignore any indirect touch events.
@objc var isIndirectPointerTouchIgnored: Bool {
set {
let number = NSNumber(booleanLiteral: newValue)
objc_setAssociatedObject(self, &IndirectPointerTouchIgnoredHandle, number, .OBJC_ASSOCIATION_ASSIGN)
}

get {
let number = objc_getAssociatedObject(self, &IndirectPointerTouchIgnoredHandle) as? NSNumber
return number?.boolValue ?? false
}
}

/// Replacement `sendEvent(_:)` function
/// - Parameter event: The event to dispatch.
@objc private func xxx_sendEvent(_ event: UIEvent) {
if isIndirectPointerTouchIgnored && event.type == .touches {
event.touches(for: self)?.forEach { touch in
if touch.type == .indirectPointer {
// for some reason, if we just ignore the event, future touch events get messed up
// so as an alternative, we still pass the event through but with a modified coordinate
touch.perform(Selector(("_setLocationInWindow:resetPrevious:")),
with: CGPoint(x: -1, y: -1),
with: true)
}
}
}
xxx_sendEvent(event)
}

fileprivate static func patchWindow() {
patch(#selector(sendEvent),
with: #selector(xxx_sendEvent),
class: Self.self)
}
}

0 comments on commit 46342e6

Please sign in to comment.