winemac: Implement simpler way to find front Wine window.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob67984cde6e6e13e7fc0e3d958f1ade1874bca060
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  */
21 #import <Carbon/Carbon.h>
22 #include <dlfcn.h>
24 #import "cocoa_app.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
32 int macdrv_err_on;
35 @implementation WineApplication
37 @synthesize wineController;
39     - (void) sendEvent:(NSEvent*)anEvent
40     {
41         if (![wineController handleEvent:anEvent])
42         {
43             [super sendEvent:anEvent];
44             [wineController didSendEvent:anEvent];
45         }
46     }
48     - (void) setWineController:(WineApplicationController*)newController
49     {
50         wineController = newController;
51         [self setDelegate:wineController];
52     }
54 @end
57 @interface WarpRecord : NSObject
59     CGEventTimestamp timeBefore, timeAfter;
60     CGPoint from, to;
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
68 @end
71 @implementation WarpRecord
73 @synthesize timeBefore, timeAfter, from, to;
75 @end;
78 @interface WineApplicationController ()
80 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
81 @property (copy, nonatomic) NSArray* cursorFrames;
82 @property (retain, nonatomic) NSTimer* cursorTimer;
83 @property (retain, nonatomic) NSImage* applicationIcon;
84 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
85 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
87     - (void) setupObservations;
88     - (void) applicationDidBecomeActive:(NSNotification *)notification;
90     static void PerformRequest(void *info);
92 @end
95 @implementation WineApplicationController
97     @synthesize keyboardType, lastFlagsChanged;
98     @synthesize orderedWineWindows, applicationIcon;
99     @synthesize cursorFrames, cursorTimer;
100     @synthesize mouseCaptureWindow;
102     + (void) initialize
103     {
104         if (self == [WineApplicationController class])
105         {
106             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
107                                       @"", @"NSQuotedKeystrokeBinding",
108                                       @"", @"NSRepeatCountBinding",
109                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
110                                       nil];
111             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
112         }
113     }
115     + (WineApplicationController*) sharedController
116     {
117         static WineApplicationController* sharedController;
118         static dispatch_once_t once;
120         dispatch_once(&once, ^{
121             sharedController = [[self alloc] init];
122         });
124         return sharedController;
125     }
127     - (id) init
128     {
129         self = [super init];
130         if (self != nil)
131         {
132             CFRunLoopSourceContext context = { 0 };
133             context.perform = PerformRequest;
134             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
135             if (!requestSource)
136             {
137                 [self release];
138                 return nil;
139             }
140             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
141             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
143             requests =  [[NSMutableArray alloc] init];
144             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
146             eventQueues = [[NSMutableArray alloc] init];
147             eventQueuesLock = [[NSLock alloc] init];
149             keyWindows = [[NSMutableArray alloc] init];
150             orderedWineWindows = [[NSMutableArray alloc] init];
152             originalDisplayModes = [[NSMutableDictionary alloc] init];
154             warpRecords = [[NSMutableArray alloc] init];
156             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
157                 !keyWindows || !orderedWineWindows || !originalDisplayModes || !warpRecords)
158             {
159                 [self release];
160                 return nil;
161             }
163             [self setupObservations];
165             keyboardType = LMGetKbdType();
167             if ([NSApp isActive])
168                 [self applicationDidBecomeActive:nil];
169         }
170         return self;
171     }
173     - (void) dealloc
174     {
175         [screenFrameCGRects release];
176         [applicationIcon release];
177         [warpRecords release];
178         [cursorTimer release];
179         [cursorFrames release];
180         [originalDisplayModes release];
181         [orderedWineWindows release];
182         [keyWindows release];
183         [eventQueues release];
184         [eventQueuesLock release];
185         if (requestsManipQueue) dispatch_release(requestsManipQueue);
186         [requests release];
187         if (requestSource)
188         {
189             CFRunLoopSourceInvalidate(requestSource);
190             CFRelease(requestSource);
191         }
192         [super dealloc];
193     }
195     - (void) transformProcessToForeground
196     {
197         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
198         {
199             NSMenu* mainMenu;
200             NSMenu* submenu;
201             NSString* bundleName;
202             NSString* title;
203             NSMenuItem* item;
205             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
206             [NSApp activateIgnoringOtherApps:YES];
208             mainMenu = [[[NSMenu alloc] init] autorelease];
210             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
211             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
212             if ([bundleName length])
213                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
214             else
215                 title = @"Quit";
216             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
217             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
218             item = [[[NSMenuItem alloc] init] autorelease];
219             [item setTitle:@"Wine"];
220             [item setSubmenu:submenu];
221             [mainMenu addItem:item];
223             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
224             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
225             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
226             [submenu addItem:[NSMenuItem separatorItem]];
227             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
228             item = [[[NSMenuItem alloc] init] autorelease];
229             [item setTitle:@"Window"];
230             [item setSubmenu:submenu];
231             [mainMenu addItem:item];
233             [NSApp setMainMenu:mainMenu];
234             [NSApp setWindowsMenu:submenu];
236             [NSApp setApplicationIconImage:self.applicationIcon];
237         }
238     }
240     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
241     {
242         PerformRequest(NULL);
244         do
245         {
246             if (processEvents)
247             {
248                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
249                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
250                                                     untilDate:timeout
251                                                        inMode:NSDefaultRunLoopMode
252                                                       dequeue:YES];
253                 if (event)
254                     [NSApp sendEvent:event];
255                 [pool release];
256             }
257             else
258                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
259         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
261         return *done;
262     }
264     - (BOOL) registerEventQueue:(WineEventQueue*)queue
265     {
266         [eventQueuesLock lock];
267         [eventQueues addObject:queue];
268         [eventQueuesLock unlock];
269         return TRUE;
270     }
272     - (void) unregisterEventQueue:(WineEventQueue*)queue
273     {
274         [eventQueuesLock lock];
275         [eventQueues removeObjectIdenticalTo:queue];
276         [eventQueuesLock unlock];
277     }
279     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
280     {
281         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
282     }
284     - (double) ticksForEventTime:(NSTimeInterval)eventTime
285     {
286         return (eventTime + eventTimeAdjustment) * 1000;
287     }
289     /* Invalidate old focus offers across all queues. */
290     - (void) invalidateGotFocusEvents
291     {
292         WineEventQueue* queue;
294         windowFocusSerial++;
296         [eventQueuesLock lock];
297         for (queue in eventQueues)
298         {
299             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
300                                    forWindow:nil];
301         }
302         [eventQueuesLock unlock];
303     }
305     - (void) windowGotFocus:(WineWindow*)window
306     {
307         macdrv_event* event;
309         [self invalidateGotFocusEvents];
311         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
312         event->window_got_focus.serial = windowFocusSerial;
313         if (triedWindows)
314             event->window_got_focus.tried_windows = [triedWindows retain];
315         else
316             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
317         [window.queue postEvent:event];
318         macdrv_release_event(event);
319     }
321     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
322     {
323         if (event->window_got_focus.serial == windowFocusSerial)
324         {
325             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
326             [triedWindows addObject:(WineWindow*)event->window];
327             for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWineWindows]])
328             {
329                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
330                 {
331                     [window makeKeyWindow];
332                     break;
333                 }
334             }
335             triedWindows = nil;
336         }
337     }
339     - (void) keyboardSelectionDidChange
340     {
341         TISInputSourceRef inputSource;
343         inputSourceIsInputMethodValid = FALSE;
345         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
346         if (inputSource)
347         {
348             CFDataRef uchr;
349             uchr = TISGetInputSourceProperty(inputSource,
350                     kTISPropertyUnicodeKeyLayoutData);
351             if (uchr)
352             {
353                 macdrv_event* event;
354                 WineEventQueue* queue;
356                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
357                 event->keyboard_changed.keyboard_type = self.keyboardType;
358                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
359                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
361                 if (event->keyboard_changed.uchr)
362                 {
363                     [eventQueuesLock lock];
365                     for (queue in eventQueues)
366                         [queue postEvent:event];
368                     [eventQueuesLock unlock];
369                 }
371                 macdrv_release_event(event);
372             }
374             CFRelease(inputSource);
375         }
376     }
378     - (CGFloat) primaryScreenHeight
379     {
380         if (!primaryScreenHeightValid)
381         {
382             NSArray* screens = [NSScreen screens];
383             NSUInteger count = [screens count];
384             if (count)
385             {
386                 NSUInteger size;
387                 CGRect* rect;
388                 NSScreen* screen;
390                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
391                 primaryScreenHeightValid = TRUE;
393                 size = count * sizeof(CGRect);
394                 if (!screenFrameCGRects)
395                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
396                 else
397                     [screenFrameCGRects setLength:size];
399                 rect = [screenFrameCGRects mutableBytes];
400                 for (screen in screens)
401                 {
402                     CGRect temp = NSRectToCGRect([screen frame]);
403                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
404                     *rect++ = temp;
405                 }
406             }
407             else
408                 return 1280; /* arbitrary value */
409         }
411         return primaryScreenHeight;
412     }
414     - (NSPoint) flippedMouseLocation:(NSPoint)point
415     {
416         /* This relies on the fact that Cocoa's mouse location points are
417            actually off by one (precisely because they were flipped from
418            Quartz screen coordinates using this same technique). */
419         point.y = [self primaryScreenHeight] - point.y;
420         return point;
421     }
423     - (void) flipRect:(NSRect*)rect
424     {
425         // We don't use -primaryScreenHeight here so there's no chance of having
426         // out-of-date cached info.  This method is called infrequently enough
427         // that getting the screen height each time is not prohibitively expensive.
428         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
429     }
431     - (void) wineWindow:(WineWindow*)window
432                 ordered:(NSWindowOrderingMode)order
433              relativeTo:(WineWindow*)otherWindow
434     {
435         NSUInteger index;
437         switch (order)
438         {
439             case NSWindowAbove:
440                 [window retain];
441                 [orderedWineWindows removeObjectIdenticalTo:window];
442                 if (otherWindow)
443                 {
444                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
445                     if (index == NSNotFound)
446                         index = 0;
447                 }
448                 else
449                 {
450                     index = 0;
451                     for (otherWindow in orderedWineWindows)
452                     {
453                         if ([otherWindow levelWhenActive] <= [window levelWhenActive])
454                             break;
455                         index++;
456                     }
457                 }
458                 [orderedWineWindows insertObject:window atIndex:index];
459                 [window release];
460                 break;
461             case NSWindowBelow:
462                 [window retain];
463                 [orderedWineWindows removeObjectIdenticalTo:window];
464                 if (otherWindow)
465                 {
466                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
467                     if (index == NSNotFound)
468                         index = [orderedWineWindows count];
469                 }
470                 else
471                 {
472                     index = 0;
473                     for (otherWindow in orderedWineWindows)
474                     {
475                         if ([otherWindow levelWhenActive] < [window levelWhenActive])
476                             break;
477                         index++;
478                     }
479                 }
480                 [orderedWineWindows insertObject:window atIndex:index];
481                 [window release];
482                 break;
483             case NSWindowOut:
484             default:
485                 break;
486         }
487     }
489     - (WineWindow*) frontWineWindow
490     {
491         NSNumber* windowNumber;
492         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
493         {
494             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
495             if ([window isKindOfClass:[WineWindow class]] && [window screen])
496                 return (WineWindow*)window;
497         }
499         return nil;
500     }
502     - (void) sendDisplaysChanged:(BOOL)activating
503     {
504         macdrv_event* event;
505         WineEventQueue* queue;
507         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
508         event->displays_changed.activating = activating;
510         [eventQueuesLock lock];
512         // If we're activating, then we just need one of our threads to get the
513         // event, so it can send it directly to the desktop window.  Otherwise,
514         // we need all of the threads to get it because we don't know which owns
515         // the desktop window and only that one will do anything with it.
516         if (activating) event->deliver = 1;
518         for (queue in eventQueues)
519             [queue postEvent:event];
520         [eventQueuesLock unlock];
522         macdrv_release_event(event);
523     }
525     // We can compare two modes directly using CFEqual, but that may require that
526     // they are identical to a level that we don't need.  In particular, when the
527     // OS switches between the integrated and discrete GPUs, the set of display
528     // modes can change in subtle ways.  We're interested in whether two modes
529     // match in their most salient features, even if they aren't identical.
530     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
531     {
532         NSString *encoding1, *encoding2;
533         uint32_t ioflags1, ioflags2, different;
534         double refresh1, refresh2;
536         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
537         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
539         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
540         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
541         if (![encoding1 isEqualToString:encoding2]) return FALSE;
543         ioflags1 = CGDisplayModeGetIOFlags(mode1);
544         ioflags2 = CGDisplayModeGetIOFlags(mode2);
545         different = ioflags1 ^ ioflags2;
546         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
547                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
548             return FALSE;
550         refresh1 = CGDisplayModeGetRefreshRate(mode1);
551         if (refresh1 == 0) refresh1 = 60;
552         refresh2 = CGDisplayModeGetRefreshRate(mode2);
553         if (refresh2 == 0) refresh2 = 60;
554         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
556         return TRUE;
557     }
559     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
560     {
561         CGDisplayModeRef ret = NULL;
562         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
563         for (id candidateModeObject in modes)
564         {
565             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
566             if ([self mode:candidateMode matchesMode:mode])
567             {
568                 ret = candidateMode;
569                 break;
570             }
571         }
572         return ret;
573     }
575     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
576     {
577         BOOL ret = FALSE;
578         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
579         CGDisplayModeRef currentMode, originalMode;
581         currentMode = CGDisplayCopyDisplayMode(displayID);
582         if (!currentMode) // Invalid display ID
583             return FALSE;
585         if ([self mode:mode matchesMode:currentMode]) // Already there!
586         {
587             CGDisplayModeRelease(currentMode);
588             return TRUE;
589         }
591         mode = [self modeMatchingMode:mode forDisplay:displayID];
592         if (!mode)
593         {
594             CGDisplayModeRelease(currentMode);
595             return FALSE;
596         }
598         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
599         if (!originalMode)
600             originalMode = currentMode;
602         if ([self mode:mode matchesMode:originalMode])
603         {
604             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
605             {
606                 CGRestorePermanentDisplayConfiguration();
607                 CGReleaseAllDisplays();
608                 [originalDisplayModes removeAllObjects];
609                 ret = TRUE;
610             }
611             else // ... otherwise, try to restore just the one display
612             {
613                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
614                 {
615                     [originalDisplayModes removeObjectForKey:displayIDKey];
616                     ret = TRUE;
617                 }
618             }
619         }
620         else
621         {
622             if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
623             {
624                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
625                 {
626                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
627                     ret = TRUE;
628                 }
629                 else if (![originalDisplayModes count])
630                 {
631                     CGRestorePermanentDisplayConfiguration();
632                     CGReleaseAllDisplays();
633                 }
634             }
635         }
637         CGDisplayModeRelease(currentMode);
639         if (ret)
640         {
641             [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
642                 [(WineWindow*)obj adjustWindowLevel];
643             }];
644         }
646         return ret;
647     }
649     - (BOOL) areDisplaysCaptured
650     {
651         return ([originalDisplayModes count] > 0);
652     }
654     - (void) hideCursor
655     {
656         if (!cursorHidden)
657         {
658             [NSCursor hide];
659             cursorHidden = TRUE;
660         }
661     }
663     - (void) unhideCursor
664     {
665         if (cursorHidden)
666         {
667             [NSCursor unhide];
668             cursorHidden = FALSE;
669         }
670     }
672     - (void) setCursor
673     {
674         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
675         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
676         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
677         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
678         CGPoint hotSpot;
679         NSCursor* cursor;
681         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
682             hotSpot = CGPointZero;
683         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
684         [image release];
685         [cursor set];
686         [self unhideCursor];
687         [cursor release];
688     }
690     - (void) nextCursorFrame:(NSTimer*)theTimer
691     {
692         NSDictionary* frame;
693         NSTimeInterval duration;
694         NSDate* date;
696         cursorFrame++;
697         if (cursorFrame >= [cursorFrames count])
698             cursorFrame = 0;
699         [self setCursor];
701         frame = [cursorFrames objectAtIndex:cursorFrame];
702         duration = [[frame objectForKey:@"duration"] doubleValue];
703         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
704         [cursorTimer setFireDate:date];
705     }
707     - (void) setCursorWithFrames:(NSArray*)frames
708     {
709         if (self.cursorFrames == frames)
710             return;
712         self.cursorFrames = frames;
713         cursorFrame = 0;
714         [cursorTimer invalidate];
715         self.cursorTimer = nil;
717         if ([frames count])
718         {
719             if ([frames count] > 1)
720             {
721                 NSDictionary* frame = [frames objectAtIndex:0];
722                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
723                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
724                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
725                                                              interval:1000000
726                                                                target:self
727                                                              selector:@selector(nextCursorFrame:)
728                                                              userInfo:nil
729                                                               repeats:YES] autorelease];
730                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
731             }
733             [self setCursor];
734         }
735     }
737     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
738     {
739         NSImage* nsimage = nil;
741         if ([images count])
742         {
743             NSSize bestSize = NSZeroSize;
744             id image;
746             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
748             for (image in images)
749             {
750                 CGImageRef cgimage = (CGImageRef)image;
751                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
752                 if (imageRep)
753                 {
754                     NSSize size = [imageRep size];
756                     [nsimage addRepresentation:imageRep];
757                     [imageRep release];
759                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
760                         bestSize = size;
761                 }
762             }
764             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
765                 [nsimage setSize:bestSize];
766             else
767                 nsimage = nil;
768         }
770         self.applicationIcon = nsimage;
771         [NSApp setApplicationIconImage:nsimage];
772     }
774     - (void) handleCommandTab
775     {
776         if ([NSApp isActive])
777         {
778             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
779             NSRunningApplication* app;
780             NSRunningApplication* otherValidApp = nil;
782             if ([originalDisplayModes count])
783             {
784                 CGRestorePermanentDisplayConfiguration();
785                 CGReleaseAllDisplays();
786                 [originalDisplayModes removeAllObjects];
787             }
789             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
790             {
791                 if (![app isEqual:thisApp] && !app.terminated &&
792                     app.activationPolicy == NSApplicationActivationPolicyRegular)
793                 {
794                     if (!app.hidden)
795                     {
796                         // There's another visible app.  Just hide ourselves and let
797                         // the system activate the other app.
798                         [NSApp hide:self];
799                         return;
800                     }
802                     if (!otherValidApp)
803                         otherValidApp = app;
804                 }
805             }
807             // Didn't find a visible GUI app.  Try the Finder or, if that's not
808             // running, the first hidden GUI app.  If even that doesn't work, we
809             // just fail to switch and remain the active app.
810             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
811             if (!app) app = otherValidApp;
812             [app unhide];
813             [app activateWithOptions:0];
814         }
815     }
817     /*
818      * ---------- Cursor clipping methods ----------
819      *
820      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
821      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
822      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
823      * general case, we leverage that.  We disassociate mouse movements from
824      * the cursor position and then move the cursor manually, keeping it within
825      * the clipping rectangle.
826      *
827      * Moving the cursor manually isn't enough.  We need to modify the event
828      * stream so that the events have the new location, too.  We need to do
829      * this at a point before the events enter Cocoa, so that Cocoa will assign
830      * the correct window to the event.  So, we install a Quartz event tap to
831      * do that.
832      *
833      * Also, there's a complication when we move the cursor.  We use
834      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
835      * events, but the change of cursor position is incorporated into the
836      * deltas of the next mouse move event.  When the mouse is disassociated
837      * from the cursor position, we need the deltas to only reflect actual
838      * device movement, not programmatic changes.  So, the event tap cancels
839      * out the change caused by our calls to CGWarpMouseCursorPosition().
840      */
841     - (void) clipCursorLocation:(CGPoint*)location
842     {
843         if (location->x < CGRectGetMinX(cursorClipRect))
844             location->x = CGRectGetMinX(cursorClipRect);
845         if (location->y < CGRectGetMinY(cursorClipRect))
846             location->y = CGRectGetMinY(cursorClipRect);
847         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
848             location->x = CGRectGetMaxX(cursorClipRect) - 1;
849         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
850             location->y = CGRectGetMaxY(cursorClipRect) - 1;
851     }
853     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
854     {
855         CGPoint oldLocation;
857         if (currentLocation)
858             oldLocation = *currentLocation;
859         else
860             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
862         if (!CGPointEqualToPoint(oldLocation, *newLocation))
863         {
864             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
865             CGError err;
867             warpRecord.from = oldLocation;
868             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
870             /* Actually move the cursor. */
871             err = CGWarpMouseCursorPosition(*newLocation);
872             if (err != kCGErrorSuccess)
873                 return FALSE;
875             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
876             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
878             if (!CGPointEqualToPoint(oldLocation, *newLocation))
879             {
880                 warpRecord.to = *newLocation;
881                 [warpRecords addObject:warpRecord];
882             }
883         }
885         return TRUE;
886     }
888     - (BOOL) isMouseMoveEventType:(CGEventType)type
889     {
890         switch(type)
891         {
892         case kCGEventMouseMoved:
893         case kCGEventLeftMouseDragged:
894         case kCGEventRightMouseDragged:
895         case kCGEventOtherMouseDragged:
896             return TRUE;
897         }
899         return FALSE;
900     }
902     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
903     {
904         int warpsFinished = 0;
905         for (WarpRecord* warpRecord in warpRecords)
906         {
907             if (warpRecord.timeAfter < eventTime ||
908                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
909                 warpsFinished++;
910             else
911                 break;
912         }
914         return warpsFinished;
915     }
917     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
918                                 type:(CGEventType)type
919                                event:(CGEventRef)event
920     {
921         CGEventTimestamp eventTime;
922         CGPoint eventLocation, cursorLocation;
924         if (type == kCGEventTapDisabledByUserInput)
925             return event;
926         if (type == kCGEventTapDisabledByTimeout)
927         {
928             CGEventTapEnable(cursorClippingEventTap, TRUE);
929             return event;
930         }
932         if (!clippingCursor)
933             return event;
935         eventTime = CGEventGetTimestamp(event);
936         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
938         eventLocation = CGEventGetLocation(event);
940         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
942         if ([self isMouseMoveEventType:type])
943         {
944             double deltaX, deltaY;
945             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
946             int i;
948             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
949             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
951             for (i = 0; i < warpsFinished; i++)
952             {
953                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
954                 deltaX -= warpRecord.to.x - warpRecord.from.x;
955                 deltaY -= warpRecord.to.y - warpRecord.from.y;
956                 [warpRecords removeObjectAtIndex:0];
957             }
959             if (warpsFinished)
960             {
961                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
962                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
963             }
965             synthesizedLocation.x += deltaX;
966             synthesizedLocation.y += deltaY;
967         }
969         // If the event is destined for another process, don't clip it.  This may
970         // happen if the user activates Exposé or Mission Control.  In that case,
971         // our app does not resign active status, so clipping is still in effect,
972         // but the cursor should not actually be clipped.
973         //
974         // In addition, the fact that mouse moves may have been delivered to a
975         // different process means we have to treat the next one we receive as
976         // absolute rather than relative.
977         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
978             [self clipCursorLocation:&synthesizedLocation];
979         else
980             lastSetCursorPositionTime = lastEventTapEventTime;
982         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
983         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
984             CGEventSetLocation(event, synthesizedLocation);
986         return event;
987     }
989     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
990                                        CGEventRef event, void *refcon)
991     {
992         WineApplicationController* controller = refcon;
993         return [controller eventTapWithProxy:proxy type:type event:event];
994     }
996     - (BOOL) installEventTap
997     {
998         ProcessSerialNumber psn;
999         OSErr err;
1000         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1001                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1002                            CGEventMaskBit(kCGEventRightMouseDown)       |
1003                            CGEventMaskBit(kCGEventRightMouseUp)         |
1004                            CGEventMaskBit(kCGEventMouseMoved)           |
1005                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1006                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1007                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1008                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1009                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1010                            CGEventMaskBit(kCGEventScrollWheel);
1011         CFRunLoopSourceRef source;
1012         void* appServices;
1013         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1015         if (cursorClippingEventTap)
1016             return TRUE;
1018         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1019         // framework with dlsym() because the Win32 function of the same name
1020         // obscures it.
1021         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1022         if (!appServices)
1023             return FALSE;
1025         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1026         if (!pGetCurrentProcess)
1027         {
1028             dlclose(appServices);
1029             return FALSE;
1030         }
1032         err = pGetCurrentProcess(&psn);
1033         dlclose(appServices);
1034         if (err != noErr)
1035             return FALSE;
1037         // We create an annotated session event tap rather than a process-specific
1038         // event tap because we need to programmatically move the cursor even when
1039         // mouse moves are directed to other processes.  We disable our tap when
1040         // other processes are active, but things like Exposé are handled by other
1041         // processes even when we remain active.
1042         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1043             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1044         if (!cursorClippingEventTap)
1045             return FALSE;
1047         CGEventTapEnable(cursorClippingEventTap, FALSE);
1049         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1050         if (!source)
1051         {
1052             CFRelease(cursorClippingEventTap);
1053             cursorClippingEventTap = NULL;
1054             return FALSE;
1055         }
1057         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1058         CFRelease(source);
1059         return TRUE;
1060     }
1062     - (BOOL) setCursorPosition:(CGPoint)pos
1063     {
1064         BOOL ret;
1066         if (clippingCursor)
1067         {
1068             [self clipCursorLocation:&pos];
1070             ret = [self warpCursorTo:&pos from:NULL];
1071             synthesizedLocation = pos;
1072             if (ret)
1073             {
1074                 // We want to discard mouse-move events that have already been
1075                 // through the event tap, because it's too late to account for
1076                 // the setting of the cursor position with them.  However, the
1077                 // events that may be queued with times after that but before
1078                 // the above warp can still be used.  So, use the last event
1079                 // tap event time so that -sendEvent: doesn't discard them.
1080                 lastSetCursorPositionTime = lastEventTapEventTime;
1081             }
1082         }
1083         else
1084         {
1085             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1086             if (ret)
1087             {
1088                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1090                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1091                 // the mouse from the cursor position for 0.25 seconds.  This means
1092                 // that mouse movement during that interval doesn't move the cursor
1093                 // and events carry a constant location (the warped-to position)
1094                 // even though they have delta values.  This screws us up because
1095                 // the accumulated deltas we send to Wine don't match any eventual
1096                 // absolute position we send (like with a button press).  We can
1097                 // work around this by simply forcibly reassociating the mouse and
1098                 // cursor position.
1099                 CGAssociateMouseAndMouseCursorPosition(true);
1100             }
1101         }
1103         if (ret)
1104         {
1105             WineEventQueue* queue;
1107             // Discard all pending mouse move events.
1108             [eventQueuesLock lock];
1109             for (queue in eventQueues)
1110             {
1111                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1112                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1113                                        forWindow:nil];
1114                 [queue resetMouseEventPositions:pos];
1115             }
1116             [eventQueuesLock unlock];
1117         }
1119         return ret;
1120     }
1122     - (void) activateCursorClipping
1123     {
1124         if (clippingCursor)
1125         {
1126             CGEventTapEnable(cursorClippingEventTap, TRUE);
1127             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1128         }
1129     }
1131     - (void) deactivateCursorClipping
1132     {
1133         if (clippingCursor)
1134         {
1135             CGEventTapEnable(cursorClippingEventTap, FALSE);
1136             [warpRecords removeAllObjects];
1137             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1138         }
1139     }
1141     - (BOOL) startClippingCursor:(CGRect)rect
1142     {
1143         CGError err;
1145         if (!cursorClippingEventTap && ![self installEventTap])
1146             return FALSE;
1148         err = CGAssociateMouseAndMouseCursorPosition(false);
1149         if (err != kCGErrorSuccess)
1150             return FALSE;
1152         clippingCursor = TRUE;
1153         cursorClipRect = rect;
1154         if ([NSApp isActive])
1155             [self activateCursorClipping];
1157         return TRUE;
1158     }
1160     - (BOOL) stopClippingCursor
1161     {
1162         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1163         if (err != kCGErrorSuccess)
1164             return FALSE;
1166         [self deactivateCursorClipping];
1167         clippingCursor = FALSE;
1169         return TRUE;
1170     }
1172     - (void) handleMouseMove:(NSEvent*)anEvent
1173     {
1174         WineWindow* targetWindow;
1175         BOOL drag = [anEvent type] != NSMouseMoved;
1177         if (mouseCaptureWindow)
1178             targetWindow = mouseCaptureWindow;
1179         else if (drag)
1180             targetWindow = (WineWindow*)[anEvent window];
1181         else
1182         {
1183             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1184                event indicates its window is the main window, even if the cursor is
1185                over a different window.  Find the actual WineWindow that is under the
1186                cursor and post the event as being for that window. */
1187             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1188             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1189             NSInteger windowUnderNumber;
1191             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1192                                   belowWindowWithWindowNumber:0];
1193             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1194         }
1196         if ([targetWindow isKindOfClass:[WineWindow class]])
1197         {
1198             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1199             macdrv_event* event;
1200             BOOL absolute;
1202             // If we recently warped the cursor (other than in our cursor-clipping
1203             // event tap), discard mouse move events until we see an event which is
1204             // later than that time.
1205             if (lastSetCursorPositionTime)
1206             {
1207                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1208                     return;
1210                 lastSetCursorPositionTime = 0;
1211                 forceNextMouseMoveAbsolute = TRUE;
1212             }
1214             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1215             {
1216                 absolute = TRUE;
1217                 forceNextMouseMoveAbsolute = FALSE;
1218             }
1219             else
1220             {
1221                 // Send absolute move events if the cursor is in the interior of
1222                 // its range.  Only send relative moves if the cursor is pinned to
1223                 // the boundaries of where it can go.  We compute the position
1224                 // that's one additional point in the direction of movement.  If
1225                 // that is outside of the clipping rect or desktop region (the
1226                 // union of the screen frames), then we figure the cursor would
1227                 // have moved outside if it could but it was pinned.
1228                 CGPoint computedPoint = point;
1229                 CGFloat deltaX = [anEvent deltaX];
1230                 CGFloat deltaY = [anEvent deltaY];
1232                 if (deltaX > 0.001)
1233                     computedPoint.x++;
1234                 else if (deltaX < -0.001)
1235                     computedPoint.x--;
1237                 if (deltaY > 0.001)
1238                     computedPoint.y++;
1239                 else if (deltaY < -0.001)
1240                     computedPoint.y--;
1242                 // Assume cursor is pinned for now
1243                 absolute = FALSE;
1244                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1245                 {
1246                     const CGRect* rects;
1247                     NSUInteger count, i;
1249                     // Caches screenFrameCGRects if necessary
1250                     [self primaryScreenHeight];
1252                     rects = [screenFrameCGRects bytes];
1253                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1255                     for (i = 0; i < count; i++)
1256                     {
1257                         if (CGRectContainsPoint(rects[i], computedPoint))
1258                         {
1259                             absolute = TRUE;
1260                             break;
1261                         }
1262                     }
1263                 }
1264             }
1266             if (absolute)
1267             {
1268                 if (clippingCursor)
1269                     [self clipCursorLocation:&point];
1271                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1272                 event->mouse_moved.x = point.x;
1273                 event->mouse_moved.y = point.y;
1275                 mouseMoveDeltaX = 0;
1276                 mouseMoveDeltaY = 0;
1277             }
1278             else
1279             {
1280                 /* Add event delta to accumulated delta error */
1281                 /* deltaY is already flipped */
1282                 mouseMoveDeltaX += [anEvent deltaX];
1283                 mouseMoveDeltaY += [anEvent deltaY];
1285                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1286                 event->mouse_moved.x = mouseMoveDeltaX;
1287                 event->mouse_moved.y = mouseMoveDeltaY;
1289                 /* Keep the remainder after integer truncation. */
1290                 mouseMoveDeltaX -= event->mouse_moved.x;
1291                 mouseMoveDeltaY -= event->mouse_moved.y;
1292             }
1294             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1295             {
1296                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1297                 event->mouse_moved.drag = drag;
1299                 [targetWindow.queue postEvent:event];
1300             }
1302             macdrv_release_event(event);
1304             lastTargetWindow = targetWindow;
1305         }
1306         else if (lastTargetWindow)
1307         {
1308             [[NSCursor arrowCursor] set];
1309             [self unhideCursor];
1310             lastTargetWindow = nil;
1311         }
1312     }
1314     - (void) handleMouseButton:(NSEvent*)theEvent
1315     {
1316         WineWindow* window;
1318         if (mouseCaptureWindow)
1319             window = mouseCaptureWindow;
1320         else
1321             window = (WineWindow*)[theEvent window];
1323         if ([window isKindOfClass:[WineWindow class]])
1324         {
1325             NSEventType type = [theEvent type];
1326             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1327             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1328             BOOL process;
1330             if (clippingCursor)
1331                 [self clipCursorLocation:&pt];
1333             if (pressed)
1334             {
1335                 if (mouseCaptureWindow)
1336                     process = TRUE;
1337                 else
1338                 {
1339                     // Test if the click was in the window's content area.
1340                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1341                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1342                     process = NSPointInRect(nspoint, contentRect);
1343                     if (process && [window styleMask] & NSResizableWindowMask)
1344                     {
1345                         // Ignore clicks in the grow box (resize widget).
1346                         HIPoint origin = { 0, 0 };
1347                         HIThemeGrowBoxDrawInfo info = { 0 };
1348                         HIRect bounds;
1349                         OSStatus status;
1351                         info.kind = kHIThemeGrowBoxKindNormal;
1352                         info.direction = kThemeGrowRight | kThemeGrowDown;
1353                         if ([window styleMask] & NSUtilityWindowMask)
1354                             info.size = kHIThemeGrowBoxSizeSmall;
1355                         else
1356                             info.size = kHIThemeGrowBoxSizeNormal;
1358                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1359                         if (status == noErr)
1360                         {
1361                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1362                                                         NSMinY(contentRect),
1363                                                         bounds.size.width,
1364                                                         bounds.size.height);
1365                             process = !NSPointInRect(nspoint, growBox);
1366                         }
1367                     }
1368                 }
1369                 if (process)
1370                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1371             }
1372             else
1373             {
1374                 NSEventType downType = type - 1;
1375                 NSUInteger downMask = NSEventMaskFromType(downType);
1376                 process = (unmatchedMouseDowns & downMask) != 0;
1377                 unmatchedMouseDowns &= ~downMask;
1378             }
1380             if (process)
1381             {
1382                 macdrv_event* event;
1384                 event = macdrv_create_event(MOUSE_BUTTON, window);
1385                 event->mouse_button.button = [theEvent buttonNumber];
1386                 event->mouse_button.pressed = pressed;
1387                 event->mouse_button.x = pt.x;
1388                 event->mouse_button.y = pt.y;
1389                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1391                 [window.queue postEvent:event];
1393                 macdrv_release_event(event);
1394             }
1395         }
1397         // Since mouse button events deliver absolute cursor position, the
1398         // accumulating delta from move events is invalidated.  Make sure
1399         // next mouse move event starts over from an absolute baseline.
1400         // Also, it's at least possible that the title bar widgets (e.g. close
1401         // button, etc.) could enter an internal event loop on a mouse down that
1402         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1403         // dragged events and, after that, any notion of the cursor position
1404         // computed from accumulating deltas would be wrong.
1405         forceNextMouseMoveAbsolute = TRUE;
1406     }
1408     - (void) handleScrollWheel:(NSEvent*)theEvent
1409     {
1410         WineWindow* window;
1412         if (mouseCaptureWindow)
1413             window = mouseCaptureWindow;
1414         else
1415             window = (WineWindow*)[theEvent window];
1417         if ([window isKindOfClass:[WineWindow class]])
1418         {
1419             CGEventRef cgevent = [theEvent CGEvent];
1420             CGPoint pt = CGEventGetLocation(cgevent);
1421             BOOL process;
1423             if (clippingCursor)
1424                 [self clipCursorLocation:&pt];
1426             if (mouseCaptureWindow)
1427                 process = TRUE;
1428             else
1429             {
1430                 // Only process the event if it was in the window's content area.
1431                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1432                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1433                 process = NSPointInRect(nspoint, contentRect);
1434             }
1436             if (process)
1437             {
1438                 macdrv_event* event;
1439                 CGFloat x, y;
1440                 BOOL continuous = FALSE;
1442                 event = macdrv_create_event(MOUSE_SCROLL, window);
1443                 event->mouse_scroll.x = pt.x;
1444                 event->mouse_scroll.y = pt.y;
1445                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1447                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1448                 {
1449                     continuous = TRUE;
1451                     /* Continuous scroll wheel events come from high-precision scrolling
1452                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1453                        For these, we can get more precise data from the CGEvent API. */
1454                     /* Axis 1 is vertical, axis 2 is horizontal. */
1455                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1456                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1457                 }
1458                 else
1459                 {
1460                     double pixelsPerLine = 10;
1461                     CGEventSourceRef source;
1463                     /* The non-continuous values are in units of "lines", not pixels. */
1464                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1465                     {
1466                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1467                         CFRelease(source);
1468                     }
1470                     x = pixelsPerLine * [theEvent deltaX];
1471                     y = pixelsPerLine * [theEvent deltaY];
1472                 }
1474                 /* Mac: negative is right or down, positive is left or up.
1475                    Win32: negative is left or down, positive is right or up.
1476                    So, negate the X scroll value to translate. */
1477                 x = -x;
1479                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1480                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1481                    6 times the pixel value. */
1482                 event->mouse_scroll.x_scroll = 6 * x;
1483                 event->mouse_scroll.y_scroll = 6 * y;
1485                 if (!continuous)
1486                 {
1487                     /* For non-continuous "clicky" wheels, if there was any motion, make
1488                        sure there was at least WHEEL_DELTA motion.  This is so, at slow
1489                        speeds where the system's acceleration curve is actually reducing the
1490                        scroll distance, the user is sure to get some action out of each click.
1491                        For example, this is important for rotating though weapons in a
1492                        first-person shooter. */
1493                     if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1494                         event->mouse_scroll.x_scroll = 120;
1495                     else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1496                         event->mouse_scroll.x_scroll = -120;
1498                     if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1499                         event->mouse_scroll.y_scroll = 120;
1500                     else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1501                         event->mouse_scroll.y_scroll = -120;
1502                 }
1504                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1505                     [window.queue postEvent:event];
1507                 macdrv_release_event(event);
1509                 // Since scroll wheel events deliver absolute cursor position, the
1510                 // accumulating delta from move events is invalidated.  Make sure next
1511                 // mouse move event starts over from an absolute baseline.
1512                 forceNextMouseMoveAbsolute = TRUE;
1513             }
1514         }
1515     }
1517     // Returns TRUE if the event was handled and caller should do nothing more
1518     // with it.  Returns FALSE if the caller should process it as normal and
1519     // then call -didSendEvent:.
1520     - (BOOL) handleEvent:(NSEvent*)anEvent
1521     {
1522         BOOL ret = FALSE;
1523         NSEventType type = [anEvent type];
1525         if (type == NSFlagsChanged)
1526             self.lastFlagsChanged = anEvent;
1527         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1528                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1529         {
1530             [self handleMouseMove:anEvent];
1531             ret = mouseCaptureWindow != nil;
1532         }
1533         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1534                  type == NSRightMouseDown || type == NSRightMouseUp ||
1535                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1536         {
1537             [self handleMouseButton:anEvent];
1538             ret = mouseCaptureWindow != nil;
1539         }
1540         else if (type == NSScrollWheel)
1541         {
1542             [self handleScrollWheel:anEvent];
1543             ret = mouseCaptureWindow != nil;
1544         }
1546         return ret;
1547     }
1549     - (void) didSendEvent:(NSEvent*)anEvent
1550     {
1551         NSEventType type = [anEvent type];
1553         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1554         {
1555             NSUInteger modifiers = [anEvent modifierFlags];
1556             if ((modifiers & NSCommandKeyMask) &&
1557                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1558             {
1559                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1560                 // by the system to switch applications.  If we're seeing it, it's
1561                 // presumably because we've captured the displays, preventing
1562                 // normal application switching.  Do it manually.
1563                 [self handleCommandTab];
1564             }
1565         }
1566     }
1568     - (void) setupObservations
1569     {
1570         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1572         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1573                         object:nil
1574                          queue:nil
1575                     usingBlock:^(NSNotification *note){
1576             NSWindow* window = [note object];
1577             [keyWindows removeObjectIdenticalTo:window];
1578             [keyWindows insertObject:window atIndex:0];
1579         }];
1581         [nc addObserverForName:NSWindowWillCloseNotification
1582                         object:nil
1583                          queue:[NSOperationQueue mainQueue]
1584                     usingBlock:^(NSNotification *note){
1585             NSWindow* window = [note object];
1586             [keyWindows removeObjectIdenticalTo:window];
1587             [orderedWineWindows removeObjectIdenticalTo:window];
1588             if (window == lastTargetWindow)
1589                 lastTargetWindow = nil;
1590             if (window == self.mouseCaptureWindow)
1591                 self.mouseCaptureWindow = nil;
1592         }];
1594         [nc addObserver:self
1595                selector:@selector(keyboardSelectionDidChange)
1596                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1597                  object:nil];
1599         /* The above notification isn't sent unless the NSTextInputContext
1600            class has initialized itself.  Poke it. */
1601         [NSTextInputContext self];
1602     }
1604     - (BOOL) inputSourceIsInputMethod
1605     {
1606         if (!inputSourceIsInputMethodValid)
1607         {
1608             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1609             if (inputSource)
1610             {
1611                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1612                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1613                 CFRelease(inputSource);
1614             }
1615             else
1616                 inputSourceIsInputMethod = FALSE;
1617             inputSourceIsInputMethodValid = TRUE;
1618         }
1620         return inputSourceIsInputMethod;
1621     }
1624     /*
1625      * ---------- NSApplicationDelegate methods ----------
1626      */
1627     - (void)applicationDidBecomeActive:(NSNotification *)notification
1628     {
1629         [self activateCursorClipping];
1631         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1632             WineWindow* window = obj;
1633             if ([window levelWhenActive] != [window level])
1634                 [window setLevel:[window levelWhenActive]];
1635         }];
1637         if (![self frontWineWindow])
1638         {
1639             for (WineWindow* window in [NSApp windows])
1640             {
1641                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1642                 {
1643                     [window deminiaturize:self];
1644                     break;
1645                 }
1646             }
1647         }
1649         // If a Wine process terminates abruptly while it has the display captured
1650         // and switched to a different resolution, Mac OS X will uncapture the
1651         // displays and switch their resolutions back.  However, the other Wine
1652         // processes won't have their notion of the desktop rect changed back.
1653         // This can lead them to refuse to draw or acknowledge clicks in certain
1654         // portions of their windows.
1655         //
1656         // To solve this, we synthesize a displays-changed event whenever we're
1657         // activated.  This will provoke a re-synchronization of Wine's notion of
1658         // the desktop rect with the actual state.
1659         [self sendDisplaysChanged:TRUE];
1661         // The cursor probably moved while we were inactive.  Accumulated mouse
1662         // movement deltas are invalidated.  Make sure the next mouse move event
1663         // starts over from an absolute baseline.
1664         forceNextMouseMoveAbsolute = TRUE;
1665     }
1667     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1668     {
1669         primaryScreenHeightValid = FALSE;
1670         [self sendDisplaysChanged:FALSE];
1672         // When the display configuration changes, the cursor position may jump.
1673         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1674         // mouse move event starts over from an absolute baseline.
1675         forceNextMouseMoveAbsolute = TRUE;
1676     }
1678     - (void)applicationDidResignActive:(NSNotification *)notification
1679     {
1680         macdrv_event* event;
1681         WineEventQueue* queue;
1683         [self invalidateGotFocusEvents];
1685         event = macdrv_create_event(APP_DEACTIVATED, nil);
1687         [eventQueuesLock lock];
1688         for (queue in eventQueues)
1689             [queue postEvent:event];
1690         [eventQueuesLock unlock];
1692         macdrv_release_event(event);
1693     }
1695     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1696     {
1697         NSApplicationTerminateReply ret = NSTerminateNow;
1698         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1699         NSAppleEventDescriptor* desc = [m currentAppleEvent];
1700         macdrv_event* event;
1701         WineEventQueue* queue;
1703         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1704         event->deliver = 1;
1705         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1706         {
1707             case kAELogOut:
1708             case kAEReallyLogOut:
1709                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1710                 break;
1711             case kAEShowRestartDialog:
1712                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1713                 break;
1714             case kAEShowShutdownDialog:
1715                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1716                 break;
1717             default:
1718                 event->app_quit_requested.reason = QUIT_REASON_NONE;
1719                 break;
1720         }
1722         [eventQueuesLock lock];
1724         if ([eventQueues count])
1725         {
1726             for (queue in eventQueues)
1727                 [queue postEvent:event];
1728             ret = NSTerminateLater;
1729         }
1731         [eventQueuesLock unlock];
1733         macdrv_release_event(event);
1735         return ret;
1736     }
1738     - (void)applicationWillResignActive:(NSNotification *)notification
1739     {
1740         [self deactivateCursorClipping];
1742         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1743             WineWindow* window = obj;
1744             NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1745             if ([window level] > level)
1746                 [window setLevel:level];
1747         }];
1748     }
1750 /***********************************************************************
1751  *              PerformRequest
1753  * Run-loop-source perform callback.  Pull request blocks from the
1754  * array of queued requests and invoke them.
1755  */
1756 static void PerformRequest(void *info)
1758     WineApplicationController* controller = [WineApplicationController sharedController];
1760     for (;;)
1761     {
1762         __block dispatch_block_t block;
1764         dispatch_sync(controller->requestsManipQueue, ^{
1765             if ([controller->requests count])
1766             {
1767                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1768                 [controller->requests removeObjectAtIndex:0];
1769             }
1770             else
1771                 block = nil;
1772         });
1774         if (!block)
1775             break;
1777         block();
1778         [block release];
1779     }
1782 /***********************************************************************
1783  *              OnMainThreadAsync
1785  * Run a block on the main thread asynchronously.
1786  */
1787 void OnMainThreadAsync(dispatch_block_t block)
1789     WineApplicationController* controller = [WineApplicationController sharedController];
1791     block = [block copy];
1792     dispatch_sync(controller->requestsManipQueue, ^{
1793         [controller->requests addObject:block];
1794     });
1795     [block release];
1796     CFRunLoopSourceSignal(controller->requestSource);
1797     CFRunLoopWakeUp(CFRunLoopGetMain());
1800 @end
1802 /***********************************************************************
1803  *              LogError
1804  */
1805 void LogError(const char* func, NSString* format, ...)
1807     va_list args;
1808     va_start(args, format);
1809     LogErrorv(func, format, args);
1810     va_end(args);
1813 /***********************************************************************
1814  *              LogErrorv
1815  */
1816 void LogErrorv(const char* func, NSString* format, va_list args)
1818     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1820     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1821     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1822     [message release];
1824     [pool release];
1827 /***********************************************************************
1828  *              macdrv_window_rejected_focus
1830  * Pass focus to the next window that hasn't already rejected this same
1831  * WINDOW_GOT_FOCUS event.
1832  */
1833 void macdrv_window_rejected_focus(const macdrv_event *event)
1835     OnMainThread(^{
1836         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1837     });
1840 /***********************************************************************
1841  *              macdrv_get_keyboard_layout
1843  * Returns the keyboard layout uchr data.
1844  */
1845 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1847     __block CFDataRef result = NULL;
1849     OnMainThread(^{
1850         TISInputSourceRef inputSource;
1852         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1853         if (inputSource)
1854         {
1855             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1856                                 kTISPropertyUnicodeKeyLayoutData);
1857             result = CFDataCreateCopy(NULL, uchr);
1858             CFRelease(inputSource);
1860             *keyboard_type = [WineApplicationController sharedController].keyboardType;
1861             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1862         }
1863     });
1865     return result;
1868 /***********************************************************************
1869  *              macdrv_beep
1871  * Play the beep sound configured by the user in System Preferences.
1872  */
1873 void macdrv_beep(void)
1875     OnMainThreadAsync(^{
1876         NSBeep();
1877     });
1880 /***********************************************************************
1881  *              macdrv_set_display_mode
1882  */
1883 int macdrv_set_display_mode(const struct macdrv_display* display,
1884                             CGDisplayModeRef display_mode)
1886     __block int ret;
1888     OnMainThread(^{
1889         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1890     });
1892     return ret;
1895 /***********************************************************************
1896  *              macdrv_set_cursor
1898  * Set the cursor.
1900  * If name is non-NULL, it is a selector for a class method on NSCursor
1901  * identifying the cursor to set.  In that case, frames is ignored.  If
1902  * name is NULL, then frames is used.
1904  * frames is an array of dictionaries.  Each dictionary is a frame of
1905  * an animated cursor.  Under the key "image" is a CGImage for the
1906  * frame.  Under the key "duration" is a CFNumber time interval, in
1907  * seconds, for how long that frame is presented before proceeding to
1908  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
1909  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1910  * This is the hot spot, measured in pixels down and to the right of the
1911  * top-left corner of the image.
1913  * If the array has exactly 1 element, the cursor is static, not
1914  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
1915  */
1916 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1918     SEL sel;
1920     sel = NSSelectorFromString((NSString*)name);
1921     if (sel)
1922     {
1923         OnMainThreadAsync(^{
1924             WineApplicationController* controller = [WineApplicationController sharedController];
1925             NSCursor* cursor = [NSCursor performSelector:sel];
1926             [controller setCursorWithFrames:nil];
1927             [cursor set];
1928             [controller unhideCursor];
1929         });
1930     }
1931     else
1932     {
1933         NSArray* nsframes = (NSArray*)frames;
1934         if ([nsframes count])
1935         {
1936             OnMainThreadAsync(^{
1937                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1938             });
1939         }
1940         else
1941         {
1942             OnMainThreadAsync(^{
1943                 WineApplicationController* controller = [WineApplicationController sharedController];
1944                 [controller setCursorWithFrames:nil];
1945                 [controller hideCursor];
1946             });
1947         }
1948     }
1951 /***********************************************************************
1952  *              macdrv_get_cursor_position
1954  * Obtains the current cursor position.  Returns zero on failure,
1955  * non-zero on success.
1956  */
1957 int macdrv_get_cursor_position(CGPoint *pos)
1959     OnMainThread(^{
1960         NSPoint location = [NSEvent mouseLocation];
1961         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1962         *pos = NSPointToCGPoint(location);
1963     });
1965     return TRUE;
1968 /***********************************************************************
1969  *              macdrv_set_cursor_position
1971  * Sets the cursor position without generating events.  Returns zero on
1972  * failure, non-zero on success.
1973  */
1974 int macdrv_set_cursor_position(CGPoint pos)
1976     __block int ret;
1978     OnMainThread(^{
1979         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1980     });
1982     return ret;
1985 /***********************************************************************
1986  *              macdrv_clip_cursor
1988  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
1989  * to or larger than the whole desktop region, the cursor is unclipped.
1990  * Returns zero on failure, non-zero on success.
1991  */
1992 int macdrv_clip_cursor(CGRect rect)
1994     __block int ret;
1996     OnMainThread(^{
1997         WineApplicationController* controller = [WineApplicationController sharedController];
1998         BOOL clipping = FALSE;
2000         if (!CGRectIsInfinite(rect))
2001         {
2002             NSRect nsrect = NSRectFromCGRect(rect);
2003             NSScreen* screen;
2005             /* Convert the rectangle from top-down coords to bottom-up. */
2006             [controller flipRect:&nsrect];
2008             clipping = FALSE;
2009             for (screen in [NSScreen screens])
2010             {
2011                 if (!NSContainsRect(nsrect, [screen frame]))
2012                 {
2013                     clipping = TRUE;
2014                     break;
2015                 }
2016             }
2017         }
2019         if (clipping)
2020             ret = [controller startClippingCursor:rect];
2021         else
2022             ret = [controller stopClippingCursor];
2023     });
2025     return ret;
2028 /***********************************************************************
2029  *              macdrv_set_application_icon
2031  * Set the application icon.  The images array contains CGImages.  If
2032  * there are more than one, then they represent different sizes or
2033  * color depths from the icon resource.  If images is NULL or empty,
2034  * restores the default application image.
2035  */
2036 void macdrv_set_application_icon(CFArrayRef images)
2038     NSArray* imageArray = (NSArray*)images;
2040     OnMainThreadAsync(^{
2041         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2042     });
2045 /***********************************************************************
2046  *              macdrv_quit_reply
2047  */
2048 void macdrv_quit_reply(int reply)
2050     OnMainThread(^{
2051         [NSApp replyToApplicationShouldTerminate:reply];
2052     });
2055 /***********************************************************************
2056  *              macdrv_using_input_method
2057  */
2058 int macdrv_using_input_method(void)
2060     __block BOOL ret;
2062     OnMainThread(^{
2063         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2064     });
2066     return ret;
2069 /***********************************************************************
2070  *              macdrv_set_mouse_capture_window
2071  */
2072 void macdrv_set_mouse_capture_window(macdrv_window window)
2074     WineWindow* w = (WineWindow*)window;
2076     OnMainThread(^{
2077         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2078     });