winemac: Synthesize additional bitmap pasteboard types from any that Cocoa can convert.
[wine] / dlls / winemac.drv / cocoa_app.m
1 /*
2  * MACDRV Cocoa application class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #import <Carbon/Carbon.h>
22 #include <dlfcn.h>
23
24 #import "cocoa_app.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
27
28
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
30
31
32 int macdrv_err_on;
33
34
35 @interface WarpRecord : NSObject
36 {
37     CGEventTimestamp timeBefore, timeAfter;
38     CGPoint from, to;
39 }
40
41 @property (nonatomic) CGEventTimestamp timeBefore;
42 @property (nonatomic) CGEventTimestamp timeAfter;
43 @property (nonatomic) CGPoint from;
44 @property (nonatomic) CGPoint to;
45
46 @end
47
48
49 @implementation WarpRecord
50
51 @synthesize timeBefore, timeAfter, from, to;
52
53 @end;
54
55
56 @interface WineApplication ()
57
58 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
59 @property (copy, nonatomic) NSArray* cursorFrames;
60 @property (retain, nonatomic) NSTimer* cursorTimer;
61
62     static void PerformRequest(void *info);
63
64 @end
65
66
67 @implementation WineApplication
68
69     @synthesize keyboardType, lastFlagsChanged;
70     @synthesize orderedWineWindows;
71     @synthesize cursorFrames, cursorTimer;
72
73     - (id) init
74     {
75         self = [super init];
76         if (self != nil)
77         {
78             CFRunLoopSourceContext context = { 0 };
79             context.perform = PerformRequest;
80             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
81             if (!requestSource)
82             {
83                 [self release];
84                 return nil;
85             }
86             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
87             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
88
89             requests =  [[NSMutableArray alloc] init];
90             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
91
92             eventQueues = [[NSMutableArray alloc] init];
93             eventQueuesLock = [[NSLock alloc] init];
94
95             keyWindows = [[NSMutableArray alloc] init];
96             orderedWineWindows = [[NSMutableArray alloc] init];
97
98             originalDisplayModes = [[NSMutableDictionary alloc] init];
99
100             warpRecords = [[NSMutableArray alloc] init];
101
102             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
103                 !keyWindows || !orderedWineWindows || !originalDisplayModes || !warpRecords)
104             {
105                 [self release];
106                 return nil;
107             }
108         }
109         return self;
110     }
111
112     - (void) dealloc
113     {
114         [warpRecords release];
115         [cursorTimer release];
116         [cursorFrames release];
117         [originalDisplayModes release];
118         [orderedWineWindows release];
119         [keyWindows release];
120         [eventQueues release];
121         [eventQueuesLock release];
122         if (requestsManipQueue) dispatch_release(requestsManipQueue);
123         [requests release];
124         if (requestSource)
125         {
126             CFRunLoopSourceInvalidate(requestSource);
127             CFRelease(requestSource);
128         }
129         [super dealloc];
130     }
131
132     - (void) transformProcessToForeground
133     {
134         if ([self activationPolicy] != NSApplicationActivationPolicyRegular)
135         {
136             NSMenu* mainMenu;
137             NSMenu* submenu;
138             NSString* bundleName;
139             NSString* title;
140             NSMenuItem* item;
141
142             [self setActivationPolicy:NSApplicationActivationPolicyRegular];
143             [self activateIgnoringOtherApps:YES];
144
145             mainMenu = [[[NSMenu alloc] init] autorelease];
146
147             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
148             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
149             if ([bundleName length])
150                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
151             else
152                 title = @"Quit";
153             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
154             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
155             item = [[[NSMenuItem alloc] init] autorelease];
156             [item setTitle:@"Wine"];
157             [item setSubmenu:submenu];
158             [mainMenu addItem:item];
159
160             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
161             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
162             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
163             [submenu addItem:[NSMenuItem separatorItem]];
164             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
165             item = [[[NSMenuItem alloc] init] autorelease];
166             [item setTitle:@"Window"];
167             [item setSubmenu:submenu];
168             [mainMenu addItem:item];
169
170             [self setMainMenu:mainMenu];
171             [self setWindowsMenu:submenu];
172         }
173     }
174
175     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout
176     {
177         PerformRequest(NULL);
178
179         do
180         {
181             [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
182         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
183
184         return *done;
185     }
186
187     - (BOOL) registerEventQueue:(WineEventQueue*)queue
188     {
189         [eventQueuesLock lock];
190         [eventQueues addObject:queue];
191         [eventQueuesLock unlock];
192         return TRUE;
193     }
194
195     - (void) unregisterEventQueue:(WineEventQueue*)queue
196     {
197         [eventQueuesLock lock];
198         [eventQueues removeObjectIdenticalTo:queue];
199         [eventQueuesLock unlock];
200     }
201
202     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
203     {
204         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
205     }
206
207     - (double) ticksForEventTime:(NSTimeInterval)eventTime
208     {
209         return (eventTime + eventTimeAdjustment) * 1000;
210     }
211
212     /* Invalidate old focus offers across all queues. */
213     - (void) invalidateGotFocusEvents
214     {
215         WineEventQueue* queue;
216
217         windowFocusSerial++;
218
219         [eventQueuesLock lock];
220         for (queue in eventQueues)
221         {
222             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
223                                    forWindow:nil];
224         }
225         [eventQueuesLock unlock];
226     }
227
228     - (void) windowGotFocus:(WineWindow*)window
229     {
230         macdrv_event event;
231
232         [NSApp invalidateGotFocusEvents];
233
234         event.type = WINDOW_GOT_FOCUS;
235         event.window = (macdrv_window)[window retain];
236         event.window_got_focus.serial = windowFocusSerial;
237         if (triedWindows)
238             event.window_got_focus.tried_windows = [triedWindows retain];
239         else
240             event.window_got_focus.tried_windows = [[NSMutableSet alloc] init];
241         [window.queue postEvent:&event];
242     }
243
244     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
245     {
246         if (event->window_got_focus.serial == windowFocusSerial)
247         {
248             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
249             [triedWindows addObject:(WineWindow*)event->window];
250             for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWindows]])
251             {
252                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
253                 {
254                     [window makeKeyWindow];
255                     break;
256                 }
257             }
258             triedWindows = nil;
259         }
260     }
261
262     - (void) keyboardSelectionDidChange
263     {
264         TISInputSourceRef inputSource;
265
266         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
267         if (inputSource)
268         {
269             CFDataRef uchr;
270             uchr = TISGetInputSourceProperty(inputSource,
271                     kTISPropertyUnicodeKeyLayoutData);
272             if (uchr)
273             {
274                 macdrv_event event;
275                 WineEventQueue* queue;
276
277                 event.type = KEYBOARD_CHANGED;
278                 event.window = NULL;
279                 event.keyboard_changed.keyboard_type = self.keyboardType;
280                 event.keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
281                 event.keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
282
283                 if (event.keyboard_changed.uchr)
284                 {
285                     [eventQueuesLock lock];
286
287                     for (queue in eventQueues)
288                     {
289                         CFRetain(event.keyboard_changed.uchr);
290                         [queue postEvent:&event];
291                     }
292
293                     [eventQueuesLock unlock];
294
295                     CFRelease(event.keyboard_changed.uchr);
296                 }
297             }
298
299             CFRelease(inputSource);
300         }
301     }
302
303     - (CGFloat) primaryScreenHeight
304     {
305         if (!primaryScreenHeightValid)
306         {
307             NSArray* screens = [NSScreen screens];
308             if ([screens count])
309             {
310                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
311                 primaryScreenHeightValid = TRUE;
312             }
313             else
314                 return 1280; /* arbitrary value */
315         }
316
317         return primaryScreenHeight;
318     }
319
320     - (NSPoint) flippedMouseLocation:(NSPoint)point
321     {
322         /* This relies on the fact that Cocoa's mouse location points are
323            actually off by one (precisely because they were flipped from
324            Quartz screen coordinates using this same technique). */
325         point.y = [self primaryScreenHeight] - point.y;
326         return point;
327     }
328
329     - (void) flipRect:(NSRect*)rect
330     {
331         // We don't use -primaryScreenHeight here so there's no chance of having
332         // out-of-date cached info.  This method is called infrequently enough
333         // that getting the screen height each time is not prohibitively expensive.
334         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
335     }
336
337     - (void) wineWindow:(WineWindow*)window
338                 ordered:(NSWindowOrderingMode)order
339              relativeTo:(WineWindow*)otherWindow
340     {
341         NSUInteger index;
342
343         switch (order)
344         {
345             case NSWindowAbove:
346                 [window retain];
347                 [orderedWineWindows removeObjectIdenticalTo:window];
348                 if (otherWindow)
349                 {
350                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
351                     if (index == NSNotFound)
352                         index = 0;
353                 }
354                 else
355                 {
356                     index = 0;
357                     for (otherWindow in orderedWineWindows)
358                     {
359                         if ([otherWindow levelWhenActive] <= [window levelWhenActive])
360                             break;
361                         index++;
362                     }
363                 }
364                 [orderedWineWindows insertObject:window atIndex:index];
365                 [window release];
366                 break;
367             case NSWindowBelow:
368                 [window retain];
369                 [orderedWineWindows removeObjectIdenticalTo:window];
370                 if (otherWindow)
371                 {
372                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
373                     if (index == NSNotFound)
374                         index = [orderedWineWindows count];
375                 }
376                 else
377                 {
378                     index = 0;
379                     for (otherWindow in orderedWineWindows)
380                     {
381                         if ([otherWindow levelWhenActive] < [window levelWhenActive])
382                             break;
383                         index++;
384                     }
385                 }
386                 [orderedWineWindows insertObject:window atIndex:index];
387                 [window release];
388                 break;
389             case NSWindowOut:
390             default:
391                 break;
392         }
393     }
394
395     - (void) sendDisplaysChanged:(BOOL)activating
396     {
397         macdrv_event event;
398         WineEventQueue* queue;
399
400         event.type = DISPLAYS_CHANGED;
401         event.window = NULL;
402         event.displays_changed.activating = activating;
403
404         [eventQueuesLock lock];
405         for (queue in eventQueues)
406             [queue postEvent:&event];
407         [eventQueuesLock unlock];
408     }
409
410     // We can compare two modes directly using CFEqual, but that may require that
411     // they are identical to a level that we don't need.  In particular, when the
412     // OS switches between the integrated and discrete GPUs, the set of display
413     // modes can change in subtle ways.  We're interested in whether two modes
414     // match in their most salient features, even if they aren't identical.
415     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
416     {
417         NSString *encoding1, *encoding2;
418         uint32_t ioflags1, ioflags2, different;
419         double refresh1, refresh2;
420
421         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
422         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
423
424         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
425         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
426         if (![encoding1 isEqualToString:encoding2]) return FALSE;
427
428         ioflags1 = CGDisplayModeGetIOFlags(mode1);
429         ioflags2 = CGDisplayModeGetIOFlags(mode2);
430         different = ioflags1 ^ ioflags2;
431         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
432                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
433             return FALSE;
434
435         refresh1 = CGDisplayModeGetRefreshRate(mode1);
436         if (refresh1 == 0) refresh1 = 60;
437         refresh2 = CGDisplayModeGetRefreshRate(mode2);
438         if (refresh2 == 0) refresh2 = 60;
439         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
440
441         return TRUE;
442     }
443
444     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
445     {
446         CGDisplayModeRef ret = NULL;
447         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
448         for (id candidateModeObject in modes)
449         {
450             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
451             if ([self mode:candidateMode matchesMode:mode])
452             {
453                 ret = candidateMode;
454                 break;
455             }
456         }
457         return ret;
458     }
459
460     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
461     {
462         BOOL ret = FALSE;
463         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
464         CGDisplayModeRef currentMode, originalMode;
465
466         currentMode = CGDisplayCopyDisplayMode(displayID);
467         if (!currentMode) // Invalid display ID
468             return FALSE;
469
470         if ([self mode:mode matchesMode:currentMode]) // Already there!
471         {
472             CGDisplayModeRelease(currentMode);
473             return TRUE;
474         }
475
476         mode = [self modeMatchingMode:mode forDisplay:displayID];
477         if (!mode)
478         {
479             CGDisplayModeRelease(currentMode);
480             return FALSE;
481         }
482
483         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
484         if (!originalMode)
485             originalMode = currentMode;
486
487         if ([self mode:mode matchesMode:originalMode])
488         {
489             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
490             {
491                 CGRestorePermanentDisplayConfiguration();
492                 CGReleaseAllDisplays();
493                 [originalDisplayModes removeAllObjects];
494                 ret = TRUE;
495             }
496             else // ... otherwise, try to restore just the one display
497             {
498                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
499                 {
500                     [originalDisplayModes removeObjectForKey:displayIDKey];
501                     ret = TRUE;
502                 }
503             }
504         }
505         else
506         {
507             if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
508             {
509                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
510                 {
511                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
512                     ret = TRUE;
513                 }
514                 else if (![originalDisplayModes count])
515                 {
516                     CGRestorePermanentDisplayConfiguration();
517                     CGReleaseAllDisplays();
518                 }
519             }
520         }
521
522         CGDisplayModeRelease(currentMode);
523
524         if (ret)
525         {
526             [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
527                 [(WineWindow*)obj adjustWindowLevel];
528             }];
529         }
530
531         return ret;
532     }
533
534     - (BOOL) areDisplaysCaptured
535     {
536         return ([originalDisplayModes count] > 0);
537     }
538
539     - (void) hideCursor
540     {
541         if (!cursorHidden)
542         {
543             [NSCursor hide];
544             cursorHidden = TRUE;
545         }
546     }
547
548     - (void) unhideCursor
549     {
550         if (cursorHidden)
551         {
552             [NSCursor unhide];
553             cursorHidden = FALSE;
554         }
555     }
556
557     - (void) setCursor
558     {
559         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
560         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
561         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
562         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
563         CGPoint hotSpot;
564         NSCursor* cursor;
565
566         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
567             hotSpot = CGPointZero;
568         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
569         [image release];
570         [cursor set];
571         [self unhideCursor];
572         [cursor release];
573     }
574
575     - (void) nextCursorFrame:(NSTimer*)theTimer
576     {
577         NSDictionary* frame;
578         NSTimeInterval duration;
579         NSDate* date;
580
581         cursorFrame++;
582         if (cursorFrame >= [cursorFrames count])
583             cursorFrame = 0;
584         [self setCursor];
585
586         frame = [cursorFrames objectAtIndex:cursorFrame];
587         duration = [[frame objectForKey:@"duration"] doubleValue];
588         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
589         [cursorTimer setFireDate:date];
590     }
591
592     - (void) setCursorWithFrames:(NSArray*)frames
593     {
594         if (self.cursorFrames == frames)
595             return;
596
597         self.cursorFrames = frames;
598         cursorFrame = 0;
599         [cursorTimer invalidate];
600         self.cursorTimer = nil;
601
602         if ([frames count])
603         {
604             if ([frames count] > 1)
605             {
606                 NSDictionary* frame = [frames objectAtIndex:0];
607                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
608                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
609                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
610                                                              interval:1000000
611                                                                target:self
612                                                              selector:@selector(nextCursorFrame:)
613                                                              userInfo:nil
614                                                               repeats:YES] autorelease];
615                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
616             }
617
618             [self setCursor];
619         }
620     }
621
622     /*
623      * ---------- Cursor clipping methods ----------
624      *
625      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
626      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
627      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
628      * general case, we leverage that.  We disassociate mouse movements from
629      * the cursor position and then move the cursor manually, keeping it within
630      * the clipping rectangle.
631      *
632      * Moving the cursor manually isn't enough.  We need to modify the event
633      * stream so that the events have the new location, too.  We need to do
634      * this at a point before the events enter Cocoa, so that Cocoa will assign
635      * the correct window to the event.  So, we install a Quartz event tap to
636      * do that.
637      *
638      * Also, there's a complication when we move the cursor.  We use
639      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
640      * events, but the change of cursor position is incorporated into the
641      * deltas of the next mouse move event.  When the mouse is disassociated
642      * from the cursor position, we need the deltas to only reflect actual
643      * device movement, not programmatic changes.  So, the event tap cancels
644      * out the change caused by our calls to CGWarpMouseCursorPosition().
645      */
646     - (void) clipCursorLocation:(CGPoint*)location
647     {
648         if (location->x < CGRectGetMinX(cursorClipRect))
649             location->x = CGRectGetMinX(cursorClipRect);
650         if (location->y < CGRectGetMinY(cursorClipRect))
651             location->y = CGRectGetMinY(cursorClipRect);
652         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
653             location->x = CGRectGetMaxX(cursorClipRect) - 1;
654         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
655             location->y = CGRectGetMaxY(cursorClipRect) - 1;
656     }
657
658     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
659     {
660         CGPoint oldLocation;
661
662         if (currentLocation)
663             oldLocation = *currentLocation;
664         else
665             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
666
667         if (!CGPointEqualToPoint(oldLocation, *newLocation))
668         {
669             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
670             CGError err;
671
672             warpRecord.from = oldLocation;
673             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
674
675             /* Actually move the cursor. */
676             err = CGWarpMouseCursorPosition(*newLocation);
677             if (err != kCGErrorSuccess)
678                 return FALSE;
679
680             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
681             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
682
683             if (!CGPointEqualToPoint(oldLocation, *newLocation))
684             {
685                 warpRecord.to = *newLocation;
686                 [warpRecords addObject:warpRecord];
687             }
688         }
689
690         return TRUE;
691     }
692
693     - (BOOL) isMouseMoveEventType:(CGEventType)type
694     {
695         switch(type)
696         {
697         case kCGEventMouseMoved:
698         case kCGEventLeftMouseDragged:
699         case kCGEventRightMouseDragged:
700         case kCGEventOtherMouseDragged:
701             return TRUE;
702         }
703
704         return FALSE;
705     }
706
707     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
708     {
709         int warpsFinished = 0;
710         for (WarpRecord* warpRecord in warpRecords)
711         {
712             if (warpRecord.timeAfter < eventTime ||
713                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
714                 warpsFinished++;
715             else
716                 break;
717         }
718
719         return warpsFinished;
720     }
721
722     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
723                                 type:(CGEventType)type
724                                event:(CGEventRef)event
725     {
726         CGEventTimestamp eventTime;
727         CGPoint eventLocation, cursorLocation;
728
729         if (type == kCGEventTapDisabledByUserInput)
730             return event;
731         if (type == kCGEventTapDisabledByTimeout)
732         {
733             CGEventTapEnable(cursorClippingEventTap, TRUE);
734             return event;
735         }
736
737         if (!clippingCursor)
738             return event;
739
740         eventTime = CGEventGetTimestamp(event);
741         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
742
743         eventLocation = CGEventGetLocation(event);
744
745         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
746
747         if ([self isMouseMoveEventType:type])
748         {
749             double deltaX, deltaY;
750             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
751             int i;
752
753             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
754             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
755
756             for (i = 0; i < warpsFinished; i++)
757             {
758                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
759                 deltaX -= warpRecord.to.x - warpRecord.from.x;
760                 deltaY -= warpRecord.to.y - warpRecord.from.y;
761                 [warpRecords removeObjectAtIndex:0];
762             }
763
764             if (warpsFinished)
765             {
766                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
767                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
768             }
769
770             synthesizedLocation.x += deltaX;
771             synthesizedLocation.y += deltaY;
772         }
773
774         // If the event is destined for another process, don't clip it.  This may
775         // happen if the user activates Exposé or Mission Control.  In that case,
776         // our app does not resign active status, so clipping is still in effect,
777         // but the cursor should not actually be clipped.
778         //
779         // In addition, the fact that mouse moves may have been delivered to a
780         // different process means we have to treat the next one we receive as
781         // absolute rather than relative.
782         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
783             [self clipCursorLocation:&synthesizedLocation];
784         else
785             lastSetCursorPositionTime = lastEventTapEventTime;
786
787         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
788         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
789             CGEventSetLocation(event, synthesizedLocation);
790
791         return event;
792     }
793
794     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
795                                        CGEventRef event, void *refcon)
796     {
797         WineApplication* app = refcon;
798         return [app eventTapWithProxy:proxy type:type event:event];
799     }
800
801     - (BOOL) installEventTap
802     {
803         ProcessSerialNumber psn;
804         OSErr err;
805         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
806                            CGEventMaskBit(kCGEventLeftMouseUp)          |
807                            CGEventMaskBit(kCGEventRightMouseDown)       |
808                            CGEventMaskBit(kCGEventRightMouseUp)         |
809                            CGEventMaskBit(kCGEventMouseMoved)           |
810                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
811                            CGEventMaskBit(kCGEventRightMouseDragged)    |
812                            CGEventMaskBit(kCGEventOtherMouseDown)       |
813                            CGEventMaskBit(kCGEventOtherMouseUp)         |
814                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
815                            CGEventMaskBit(kCGEventScrollWheel);
816         CFRunLoopSourceRef source;
817         void* appServices;
818         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
819
820         if (cursorClippingEventTap)
821             return TRUE;
822
823         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
824         // framework with dlsym() because the Win32 function of the same name
825         // obscures it.
826         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
827         if (!appServices)
828             return FALSE;
829
830         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
831         if (!pGetCurrentProcess)
832         {
833             dlclose(appServices);
834             return FALSE;
835         }
836
837         err = pGetCurrentProcess(&psn);
838         dlclose(appServices);
839         if (err != noErr)
840             return FALSE;
841
842         // We create an annotated session event tap rather than a process-specific
843         // event tap because we need to programmatically move the cursor even when
844         // mouse moves are directed to other processes.  We disable our tap when
845         // other processes are active, but things like Exposé are handled by other
846         // processes even when we remain active.
847         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
848             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
849         if (!cursorClippingEventTap)
850             return FALSE;
851
852         CGEventTapEnable(cursorClippingEventTap, FALSE);
853
854         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
855         if (!source)
856         {
857             CFRelease(cursorClippingEventTap);
858             cursorClippingEventTap = NULL;
859             return FALSE;
860         }
861
862         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
863         CFRelease(source);
864         return TRUE;
865     }
866
867     - (BOOL) setCursorPosition:(CGPoint)pos
868     {
869         BOOL ret;
870
871         if (clippingCursor)
872         {
873             [self clipCursorLocation:&pos];
874
875             synthesizedLocation = pos;
876             ret = [self warpCursorTo:&synthesizedLocation from:NULL];
877             if (ret)
878             {
879                 // We want to discard mouse-move events that have already been
880                 // through the event tap, because it's too late to account for
881                 // the setting of the cursor position with them.  However, the
882                 // events that may be queued with times after that but before
883                 // the above warp can still be used.  So, use the last event
884                 // tap event time so that -sendEvent: doesn't discard them.
885                 lastSetCursorPositionTime = lastEventTapEventTime;
886             }
887         }
888         else
889         {
890             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
891             if (ret)
892                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
893         }
894
895         if (ret)
896         {
897             WineEventQueue* queue;
898
899             // Discard all pending mouse move events.
900             [eventQueuesLock lock];
901             for (queue in eventQueues)
902             {
903                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
904                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
905                                        forWindow:nil];
906             }
907             [eventQueuesLock unlock];
908         }
909
910         return ret;
911     }
912
913     - (void) activateCursorClipping
914     {
915         if (clippingCursor)
916         {
917             CGEventTapEnable(cursorClippingEventTap, TRUE);
918             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
919         }
920     }
921
922     - (void) deactivateCursorClipping
923     {
924         if (clippingCursor)
925         {
926             CGEventTapEnable(cursorClippingEventTap, FALSE);
927             [warpRecords removeAllObjects];
928             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
929         }
930     }
931
932     - (BOOL) startClippingCursor:(CGRect)rect
933     {
934         CGError err;
935
936         if (!cursorClippingEventTap && ![self installEventTap])
937             return FALSE;
938
939         err = CGAssociateMouseAndMouseCursorPosition(false);
940         if (err != kCGErrorSuccess)
941             return FALSE;
942
943         clippingCursor = TRUE;
944         cursorClipRect = rect;
945         if ([self isActive])
946             [self activateCursorClipping];
947
948         return TRUE;
949     }
950
951     - (BOOL) stopClippingCursor
952     {
953         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
954         if (err != kCGErrorSuccess)
955             return FALSE;
956
957         [self deactivateCursorClipping];
958         clippingCursor = FALSE;
959
960         return TRUE;
961     }
962
963
964     /*
965      * ---------- NSApplication method overrides ----------
966      */
967     - (void) sendEvent:(NSEvent*)anEvent
968     {
969         NSEventType type = [anEvent type];
970         if (type == NSFlagsChanged)
971             self.lastFlagsChanged = anEvent;
972
973         [super sendEvent:anEvent];
974
975         if (type == NSMouseMoved || type == NSLeftMouseDragged ||
976             type == NSRightMouseDragged || type == NSOtherMouseDragged)
977         {
978             WineWindow* targetWindow;
979
980             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
981                event indicates its window is the main window, even if the cursor is
982                over a different window.  Find the actual WineWindow that is under the
983                cursor and post the event as being for that window. */
984             if (type == NSMouseMoved)
985             {
986                 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
987                 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
988                 NSInteger windowUnderNumber;
989
990                 windowUnderNumber = [NSWindow windowNumberAtPoint:point
991                                       belowWindowWithWindowNumber:0];
992                 targetWindow = (WineWindow*)[self windowWithWindowNumber:windowUnderNumber];
993             }
994             else
995                 targetWindow = (WineWindow*)[anEvent window];
996
997             if ([targetWindow isKindOfClass:[WineWindow class]])
998             {
999                 BOOL absolute = forceNextMouseMoveAbsolute || (targetWindow != lastTargetWindow);
1000                 forceNextMouseMoveAbsolute = FALSE;
1001
1002                 // If we recently warped the cursor (other than in our cursor-clipping
1003                 // event tap), discard mouse move events until we see an event which is
1004                 // later than that time.
1005                 if (lastSetCursorPositionTime)
1006                 {
1007                     if ([anEvent timestamp] <= lastSetCursorPositionTime)
1008                         return;
1009
1010                     lastSetCursorPositionTime = 0;
1011                     absolute = TRUE;
1012                 }
1013
1014                 [targetWindow postMouseMovedEvent:anEvent absolute:absolute];
1015                 lastTargetWindow = targetWindow;
1016             }
1017             else if (lastTargetWindow)
1018             {
1019                 [[NSCursor arrowCursor] set];
1020                 [self unhideCursor];
1021                 lastTargetWindow = nil;
1022             }
1023         }
1024         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1025                  type == NSRightMouseDown || type == NSRightMouseUp ||
1026                  type == NSOtherMouseDown || type == NSOtherMouseUp ||
1027                  type == NSScrollWheel)
1028         {
1029             // Since mouse button and scroll wheel events deliver absolute cursor
1030             // position, the accumulating delta from move events is invalidated.
1031             // Make sure next mouse move event starts over from an absolute baseline.
1032             forceNextMouseMoveAbsolute = TRUE;
1033         }
1034     }
1035
1036
1037     /*
1038      * ---------- NSApplicationDelegate methods ----------
1039      */
1040     - (void)applicationDidBecomeActive:(NSNotification *)notification
1041     {
1042         [self activateCursorClipping];
1043
1044         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1045             WineWindow* window = obj;
1046             if ([window levelWhenActive] != [window level])
1047                 [window setLevel:[window levelWhenActive]];
1048         }];
1049
1050         // If a Wine process terminates abruptly while it has the display captured
1051         // and switched to a different resolution, Mac OS X will uncapture the
1052         // displays and switch their resolutions back.  However, the other Wine
1053         // processes won't have their notion of the desktop rect changed back.
1054         // This can lead them to refuse to draw or acknowledge clicks in certain
1055         // portions of their windows.
1056         //
1057         // To solve this, we synthesize a displays-changed event whenever we're
1058         // activated.  This will provoke a re-synchronization of Wine's notion of
1059         // the desktop rect with the actual state.
1060         [self sendDisplaysChanged:TRUE];
1061
1062         // The cursor probably moved while we were inactive.  Accumulated mouse
1063         // movement deltas are invalidated.  Make sure the next mouse move event
1064         // starts over from an absolute baseline.
1065         forceNextMouseMoveAbsolute = TRUE;
1066     }
1067
1068     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1069     {
1070         primaryScreenHeightValid = FALSE;
1071         [self sendDisplaysChanged:FALSE];
1072
1073         // When the display configuration changes, the cursor position may jump.
1074         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1075         // mouse move event starts over from an absolute baseline.
1076         forceNextMouseMoveAbsolute = TRUE;
1077     }
1078
1079     - (void)applicationDidResignActive:(NSNotification *)notification
1080     {
1081         macdrv_event event;
1082         WineEventQueue* queue;
1083
1084         [self invalidateGotFocusEvents];
1085
1086         event.type = APP_DEACTIVATED;
1087         event.window = NULL;
1088
1089         [eventQueuesLock lock];
1090         for (queue in eventQueues)
1091             [queue postEvent:&event];
1092         [eventQueuesLock unlock];
1093     }
1094
1095     - (void)applicationWillFinishLaunching:(NSNotification *)notification
1096     {
1097         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1098
1099         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1100                         object:nil
1101                          queue:nil
1102                     usingBlock:^(NSNotification *note){
1103             NSWindow* window = [note object];
1104             [keyWindows removeObjectIdenticalTo:window];
1105             [keyWindows insertObject:window atIndex:0];
1106         }];
1107
1108         [nc addObserverForName:NSWindowWillCloseNotification
1109                         object:nil
1110                          queue:[NSOperationQueue mainQueue]
1111                     usingBlock:^(NSNotification *note){
1112             NSWindow* window = [note object];
1113             [keyWindows removeObjectIdenticalTo:window];
1114             [orderedWineWindows removeObjectIdenticalTo:window];
1115             if (window == lastTargetWindow)
1116                 lastTargetWindow = nil;
1117         }];
1118
1119         [nc addObserver:self
1120                selector:@selector(keyboardSelectionDidChange)
1121                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1122                  object:nil];
1123
1124         /* The above notification isn't sent unless the NSTextInputContext
1125            class has initialized itself.  Poke it. */
1126         [NSTextInputContext self];
1127
1128         self.keyboardType = LMGetKbdType();
1129     }
1130
1131     - (void)applicationWillResignActive:(NSNotification *)notification
1132     {
1133         [self deactivateCursorClipping];
1134
1135         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1136             WineWindow* window = obj;
1137             NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1138             if ([window level] > level)
1139                 [window setLevel:level];
1140         }];
1141     }
1142
1143 /***********************************************************************
1144  *              PerformRequest
1145  *
1146  * Run-loop-source perform callback.  Pull request blocks from the
1147  * array of queued requests and invoke them.
1148  */
1149 static void PerformRequest(void *info)
1150 {
1151     WineApplication* app = (WineApplication*)NSApp;
1152
1153     for (;;)
1154     {
1155         __block dispatch_block_t block;
1156
1157         dispatch_sync(app->requestsManipQueue, ^{
1158             if ([app->requests count])
1159             {
1160                 block = (dispatch_block_t)[[app->requests objectAtIndex:0] retain];
1161                 [app->requests removeObjectAtIndex:0];
1162             }
1163             else
1164                 block = nil;
1165         });
1166
1167         if (!block)
1168             break;
1169
1170         block();
1171         [block release];
1172     }
1173 }
1174
1175 /***********************************************************************
1176  *              OnMainThreadAsync
1177  *
1178  * Run a block on the main thread asynchronously.
1179  */
1180 void OnMainThreadAsync(dispatch_block_t block)
1181 {
1182     WineApplication* app = (WineApplication*)NSApp;
1183
1184     block = [block copy];
1185     dispatch_sync(app->requestsManipQueue, ^{
1186         [app->requests addObject:block];
1187     });
1188     [block release];
1189     CFRunLoopSourceSignal(app->requestSource);
1190     CFRunLoopWakeUp(CFRunLoopGetMain());
1191 }
1192
1193 @end
1194
1195 /***********************************************************************
1196  *              LogError
1197  */
1198 void LogError(const char* func, NSString* format, ...)
1199 {
1200     va_list args;
1201     va_start(args, format);
1202     LogErrorv(func, format, args);
1203     va_end(args);
1204 }
1205
1206 /***********************************************************************
1207  *              LogErrorv
1208  */
1209 void LogErrorv(const char* func, NSString* format, va_list args)
1210 {
1211     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1212     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1213     [message release];
1214 }
1215
1216 /***********************************************************************
1217  *              macdrv_window_rejected_focus
1218  *
1219  * Pass focus to the next window that hasn't already rejected this same
1220  * WINDOW_GOT_FOCUS event.
1221  */
1222 void macdrv_window_rejected_focus(const macdrv_event *event)
1223 {
1224     OnMainThread(^{
1225         [NSApp windowRejectedFocusEvent:event];
1226     });
1227 }
1228
1229 /***********************************************************************
1230  *              macdrv_get_keyboard_layout
1231  *
1232  * Returns the keyboard layout uchr data.
1233  */
1234 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1235 {
1236     __block CFDataRef result = NULL;
1237
1238     OnMainThread(^{
1239         TISInputSourceRef inputSource;
1240
1241         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1242         if (inputSource)
1243         {
1244             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1245                                 kTISPropertyUnicodeKeyLayoutData);
1246             result = CFDataCreateCopy(NULL, uchr);
1247             CFRelease(inputSource);
1248
1249             *keyboard_type = ((WineApplication*)NSApp).keyboardType;
1250             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1251         }
1252     });
1253
1254     return result;
1255 }
1256
1257 /***********************************************************************
1258  *              macdrv_beep
1259  *
1260  * Play the beep sound configured by the user in System Preferences.
1261  */
1262 void macdrv_beep(void)
1263 {
1264     OnMainThreadAsync(^{
1265         NSBeep();
1266     });
1267 }
1268
1269 /***********************************************************************
1270  *              macdrv_set_display_mode
1271  */
1272 int macdrv_set_display_mode(const struct macdrv_display* display,
1273                             CGDisplayModeRef display_mode)
1274 {
1275     __block int ret;
1276
1277     OnMainThread(^{
1278         ret = [NSApp setMode:display_mode forDisplay:display->displayID];
1279     });
1280
1281     return ret;
1282 }
1283
1284 /***********************************************************************
1285  *              macdrv_set_cursor
1286  *
1287  * Set the cursor.
1288  *
1289  * If name is non-NULL, it is a selector for a class method on NSCursor
1290  * identifying the cursor to set.  In that case, frames is ignored.  If
1291  * name is NULL, then frames is used.
1292  *
1293  * frames is an array of dictionaries.  Each dictionary is a frame of
1294  * an animated cursor.  Under the key "image" is a CGImage for the
1295  * frame.  Under the key "duration" is a CFNumber time interval, in
1296  * seconds, for how long that frame is presented before proceeding to
1297  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
1298  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1299  * This is the hot spot, measured in pixels down and to the right of the
1300  * top-left corner of the image.
1301  *
1302  * If the array has exactly 1 element, the cursor is static, not
1303  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
1304  */
1305 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1306 {
1307     SEL sel;
1308
1309     sel = NSSelectorFromString((NSString*)name);
1310     if (sel)
1311     {
1312         OnMainThreadAsync(^{
1313             NSCursor* cursor = [NSCursor performSelector:sel];
1314             [NSApp setCursorWithFrames:nil];
1315             [cursor set];
1316             [NSApp unhideCursor];
1317         });
1318     }
1319     else
1320     {
1321         NSArray* nsframes = (NSArray*)frames;
1322         if ([nsframes count])
1323         {
1324             OnMainThreadAsync(^{
1325                 [NSApp setCursorWithFrames:nsframes];
1326             });
1327         }
1328         else
1329         {
1330             OnMainThreadAsync(^{
1331                 [NSApp setCursorWithFrames:nil];
1332                 [NSApp hideCursor];
1333             });
1334         }
1335     }
1336 }
1337
1338 /***********************************************************************
1339  *              macdrv_get_cursor_position
1340  *
1341  * Obtains the current cursor position.  Returns zero on failure,
1342  * non-zero on success.
1343  */
1344 int macdrv_get_cursor_position(CGPoint *pos)
1345 {
1346     OnMainThread(^{
1347         NSPoint location = [NSEvent mouseLocation];
1348         location = [NSApp flippedMouseLocation:location];
1349         *pos = NSPointToCGPoint(location);
1350     });
1351
1352     return TRUE;
1353 }
1354
1355 /***********************************************************************
1356  *              macdrv_set_cursor_position
1357  *
1358  * Sets the cursor position without generating events.  Returns zero on
1359  * failure, non-zero on success.
1360  */
1361 int macdrv_set_cursor_position(CGPoint pos)
1362 {
1363     __block int ret;
1364
1365     OnMainThread(^{
1366         ret = [NSApp setCursorPosition:pos];
1367     });
1368
1369     return ret;
1370 }
1371
1372 /***********************************************************************
1373  *              macdrv_clip_cursor
1374  *
1375  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
1376  * to or larger than the whole desktop region, the cursor is unclipped.
1377  * Returns zero on failure, non-zero on success.
1378  */
1379 int macdrv_clip_cursor(CGRect rect)
1380 {
1381     __block int ret;
1382
1383     OnMainThread(^{
1384         BOOL clipping = FALSE;
1385
1386         if (!CGRectIsInfinite(rect))
1387         {
1388             NSRect nsrect = NSRectFromCGRect(rect);
1389             NSScreen* screen;
1390
1391             /* Convert the rectangle from top-down coords to bottom-up. */
1392             [NSApp flipRect:&nsrect];
1393
1394             clipping = FALSE;
1395             for (screen in [NSScreen screens])
1396             {
1397                 if (!NSContainsRect(nsrect, [screen frame]))
1398                 {
1399                     clipping = TRUE;
1400                     break;
1401                 }
1402             }
1403         }
1404
1405         if (clipping)
1406             ret = [NSApp startClippingCursor:rect];
1407         else
1408             ret = [NSApp stopClippingCursor];
1409     });
1410
1411     return ret;
1412 }