@@ -218,12 +218,13 @@ final class MenuBarLayoutManager {
218218 // WindowServer location still matches the real hardware pointer,
219219 // including when the user clicked inside OverflowBar itself.
220220 let physicalPointerLocation = restoreCursorLocation ?? CGEvent ( source: nil ) ? . location
221+ let eventPoint = safeEventPoint ( preferred: physicalPointerLocation, fallback: currentFrame ( windowID: targetWindowID) ?? . zero)
221222 guard let itemWindowID = item. windowID, let ownerPID = item. ownerPID,
222223 currentFrame ( windowID: itemWindowID) != nil ,
223224 let targetFrame = currentFrame ( windowID: targetWindowID) ,
224225 let source = eventSource ( for: ownerPID) ,
225- let down = targetedEvent ( type: . leftMouseDown, point: CGPoint ( x : 20_000 , y : 20_000 ) , windowID: itemWindowID, pid: ownerPID, source: source, command: true ) ,
226- let up = targetedEvent ( type: . leftMouseUp, point: CGPoint ( x : placement == . left ? targetFrame . minX : targetFrame . maxX , y : targetFrame . midY ) , windowID: targetWindowID, pid: ownerPID, source: source, command: false ) else {
226+ let down = targetedEvent ( type: . leftMouseDown, point: eventPoint , windowID: itemWindowID, pid: ownerPID, source: source, command: true ) ,
227+ let up = targetedEvent ( type: . leftMouseUp, point: eventPoint , windowID: targetWindowID, pid: ownerPID, source: source, command: false ) else {
227228 completion ( false )
228229 return
229230 }
@@ -233,10 +234,15 @@ final class MenuBarLayoutManager {
233234 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.03 ) {
234235 self ? . relay ( up, to: ownerPID) { success in
235236 self ? . logger. info ( " Mouse-up relay window \( itemWindowID, privacy: . public) success= \( success, privacy: . public) " )
236- // The relay consumes the synthetic event after forwarding
237- // it to the owner process. Do not warp or otherwise
238- // restore the global pointer here; that reintroduces the
239- // stale-hover state this manager is designed to avoid.
237+ // A hide drag still has to target the off-screen staging
238+ // window, so WindowServer may clamp its logical pointer
239+ // while processing the event. Restore the pointer only
240+ // after the hide transaction (never during reveal), and
241+ // only to the location captured immediately before this
242+ // transaction began.
243+ if placement == . left {
244+ self ? . restoreSyntheticPointer ( physicalPointerLocation)
245+ }
240246 self ? . verifyMove ( item, relativeTo: targetWindowID, placement: placement, attempt: attempt, check: 0 , restoreCursorLocation: physicalPointerLocation, completion: completion)
241247 }
242248 }
@@ -264,6 +270,9 @@ final class MenuBarLayoutManager {
264270 self . move ( item, relativeTo: targetWindowID, placement: placement, attempt: attempt + 1 , restoreCursorLocation: restoreCursorLocation, completion: completion)
265271 } else {
266272 self . logger. info ( " Move verification window \( itemWindowID, privacy: . public) failed " )
273+ if placement == . left {
274+ self . restoreSyntheticPointer ( restoreCursorLocation)
275+ }
267276 completion ( false )
268277 }
269278 }
@@ -291,6 +300,29 @@ final class MenuBarLayoutManager {
291300 return CGEventSource ( stateID: state)
292301 }
293302
303+ /// All synthetic drag events must carry an on-screen cursor coordinate.
304+ /// The target window fields select the status-item source/destination;
305+ /// using an off-screen point (20,000 or a negative hidden-section frame)
306+ /// makes WindowServer clamp the logical pointer to the top-left corner.
307+ private func safeEventPoint( preferred: CGPoint ? , fallback: CGRect ) -> CGPoint {
308+ let displays = Self . activeDisplayBounds ( )
309+ if let preferred, displays. contains ( where: { $0. contains ( preferred) } ) {
310+ return preferred
311+ }
312+ if let display = displays. first {
313+ let x = min ( max ( fallback. midX, display. minX + 8 ) , display. maxX - 8 )
314+ let y = min ( max ( fallback. midY, display. minY + 8 ) , display. maxY - 8 )
315+ return CGPoint ( x: x, y: y)
316+ }
317+ return CGPoint ( x: 8 , y: 8 )
318+ }
319+
320+ private func restoreSyntheticPointer( _ point: CGPoint ? ) {
321+ guard let point, Self . activeDisplayBounds ( ) . contains ( where: { $0. contains ( point) } ) else { return }
322+ CGWarpMouseCursorPosition ( point)
323+ CGAssociateMouseAndMouseCursorPosition ( 1 )
324+ }
325+
294326 private func targetedEvent( type: CGEventType , point: CGPoint , windowID: CGWindowID , pid: pid_t , source: CGEventSource , command: Bool ) -> CGEvent ? {
295327 guard let event = CGEvent ( mouseEventSource: source, mouseType: type, mouseCursorPosition: point, mouseButton: . left) else { return nil }
296328 event. flags = command ? . maskCommand : [ ]
0 commit comments