Skip to content
137 changes: 124 additions & 13 deletions Loop/Core/WindowDragManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Defaults
import os
import Scribe
import SwiftUI

Expand All @@ -23,12 +24,24 @@ final class WindowDragManager {

private let previewController = PreviewController()

/// Listen-only. Snap detection always goes through this, so a stall cannot swallow drags.
private var leftMouseDraggedMonitor: PassiveEventMonitor?
private var leftMouseUpMonitor: PassiveEventMonitor?

/// Rewrites top-edge drag events. Exists only while a resolved window is moving and
/// Suppress Mission Control is on. An active tap that stalls can block drags system-wide,
/// so this must not outlive the drag.
private var missionControlDragMonitor: ActiveEventMonitor?

private var determineDraggedWindowTask: Task<(), Never>?
private var accessibilityCheckerTask: Task<(), Never>?

/// Previous drag Y, so a single crossing of a stacked-display seam is not treated as the top edge.
private let topEdgeRewrite = OSAllocatedUnfairLock(initialState: MissionControlTopEdgeRewrite())

/// Bumped on mouse-up so an in-flight drag task cannot install the active tap after the drag ends.
private var dragSession = 0

private var currentMousePosition: CGPoint {
NSEvent.mouseLocation.flipY(screen: NSScreen.screens[0])
}
Expand Down Expand Up @@ -87,19 +100,94 @@ final class WindowDragManager {
}

private func removeListeners() {
stopMissionControlDragMonitor()
leftMouseUpMonitor?.stop()
leftMouseDraggedMonitor?.stop()

leftMouseUpMonitor = nil
leftMouseDraggedMonitor = nil
}

/// Installs the active tap once a window drag is underway. Non-window drags never reach it.
private func startMissionControlDragMonitorIfNeeded(session: Int) {
guard session == dragSession, missionControlDragMonitor == nil else { return }
guard Defaults[.windowSnapping], Defaults[.suppressMissionControlOnTopDrag] else { return }

let monitor = ActiveEventMonitor(
"mission_control_drag_monitor",
events: [.leftMouseDragged]
) { [weak self] event -> Unmanaged<CGEvent>? in
self?.rewriteTopEdgeDrag(event)
return Unmanaged.passUnretained(event)
}

monitor.start()
// Mouse-up can end the session while the tap is being created. A failed tap must not
// stick around, or later drag events will skip trying again.
guard session == dragSession, monitor.isEnabled else {
monitor.stop()
return
}
missionControlDragMonitor = monitor
}

private func stopMissionControlDragMonitor() {
missionControlDragMonitor?.stop()
missionControlDragMonitor = nil
topEdgeRewrite.withLock { $0.previousY = nil }
}

/// Keeps Mission Control from opening during a top-edge window snap.
///
/// Rewriting the event is smoother than `CGWarpMouseCursorPosition`, which fights the cursor
/// every frame. CoreGraphics bounds are used because this runs on the event-tap thread.
private nonisolated func rewriteTopEdgeDrag(_ event: CGEvent) {
let frames = Self.activeDisplayFrames()
topEdgeRewrite.withLock { state in
if let adjusted = state.rewrittenLocation(event.location, displayFrames: frames) {
event.location = adjusted
}
}
}

/// Top of the display under `point`, in CoreGraphics coordinates (`minY` is the top edge).
///
/// Includes a point sitting up to 1pt past the edge. The cursor is usually clamped onto
/// the edge, but some events are reported slightly above it.
nonisolated static func topEdgeY(at point: CGPoint, displayFrames: [CGRect]) -> CGFloat? {
let topEdges = displayFrames.filter { frame in
point.x >= frame.minX && point.x <= frame.maxX &&
point.y <= frame.minY && point.y >= frame.minY - 1
}

// More than one display can match on a shared corner. Use the edge nearest the pointer.
return topEdges.min(by: {
abs($0.minY - point.y) < abs($1.minY - point.y)
})?.minY
}

private nonisolated static func activeDisplayFrames() -> [CGRect] {
var count: UInt32 = 0
guard CGGetActiveDisplayList(0, nil, &count) == .success, count > 0 else {
return []
}

var displays = [CGDirectDisplayID](repeating: 0, count: Int(count))
guard CGGetActiveDisplayList(count, &displays, &count) == .success else {
return []
}

return displays.prefix(Int(count)).map { CGDisplayBounds($0) }
}

private func leftMouseDragged(event _: CGEvent) {
guard shouldMonitorDragActions else {
return
}

Task {
let session = dragSession

// Process window (only ONCE during a window drag)
if resizeContext == nil, !didFailToResolveDraggedWindow {
setCurrentDraggingWindow()
Expand All @@ -114,15 +202,7 @@ final class WindowDragManager {
}

if Defaults[.windowSnapping] {
// Only warp cursor away from top edge if top snap area is enabled
if Defaults[.suppressMissionControlOnTopDrag],
let frame = NSScreen.main?.displayBounds,
let mouseLocation = CGEvent.mouseLocation,
mouseLocation.y == frame.minY {
let newOrigin = CGPoint(x: mouseLocation.x, y: frame.minY + 1)
CGWarpMouseCursorPosition(newOrigin)
}

startMissionControlDragMonitorIfNeeded(session: session)
processSnapAction()
}
}
Expand All @@ -134,11 +214,14 @@ final class WindowDragManager {
}

private func leftMouseUp(_: CGEvent) {
guard Defaults[.windowSnapping] else {
return
}

Task {
dragSession += 1
stopMissionControlDragMonitor()

guard Defaults[.windowSnapping] else {
return
}

previewController.close()

if let context = resizeContext,
Expand Down Expand Up @@ -193,6 +276,7 @@ final class WindowDragManager {
initialWindowFrame = nil
determineDraggedWindowTask?.cancel()
determineDraggedWindowTask = nil
stopMissionControlDragMonitor()
}

private func hasWindowMoved(_ windowFrame: CGRect, _ initialFrame: CGRect) -> Bool {
Expand Down Expand Up @@ -297,3 +381,30 @@ final class WindowDragManager {
}
}
}

/// Decides when a drag event should be moved 1pt off a display's top edge.
///
/// The first event that lands on an edge is left alone. Rewriting starts only when the previous
/// event was already on that same edge, which is what a held top-edge drag looks like. Crossing
/// the seam between stacked displays is a single event and is not rewritten.
struct MissionControlTopEdgeRewrite: Equatable {
var previousY: CGFloat?

mutating func rewrittenLocation(_ location: CGPoint, displayFrames: [CGRect]) -> CGPoint? {
let currentY = location.y
defer { previousY = currentY }

guard let top = WindowDragManager.topEdgeY(at: location, displayFrames: displayFrames),
let previousY,
previousY <= top,
previousY >= top - 1,
currentY <= top
else {
return nil
}

var adjusted = location
adjusted.y = top + 1
return adjusted
}
}
77 changes: 77 additions & 0 deletions LoopTests/MissionControlSuppressionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// MissionControlSuppressionTests.swift
// LoopTests
//
// Created by Joshua Van Deren on 2026-09-26.
//

import CoreGraphics
@testable import Loop
import Testing

struct MissionControlSuppressionTests {
@Test func ignoresPointsInsideTheDisplay() {
let display = CGRect(x: 0, y: 0, width: 1000, height: 800)

#expect(WindowDragManager.topEdgeY(at: CGPoint(x: 100, y: 10), displayFrames: [display]) == nil)
}

@Test func returnsTheTopEdgeWhenThePointerIsOnIt() {
let display = CGRect(x: 0, y: 0, width: 1000, height: 800)

#expect(WindowDragManager.topEdgeY(at: CGPoint(x: 100, y: 0), displayFrames: [display]) == 0)
}

@Test func returnsTheTopEdgeWhenThePointerIsSlightlyPastIt() {
let display = CGRect(x: 0, y: 0, width: 1000, height: 800)

#expect(WindowDragManager.topEdgeY(at: CGPoint(x: 100, y: -0.5), displayFrames: [display]) == 0)
}

@Test func ignoresPointsWellAboveTheDisplay() {
let display = CGRect(x: 0, y: 0, width: 1000, height: 800)

#expect(WindowDragManager.topEdgeY(at: CGPoint(x: 100, y: -2), displayFrames: [display]) == nil)
}

@Test func reportsTheSeamBetweenStackedDisplays() {
let upper = CGRect(x: 0, y: -800, width: 1000, height: 800)
let lower = CGRect(x: 0, y: 0, width: 1000, height: 800)

// y = 0 is the lower display's top and the upper display's bottom.
#expect(WindowDragManager.topEdgeY(at: CGPoint(x: 100, y: 0), displayFrames: [upper, lower]) == 0)
}

@Test func rewritesOnlyAfterThePointerWasAlreadyOnTheEdge() {
let display = CGRect(x: 0, y: 0, width: 1000, height: 800)
var rewrite = MissionControlTopEdgeRewrite()

let first = rewrite.rewrittenLocation(CGPoint(x: 100, y: 0), displayFrames: [display])
let second = rewrite.rewrittenLocation(CGPoint(x: 100, y: 0), displayFrames: [display])

#expect(first == nil)
#expect(second == CGPoint(x: 100, y: 1))
}

@Test func doesNotRewriteASingleCrossingBetweenStackedDisplays() {
let upper = CGRect(x: 0, y: -800, width: 1000, height: 800)
let lower = CGRect(x: 0, y: 0, width: 1000, height: 800)
let frames = [upper, lower]
var rewrite = MissionControlTopEdgeRewrite()

_ = rewrite.rewrittenLocation(CGPoint(x: 100, y: -20), displayFrames: frames)
let crossing = rewrite.rewrittenLocation(CGPoint(x: 100, y: 0), displayFrames: frames)
let insideLower = rewrite.rewrittenLocation(CGPoint(x: 100, y: 20), displayFrames: frames)

#expect(crossing == nil)
#expect(insideLower == nil)
}

@Test func usesTheNearestTopEdgeWhenDisplaysShareACorner() {
let left = CGRect(x: 0, y: 0, width: 1000, height: 800)
let right = CGRect(x: 1000, y: 0.4, width: 1000, height: 800)
let corner = CGPoint(x: 1000, y: 0)

#expect(WindowDragManager.topEdgeY(at: corner, displayFrames: [left, right]) == 0)
}
}