import Cocoa import FlutterMacOS // Hides the OS cursor and keeps it pinned in place for the duration of a // drag, streaming raw pointer deltas back to Dart ourselves. // // Why not just let Flutters own pointer events drive this: Flutter computes // PointerEvent.delta from the ABSOLUTE cursor position of consecutive // events, not from raw hardware deltaX/deltaY. An earlier version tried to // keep the cursor pinned by warping it back to an anchor point after every // move while staying associated in between - that still let the OS visibly // move the cursor between warps, and the warps themselves raced against // real movement over the async Dart<->native round trip. // // The actual fix: CGAssociateMouseAndMouseCursorPosition(false) for the // WHOLE gesture, not toggled per-event. WindowServer then simply stops // moving the on-screen cursor at all in response to hardware input - no // per-frame warping needed. mouseDragged events keep firing completely // normally while disassociated, with correct deltaX/deltaY reflecting real // HID movement (its only locationInWindow that freezes) - which is exactly // why we read deltaX/deltaY here instead of position. Re-associating on // unlock resumes normal 1:1 tracking from wherever the cursor was frozen, // so it naturally reappears back where the drag started. final class CursorLock { static let shared = CursorLock() private var locked = false private var monitor: Any? // strong on purpose - CursorLock.shared is a permanent singleton, theres // no retain cycle risk, and a weak ref here previously meant the channel // (whose only other strong reference was a local `let` in awakeFromNib) // got deallocated the moment that function returned, silently breaking // delta forwarding. private var channel: FlutterMethodChannel? func attach(channel: FlutterMethodChannel) { self.channel = channel } func lock() { guard !locked else { return } locked = true NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) monitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDragged]) { [weak self] event in guard let self, self.locked else { return event } self.channel?.invokeMethod("scrubDelta", arguments: ["dx": event.deltaX, "dy": event.deltaY]) // swallowed - Flutters own position for this event is frozen (and // therefore meaningless) while disassociated, no point forwarding it. return nil } } func unlock() { guard locked else { return } locked = false if let monitor { NSEvent.removeMonitor(monitor) } monitor = nil CGAssociateMouseAndMouseCursorPosition(1) NSCursor.unhide() } }