         return _addObservers().retry(.exponentialDelayed(maxCount: 4, initial: 0.1, multiplier: 2))
     }
 
+    private func swapDraggedWindowWithDropzone(_ draggedWindow: SIWindow) {
+        guard let screen = draggedWindow.screen() else {
+            return
+        }
+
+        let windows = windowManager.windows(on: screen)
+
+        // need to flip mouse coordinate system to fit Amethyst https://stackoverflow.com/a/45289010/2063546
+        let flippedPointerLocation = NSPointToCGPoint(NSEvent.mouseLocation)
+        let unflippedY = NSScreen.globalHeight() - flippedPointerLocation.y
+        let pointerLocation = NSPointToCGPoint(NSPoint(x: flippedPointerLocation.x, y: unflippedY))
+
+        // Ignore if there is no window at that point
+        guard let secondWindow = SIWindow.secondWindowForScreenAtPoint(pointerLocation, withWindows: windows) else {
+            return
+        }
+        windowManager.switchWindow(draggedWindow, with: secondWindow)
+    }
+
     private func _addObservers() -> Observable<Bool> {
         let application = self.application
         let windowManager = self.windowManager
+
+        var leftMouseButtonUpMoment: NSDate? = nil
+        var windowBeingDragged: SIWindow? = nil
+        let mouseEventsToWatch: NSEvent.EventTypeMask = [.leftMouseDown, .leftMouseUp] //, .leftMouseDragged]
+
+        NSEvent.addGlobalMonitorForEvents(matching: mouseEventsToWatch) { anEvent in
+            switch anEvent.type {
+            case .leftMouseDown:
+                leftMouseButtonUpMoment = nil
+            case .leftMouseDragged:
+                leftMouseButtonUpMoment = nil
+            case .leftMouseUp:
+                if let draggedWindow = windowBeingDragged {
+                    self.swapDraggedWindowWithDropzone(draggedWindow)
+                    leftMouseButtonUpMoment = nil
+                } else {
+                    // assume window move event will come shortly after
+                    leftMouseButtonUpMoment = NSDate()
+                }
+                windowBeingDragged = nil
+
+            default: ()
+            }
+        }
 
         return Observable.create { observer in
             var success: Bool = false

                    return
                }

                let windows = windowManager.windows(on: screen)

                // need to flip mouse coordinate system to fit Amethyst https://stackoverflow.com/a/42901022/2063546
                let flippedPointerLocation = NSPointToCGPoint(NSEvent.mouseLocation)
                let unflippedY = NSScreen.globalHeight() - flippedPointerLocation.y
                let pointerLocation = NSPointToCGPoint(NSPoint(x: flippedPointerLocation.x, y: unflippedY))

                // Ignore if there is no window at that point
                guard let secondWindow = SIWindow.secondWindowForScreenAtPoint(pointerLocation, withWindows: windows) else {
                    return
                }

                windowManager.switchWindow(focusedWindow, with: secondWindow)
            }
            observer.on(.next(true))
            observer.on(.completed)

import Silica
import SwiftyJSON

fileprivate let mouseDragRaceThresholdSeconds = 0.15

enum WindowChange {
    case add(window: SIWindow)
    case remove(window: SIWindow)
