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