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