winemac: Implement SetCapture().
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob4e7cecc3f34de508149edf034beb85cc989d4650
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     - (void) sendDisplaysChanged:(BOOL)activating
490     {
491         macdrv_event* event;
492         WineEventQueue* queue;
494         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
495         event->displays_changed.activating = activating;
497         [eventQueuesLock lock];
499         // If we're activating, then we just need one of our threads to get the
500         // event, so it can send it directly to the desktop window.  Otherwise,
501         // we need all of the threads to get it because we don't know which owns
502         // the desktop window and only that one will do anything with it.
503         if (activating) event->deliver = 1;
505         for (queue in eventQueues)
506             [queue postEvent:event];
507         [eventQueuesLock unlock];
509         macdrv_release_event(event);
510     }
512     // We can compare two modes directly using CFEqual, but that may require that
513     // they are identical to a level that we don't need.  In particular, when the
514     // OS switches between the integrated and discrete GPUs, the set of display
515     // modes can change in subtle ways.  We're interested in whether two modes
516     // match in their most salient features, even if they aren't identical.
517     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
518     {
519         NSString *encoding1, *encoding2;
520         uint32_t ioflags1, ioflags2, different;
521         double refresh1, refresh2;
523         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
524         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
526         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
527         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
528         if (![encoding1 isEqualToString:encoding2]) return FALSE;
530         ioflags1 = CGDisplayModeGetIOFlags(mode1);
531         ioflags2 = CGDisplayModeGetIOFlags(mode2);
532         different = ioflags1 ^ ioflags2;
533         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
534                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
535             return FALSE;
537         refresh1 = CGDisplayModeGetRefreshRate(mode1);
538         if (refresh1 == 0) refresh1 = 60;
539         refresh2 = CGDisplayModeGetRefreshRate(mode2);
540         if (refresh2 == 0) refresh2 = 60;
541         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
543         return TRUE;
544     }
546     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
547     {
548         CGDisplayModeRef ret = NULL;
549         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
550         for (id candidateModeObject in modes)
551         {
552             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
553             if ([self mode:candidateMode matchesMode:mode])
554             {
555                 ret = candidateMode;
556                 break;
557             }
558         }
559         return ret;
560     }
562     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
563     {
564         BOOL ret = FALSE;
565         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
566         CGDisplayModeRef currentMode, originalMode;
568         currentMode = CGDisplayCopyDisplayMode(displayID);
569         if (!currentMode) // Invalid display ID
570             return FALSE;
572         if ([self mode:mode matchesMode:currentMode]) // Already there!
573         {
574             CGDisplayModeRelease(currentMode);
575             return TRUE;
576         }
578         mode = [self modeMatchingMode:mode forDisplay:displayID];
579         if (!mode)
580         {
581             CGDisplayModeRelease(currentMode);
582             return FALSE;
583         }
585         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
586         if (!originalMode)
587             originalMode = currentMode;
589         if ([self mode:mode matchesMode:originalMode])
590         {
591             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
592             {
593                 CGRestorePermanentDisplayConfiguration();
594                 CGReleaseAllDisplays();
595                 [originalDisplayModes removeAllObjects];
596                 ret = TRUE;
597             }
598             else // ... otherwise, try to restore just the one display
599             {
600                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
601                 {
602                     [originalDisplayModes removeObjectForKey:displayIDKey];
603                     ret = TRUE;
604                 }
605             }
606         }
607         else
608         {
609             if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
610             {
611                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
612                 {
613                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
614                     ret = TRUE;
615                 }
616                 else if (![originalDisplayModes count])
617                 {
618                     CGRestorePermanentDisplayConfiguration();
619                     CGReleaseAllDisplays();
620                 }
621             }
622         }
624         CGDisplayModeRelease(currentMode);
626         if (ret)
627         {
628             [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
629                 [(WineWindow*)obj adjustWindowLevel];
630             }];
631         }
633         return ret;
634     }
636     - (BOOL) areDisplaysCaptured
637     {
638         return ([originalDisplayModes count] > 0);
639     }
641     - (void) hideCursor
642     {
643         if (!cursorHidden)
644         {
645             [NSCursor hide];
646             cursorHidden = TRUE;
647         }
648     }
650     - (void) unhideCursor
651     {
652         if (cursorHidden)
653         {
654             [NSCursor unhide];
655             cursorHidden = FALSE;
656         }
657     }
659     - (void) setCursor
660     {
661         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
662         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
663         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
664         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
665         CGPoint hotSpot;
666         NSCursor* cursor;
668         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
669             hotSpot = CGPointZero;
670         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
671         [image release];
672         [cursor set];
673         [self unhideCursor];
674         [cursor release];
675     }
677     - (void) nextCursorFrame:(NSTimer*)theTimer
678     {
679         NSDictionary* frame;
680         NSTimeInterval duration;
681         NSDate* date;
683         cursorFrame++;
684         if (cursorFrame >= [cursorFrames count])
685             cursorFrame = 0;
686         [self setCursor];
688         frame = [cursorFrames objectAtIndex:cursorFrame];
689         duration = [[frame objectForKey:@"duration"] doubleValue];
690         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
691         [cursorTimer setFireDate:date];
692     }
694     - (void) setCursorWithFrames:(NSArray*)frames
695     {
696         if (self.cursorFrames == frames)
697             return;
699         self.cursorFrames = frames;
700         cursorFrame = 0;
701         [cursorTimer invalidate];
702         self.cursorTimer = nil;
704         if ([frames count])
705         {
706             if ([frames count] > 1)
707             {
708                 NSDictionary* frame = [frames objectAtIndex:0];
709                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
710                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
711                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
712                                                              interval:1000000
713                                                                target:self
714                                                              selector:@selector(nextCursorFrame:)
715                                                              userInfo:nil
716                                                               repeats:YES] autorelease];
717                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
718             }
720             [self setCursor];
721         }
722     }
724     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
725     {
726         NSImage* nsimage = nil;
728         if ([images count])
729         {
730             NSSize bestSize = NSZeroSize;
731             id image;
733             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
735             for (image in images)
736             {
737                 CGImageRef cgimage = (CGImageRef)image;
738                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
739                 if (imageRep)
740                 {
741                     NSSize size = [imageRep size];
743                     [nsimage addRepresentation:imageRep];
744                     [imageRep release];
746                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
747                         bestSize = size;
748                 }
749             }
751             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
752                 [nsimage setSize:bestSize];
753             else
754                 nsimage = nil;
755         }
757         self.applicationIcon = nsimage;
758         [NSApp setApplicationIconImage:nsimage];
759     }
761     - (void) handleCommandTab
762     {
763         if ([NSApp isActive])
764         {
765             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
766             NSRunningApplication* app;
767             NSRunningApplication* otherValidApp = nil;
769             if ([originalDisplayModes count])
770             {
771                 CGRestorePermanentDisplayConfiguration();
772                 CGReleaseAllDisplays();
773                 [originalDisplayModes removeAllObjects];
774             }
776             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
777             {
778                 if (![app isEqual:thisApp] && !app.terminated &&
779                     app.activationPolicy == NSApplicationActivationPolicyRegular)
780                 {
781                     if (!app.hidden)
782                     {
783                         // There's another visible app.  Just hide ourselves and let
784                         // the system activate the other app.
785                         [NSApp hide:self];
786                         return;
787                     }
789                     if (!otherValidApp)
790                         otherValidApp = app;
791                 }
792             }
794             // Didn't find a visible GUI app.  Try the Finder or, if that's not
795             // running, the first hidden GUI app.  If even that doesn't work, we
796             // just fail to switch and remain the active app.
797             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
798             if (!app) app = otherValidApp;
799             [app unhide];
800             [app activateWithOptions:0];
801         }
802     }
804     /*
805      * ---------- Cursor clipping methods ----------
806      *
807      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
808      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
809      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
810      * general case, we leverage that.  We disassociate mouse movements from
811      * the cursor position and then move the cursor manually, keeping it within
812      * the clipping rectangle.
813      *
814      * Moving the cursor manually isn't enough.  We need to modify the event
815      * stream so that the events have the new location, too.  We need to do
816      * this at a point before the events enter Cocoa, so that Cocoa will assign
817      * the correct window to the event.  So, we install a Quartz event tap to
818      * do that.
819      *
820      * Also, there's a complication when we move the cursor.  We use
821      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
822      * events, but the change of cursor position is incorporated into the
823      * deltas of the next mouse move event.  When the mouse is disassociated
824      * from the cursor position, we need the deltas to only reflect actual
825      * device movement, not programmatic changes.  So, the event tap cancels
826      * out the change caused by our calls to CGWarpMouseCursorPosition().
827      */
828     - (void) clipCursorLocation:(CGPoint*)location
829     {
830         if (location->x < CGRectGetMinX(cursorClipRect))
831             location->x = CGRectGetMinX(cursorClipRect);
832         if (location->y < CGRectGetMinY(cursorClipRect))
833             location->y = CGRectGetMinY(cursorClipRect);
834         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
835             location->x = CGRectGetMaxX(cursorClipRect) - 1;
836         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
837             location->y = CGRectGetMaxY(cursorClipRect) - 1;
838     }
840     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
841     {
842         CGPoint oldLocation;
844         if (currentLocation)
845             oldLocation = *currentLocation;
846         else
847             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
849         if (!CGPointEqualToPoint(oldLocation, *newLocation))
850         {
851             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
852             CGError err;
854             warpRecord.from = oldLocation;
855             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
857             /* Actually move the cursor. */
858             err = CGWarpMouseCursorPosition(*newLocation);
859             if (err != kCGErrorSuccess)
860                 return FALSE;
862             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
863             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
865             if (!CGPointEqualToPoint(oldLocation, *newLocation))
866             {
867                 warpRecord.to = *newLocation;
868                 [warpRecords addObject:warpRecord];
869             }
870         }
872         return TRUE;
873     }
875     - (BOOL) isMouseMoveEventType:(CGEventType)type
876     {
877         switch(type)
878         {
879         case kCGEventMouseMoved:
880         case kCGEventLeftMouseDragged:
881         case kCGEventRightMouseDragged:
882         case kCGEventOtherMouseDragged:
883             return TRUE;
884         }
886         return FALSE;
887     }
889     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
890     {
891         int warpsFinished = 0;
892         for (WarpRecord* warpRecord in warpRecords)
893         {
894             if (warpRecord.timeAfter < eventTime ||
895                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
896                 warpsFinished++;
897             else
898                 break;
899         }
901         return warpsFinished;
902     }
904     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
905                                 type:(CGEventType)type
906                                event:(CGEventRef)event
907     {
908         CGEventTimestamp eventTime;
909         CGPoint eventLocation, cursorLocation;
911         if (type == kCGEventTapDisabledByUserInput)
912             return event;
913         if (type == kCGEventTapDisabledByTimeout)
914         {
915             CGEventTapEnable(cursorClippingEventTap, TRUE);
916             return event;
917         }
919         if (!clippingCursor)
920             return event;
922         eventTime = CGEventGetTimestamp(event);
923         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
925         eventLocation = CGEventGetLocation(event);
927         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
929         if ([self isMouseMoveEventType:type])
930         {
931             double deltaX, deltaY;
932             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
933             int i;
935             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
936             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
938             for (i = 0; i < warpsFinished; i++)
939             {
940                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
941                 deltaX -= warpRecord.to.x - warpRecord.from.x;
942                 deltaY -= warpRecord.to.y - warpRecord.from.y;
943                 [warpRecords removeObjectAtIndex:0];
944             }
946             if (warpsFinished)
947             {
948                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
949                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
950             }
952             synthesizedLocation.x += deltaX;
953             synthesizedLocation.y += deltaY;
954         }
956         // If the event is destined for another process, don't clip it.  This may
957         // happen if the user activates Exposé or Mission Control.  In that case,
958         // our app does not resign active status, so clipping is still in effect,
959         // but the cursor should not actually be clipped.
960         //
961         // In addition, the fact that mouse moves may have been delivered to a
962         // different process means we have to treat the next one we receive as
963         // absolute rather than relative.
964         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
965             [self clipCursorLocation:&synthesizedLocation];
966         else
967             lastSetCursorPositionTime = lastEventTapEventTime;
969         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
970         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
971             CGEventSetLocation(event, synthesizedLocation);
973         return event;
974     }
976     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
977                                        CGEventRef event, void *refcon)
978     {
979         WineApplicationController* controller = refcon;
980         return [controller eventTapWithProxy:proxy type:type event:event];
981     }
983     - (BOOL) installEventTap
984     {
985         ProcessSerialNumber psn;
986         OSErr err;
987         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
988                            CGEventMaskBit(kCGEventLeftMouseUp)          |
989                            CGEventMaskBit(kCGEventRightMouseDown)       |
990                            CGEventMaskBit(kCGEventRightMouseUp)         |
991                            CGEventMaskBit(kCGEventMouseMoved)           |
992                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
993                            CGEventMaskBit(kCGEventRightMouseDragged)    |
994                            CGEventMaskBit(kCGEventOtherMouseDown)       |
995                            CGEventMaskBit(kCGEventOtherMouseUp)         |
996                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
997                            CGEventMaskBit(kCGEventScrollWheel);
998         CFRunLoopSourceRef source;
999         void* appServices;
1000         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1002         if (cursorClippingEventTap)
1003             return TRUE;
1005         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1006         // framework with dlsym() because the Win32 function of the same name
1007         // obscures it.
1008         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1009         if (!appServices)
1010             return FALSE;
1012         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1013         if (!pGetCurrentProcess)
1014         {
1015             dlclose(appServices);
1016             return FALSE;
1017         }
1019         err = pGetCurrentProcess(&psn);
1020         dlclose(appServices);
1021         if (err != noErr)
1022             return FALSE;
1024         // We create an annotated session event tap rather than a process-specific
1025         // event tap because we need to programmatically move the cursor even when
1026         // mouse moves are directed to other processes.  We disable our tap when
1027         // other processes are active, but things like Exposé are handled by other
1028         // processes even when we remain active.
1029         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1030             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1031         if (!cursorClippingEventTap)
1032             return FALSE;
1034         CGEventTapEnable(cursorClippingEventTap, FALSE);
1036         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1037         if (!source)
1038         {
1039             CFRelease(cursorClippingEventTap);
1040             cursorClippingEventTap = NULL;
1041             return FALSE;
1042         }
1044         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1045         CFRelease(source);
1046         return TRUE;
1047     }
1049     - (BOOL) setCursorPosition:(CGPoint)pos
1050     {
1051         BOOL ret;
1053         if (clippingCursor)
1054         {
1055             [self clipCursorLocation:&pos];
1057             ret = [self warpCursorTo:&pos from:NULL];
1058             synthesizedLocation = pos;
1059             if (ret)
1060             {
1061                 // We want to discard mouse-move events that have already been
1062                 // through the event tap, because it's too late to account for
1063                 // the setting of the cursor position with them.  However, the
1064                 // events that may be queued with times after that but before
1065                 // the above warp can still be used.  So, use the last event
1066                 // tap event time so that -sendEvent: doesn't discard them.
1067                 lastSetCursorPositionTime = lastEventTapEventTime;
1068             }
1069         }
1070         else
1071         {
1072             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1073             if (ret)
1074             {
1075                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1077                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1078                 // the mouse from the cursor position for 0.25 seconds.  This means
1079                 // that mouse movement during that interval doesn't move the cursor
1080                 // and events carry a constant location (the warped-to position)
1081                 // even though they have delta values.  This screws us up because
1082                 // the accumulated deltas we send to Wine don't match any eventual
1083                 // absolute position we send (like with a button press).  We can
1084                 // work around this by simply forcibly reassociating the mouse and
1085                 // cursor position.
1086                 CGAssociateMouseAndMouseCursorPosition(true);
1087             }
1088         }
1090         if (ret)
1091         {
1092             WineEventQueue* queue;
1094             // Discard all pending mouse move events.
1095             [eventQueuesLock lock];
1096             for (queue in eventQueues)
1097             {
1098                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1099                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1100                                        forWindow:nil];
1101                 [queue resetMouseEventPositions:pos];
1102             }
1103             [eventQueuesLock unlock];
1104         }
1106         return ret;
1107     }
1109     - (void) activateCursorClipping
1110     {
1111         if (clippingCursor)
1112         {
1113             CGEventTapEnable(cursorClippingEventTap, TRUE);
1114             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1115         }
1116     }
1118     - (void) deactivateCursorClipping
1119     {
1120         if (clippingCursor)
1121         {
1122             CGEventTapEnable(cursorClippingEventTap, FALSE);
1123             [warpRecords removeAllObjects];
1124             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1125         }
1126     }
1128     - (BOOL) startClippingCursor:(CGRect)rect
1129     {
1130         CGError err;
1132         if (!cursorClippingEventTap && ![self installEventTap])
1133             return FALSE;
1135         err = CGAssociateMouseAndMouseCursorPosition(false);
1136         if (err != kCGErrorSuccess)
1137             return FALSE;
1139         clippingCursor = TRUE;
1140         cursorClipRect = rect;
1141         if ([NSApp isActive])
1142             [self activateCursorClipping];
1144         return TRUE;
1145     }
1147     - (BOOL) stopClippingCursor
1148     {
1149         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1150         if (err != kCGErrorSuccess)
1151             return FALSE;
1153         [self deactivateCursorClipping];
1154         clippingCursor = FALSE;
1156         return TRUE;
1157     }
1159     - (void) handleMouseMove:(NSEvent*)anEvent
1160     {
1161         WineWindow* targetWindow;
1162         BOOL drag = [anEvent type] != NSMouseMoved;
1164         if (mouseCaptureWindow)
1165             targetWindow = mouseCaptureWindow;
1166         else if (drag)
1167             targetWindow = (WineWindow*)[anEvent window];
1168         else
1169         {
1170             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1171                event indicates its window is the main window, even if the cursor is
1172                over a different window.  Find the actual WineWindow that is under the
1173                cursor and post the event as being for that window. */
1174             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1175             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1176             NSInteger windowUnderNumber;
1178             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1179                                   belowWindowWithWindowNumber:0];
1180             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1181         }
1183         if ([targetWindow isKindOfClass:[WineWindow class]])
1184         {
1185             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1186             macdrv_event* event;
1187             BOOL absolute;
1189             // If we recently warped the cursor (other than in our cursor-clipping
1190             // event tap), discard mouse move events until we see an event which is
1191             // later than that time.
1192             if (lastSetCursorPositionTime)
1193             {
1194                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1195                     return;
1197                 lastSetCursorPositionTime = 0;
1198                 forceNextMouseMoveAbsolute = TRUE;
1199             }
1201             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1202             {
1203                 absolute = TRUE;
1204                 forceNextMouseMoveAbsolute = FALSE;
1205             }
1206             else
1207             {
1208                 // Send absolute move events if the cursor is in the interior of
1209                 // its range.  Only send relative moves if the cursor is pinned to
1210                 // the boundaries of where it can go.  We compute the position
1211                 // that's one additional point in the direction of movement.  If
1212                 // that is outside of the clipping rect or desktop region (the
1213                 // union of the screen frames), then we figure the cursor would
1214                 // have moved outside if it could but it was pinned.
1215                 CGPoint computedPoint = point;
1216                 CGFloat deltaX = [anEvent deltaX];
1217                 CGFloat deltaY = [anEvent deltaY];
1219                 if (deltaX > 0.001)
1220                     computedPoint.x++;
1221                 else if (deltaX < -0.001)
1222                     computedPoint.x--;
1224                 if (deltaY > 0.001)
1225                     computedPoint.y++;
1226                 else if (deltaY < -0.001)
1227                     computedPoint.y--;
1229                 // Assume cursor is pinned for now
1230                 absolute = FALSE;
1231                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1232                 {
1233                     const CGRect* rects;
1234                     NSUInteger count, i;
1236                     // Caches screenFrameCGRects if necessary
1237                     [self primaryScreenHeight];
1239                     rects = [screenFrameCGRects bytes];
1240                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1242                     for (i = 0; i < count; i++)
1243                     {
1244                         if (CGRectContainsPoint(rects[i], computedPoint))
1245                         {
1246                             absolute = TRUE;
1247                             break;
1248                         }
1249                     }
1250                 }
1251             }
1253             if (absolute)
1254             {
1255                 if (clippingCursor)
1256                     [self clipCursorLocation:&point];
1258                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1259                 event->mouse_moved.x = point.x;
1260                 event->mouse_moved.y = point.y;
1262                 mouseMoveDeltaX = 0;
1263                 mouseMoveDeltaY = 0;
1264             }
1265             else
1266             {
1267                 /* Add event delta to accumulated delta error */
1268                 /* deltaY is already flipped */
1269                 mouseMoveDeltaX += [anEvent deltaX];
1270                 mouseMoveDeltaY += [anEvent deltaY];
1272                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1273                 event->mouse_moved.x = mouseMoveDeltaX;
1274                 event->mouse_moved.y = mouseMoveDeltaY;
1276                 /* Keep the remainder after integer truncation. */
1277                 mouseMoveDeltaX -= event->mouse_moved.x;
1278                 mouseMoveDeltaY -= event->mouse_moved.y;
1279             }
1281             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1282             {
1283                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1284                 event->mouse_moved.drag = drag;
1286                 [targetWindow.queue postEvent:event];
1287             }
1289             macdrv_release_event(event);
1291             lastTargetWindow = targetWindow;
1292         }
1293         else if (lastTargetWindow)
1294         {
1295             [[NSCursor arrowCursor] set];
1296             [self unhideCursor];
1297             lastTargetWindow = nil;
1298         }
1299     }
1301     - (void) handleMouseButton:(NSEvent*)theEvent
1302     {
1303         WineWindow* window;
1305         if (mouseCaptureWindow)
1306             window = mouseCaptureWindow;
1307         else
1308             window = (WineWindow*)[theEvent window];
1310         if ([window isKindOfClass:[WineWindow class]])
1311         {
1312             NSEventType type = [theEvent type];
1313             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1314             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1315             BOOL process;
1317             if (clippingCursor)
1318                 [self clipCursorLocation:&pt];
1320             if (pressed)
1321             {
1322                 if (mouseCaptureWindow)
1323                     process = TRUE;
1324                 else
1325                 {
1326                     // Test if the click was in the window's content area.
1327                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1328                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1329                     process = NSPointInRect(nspoint, contentRect);
1330                     if (process && [window styleMask] & NSResizableWindowMask)
1331                     {
1332                         // Ignore clicks in the grow box (resize widget).
1333                         HIPoint origin = { 0, 0 };
1334                         HIThemeGrowBoxDrawInfo info = { 0 };
1335                         HIRect bounds;
1336                         OSStatus status;
1338                         info.kind = kHIThemeGrowBoxKindNormal;
1339                         info.direction = kThemeGrowRight | kThemeGrowDown;
1340                         if ([window styleMask] & NSUtilityWindowMask)
1341                             info.size = kHIThemeGrowBoxSizeSmall;
1342                         else
1343                             info.size = kHIThemeGrowBoxSizeNormal;
1345                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1346                         if (status == noErr)
1347                         {
1348                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1349                                                         NSMinY(contentRect),
1350                                                         bounds.size.width,
1351                                                         bounds.size.height);
1352                             process = !NSPointInRect(nspoint, growBox);
1353                         }
1354                     }
1355                 }
1356                 if (process)
1357                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1358             }
1359             else
1360             {
1361                 NSEventType downType = type - 1;
1362                 NSUInteger downMask = NSEventMaskFromType(downType);
1363                 process = (unmatchedMouseDowns & downMask) != 0;
1364                 unmatchedMouseDowns &= ~downMask;
1365             }
1367             if (process)
1368             {
1369                 macdrv_event* event;
1371                 event = macdrv_create_event(MOUSE_BUTTON, window);
1372                 event->mouse_button.button = [theEvent buttonNumber];
1373                 event->mouse_button.pressed = pressed;
1374                 event->mouse_button.x = pt.x;
1375                 event->mouse_button.y = pt.y;
1376                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1378                 [window.queue postEvent:event];
1380                 macdrv_release_event(event);
1381             }
1382         }
1384         // Since mouse button events deliver absolute cursor position, the
1385         // accumulating delta from move events is invalidated.  Make sure
1386         // next mouse move event starts over from an absolute baseline.
1387         // Also, it's at least possible that the title bar widgets (e.g. close
1388         // button, etc.) could enter an internal event loop on a mouse down that
1389         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1390         // dragged events and, after that, any notion of the cursor position
1391         // computed from accumulating deltas would be wrong.
1392         forceNextMouseMoveAbsolute = TRUE;
1393     }
1395     - (void) handleScrollWheel:(NSEvent*)theEvent
1396     {
1397         WineWindow* window;
1399         if (mouseCaptureWindow)
1400             window = mouseCaptureWindow;
1401         else
1402             window = (WineWindow*)[theEvent window];
1404         if ([window isKindOfClass:[WineWindow class]])
1405         {
1406             CGEventRef cgevent = [theEvent CGEvent];
1407             CGPoint pt = CGEventGetLocation(cgevent);
1408             BOOL process;
1410             if (clippingCursor)
1411                 [self clipCursorLocation:&pt];
1413             if (mouseCaptureWindow)
1414                 process = TRUE;
1415             else
1416             {
1417                 // Only process the event if it was in the window's content area.
1418                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1419                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1420                 process = NSPointInRect(nspoint, contentRect);
1421             }
1423             if (process)
1424             {
1425                 macdrv_event* event;
1426                 CGFloat x, y;
1427                 BOOL continuous = FALSE;
1429                 event = macdrv_create_event(MOUSE_SCROLL, window);
1430                 event->mouse_scroll.x = pt.x;
1431                 event->mouse_scroll.y = pt.y;
1432                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1434                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1435                 {
1436                     continuous = TRUE;
1438                     /* Continuous scroll wheel events come from high-precision scrolling
1439                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1440                        For these, we can get more precise data from the CGEvent API. */
1441                     /* Axis 1 is vertical, axis 2 is horizontal. */
1442                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1443                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1444                 }
1445                 else
1446                 {
1447                     double pixelsPerLine = 10;
1448                     CGEventSourceRef source;
1450                     /* The non-continuous values are in units of "lines", not pixels. */
1451                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1452                     {
1453                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1454                         CFRelease(source);
1455                     }
1457                     x = pixelsPerLine * [theEvent deltaX];
1458                     y = pixelsPerLine * [theEvent deltaY];
1459                 }
1461                 /* Mac: negative is right or down, positive is left or up.
1462                    Win32: negative is left or down, positive is right or up.
1463                    So, negate the X scroll value to translate. */
1464                 x = -x;
1466                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1467                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1468                    6 times the pixel value. */
1469                 event->mouse_scroll.x_scroll = 6 * x;
1470                 event->mouse_scroll.y_scroll = 6 * y;
1472                 if (!continuous)
1473                 {
1474                     /* For non-continuous "clicky" wheels, if there was any motion, make
1475                        sure there was at least WHEEL_DELTA motion.  This is so, at slow
1476                        speeds where the system's acceleration curve is actually reducing the
1477                        scroll distance, the user is sure to get some action out of each click.
1478                        For example, this is important for rotating though weapons in a
1479                        first-person shooter. */
1480                     if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1481                         event->mouse_scroll.x_scroll = 120;
1482                     else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1483                         event->mouse_scroll.x_scroll = -120;
1485                     if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1486                         event->mouse_scroll.y_scroll = 120;
1487                     else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1488                         event->mouse_scroll.y_scroll = -120;
1489                 }
1491                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1492                     [window.queue postEvent:event];
1494                 macdrv_release_event(event);
1496                 // Since scroll wheel events deliver absolute cursor position, the
1497                 // accumulating delta from move events is invalidated.  Make sure next
1498                 // mouse move event starts over from an absolute baseline.
1499                 forceNextMouseMoveAbsolute = TRUE;
1500             }
1501         }
1502     }
1504     // Returns TRUE if the event was handled and caller should do nothing more
1505     // with it.  Returns FALSE if the caller should process it as normal and
1506     // then call -didSendEvent:.
1507     - (BOOL) handleEvent:(NSEvent*)anEvent
1508     {
1509         BOOL ret = FALSE;
1510         NSEventType type = [anEvent type];
1512         if (type == NSFlagsChanged)
1513             self.lastFlagsChanged = anEvent;
1514         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1515                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1516         {
1517             [self handleMouseMove:anEvent];
1518             ret = mouseCaptureWindow != nil;
1519         }
1520         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1521                  type == NSRightMouseDown || type == NSRightMouseUp ||
1522                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1523         {
1524             [self handleMouseButton:anEvent];
1525             ret = mouseCaptureWindow != nil;
1526         }
1527         else if (type == NSScrollWheel)
1528         {
1529             [self handleScrollWheel:anEvent];
1530             ret = mouseCaptureWindow != nil;
1531         }
1533         return ret;
1534     }
1536     - (void) didSendEvent:(NSEvent*)anEvent
1537     {
1538         NSEventType type = [anEvent type];
1540         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1541         {
1542             NSUInteger modifiers = [anEvent modifierFlags];
1543             if ((modifiers & NSCommandKeyMask) &&
1544                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1545             {
1546                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1547                 // by the system to switch applications.  If we're seeing it, it's
1548                 // presumably because we've captured the displays, preventing
1549                 // normal application switching.  Do it manually.
1550                 [self handleCommandTab];
1551             }
1552         }
1553     }
1555     - (void) setupObservations
1556     {
1557         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1559         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1560                         object:nil
1561                          queue:nil
1562                     usingBlock:^(NSNotification *note){
1563             NSWindow* window = [note object];
1564             [keyWindows removeObjectIdenticalTo:window];
1565             [keyWindows insertObject:window atIndex:0];
1566         }];
1568         [nc addObserverForName:NSWindowWillCloseNotification
1569                         object:nil
1570                          queue:[NSOperationQueue mainQueue]
1571                     usingBlock:^(NSNotification *note){
1572             NSWindow* window = [note object];
1573             [keyWindows removeObjectIdenticalTo:window];
1574             [orderedWineWindows removeObjectIdenticalTo:window];
1575             if (window == lastTargetWindow)
1576                 lastTargetWindow = nil;
1577             if (window == self.mouseCaptureWindow)
1578                 self.mouseCaptureWindow = nil;
1579         }];
1581         [nc addObserver:self
1582                selector:@selector(keyboardSelectionDidChange)
1583                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1584                  object:nil];
1586         /* The above notification isn't sent unless the NSTextInputContext
1587            class has initialized itself.  Poke it. */
1588         [NSTextInputContext self];
1589     }
1591     - (BOOL) inputSourceIsInputMethod
1592     {
1593         if (!inputSourceIsInputMethodValid)
1594         {
1595             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1596             if (inputSource)
1597             {
1598                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1599                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1600                 CFRelease(inputSource);
1601             }
1602             else
1603                 inputSourceIsInputMethod = FALSE;
1604             inputSourceIsInputMethodValid = TRUE;
1605         }
1607         return inputSourceIsInputMethod;
1608     }
1611     /*
1612      * ---------- NSApplicationDelegate methods ----------
1613      */
1614     - (void)applicationDidBecomeActive:(NSNotification *)notification
1615     {
1616         [self activateCursorClipping];
1618         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1619             WineWindow* window = obj;
1620             if ([window levelWhenActive] != [window level])
1621                 [window setLevel:[window levelWhenActive]];
1622         }];
1624         // If a Wine process terminates abruptly while it has the display captured
1625         // and switched to a different resolution, Mac OS X will uncapture the
1626         // displays and switch their resolutions back.  However, the other Wine
1627         // processes won't have their notion of the desktop rect changed back.
1628         // This can lead them to refuse to draw or acknowledge clicks in certain
1629         // portions of their windows.
1630         //
1631         // To solve this, we synthesize a displays-changed event whenever we're
1632         // activated.  This will provoke a re-synchronization of Wine's notion of
1633         // the desktop rect with the actual state.
1634         [self sendDisplaysChanged:TRUE];
1636         // The cursor probably moved while we were inactive.  Accumulated mouse
1637         // movement deltas are invalidated.  Make sure the next mouse move event
1638         // starts over from an absolute baseline.
1639         forceNextMouseMoveAbsolute = TRUE;
1640     }
1642     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1643     {
1644         primaryScreenHeightValid = FALSE;
1645         [self sendDisplaysChanged:FALSE];
1647         // When the display configuration changes, the cursor position may jump.
1648         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1649         // mouse move event starts over from an absolute baseline.
1650         forceNextMouseMoveAbsolute = TRUE;
1651     }
1653     - (void)applicationDidResignActive:(NSNotification *)notification
1654     {
1655         macdrv_event* event;
1656         WineEventQueue* queue;
1658         [self invalidateGotFocusEvents];
1660         event = macdrv_create_event(APP_DEACTIVATED, nil);
1662         [eventQueuesLock lock];
1663         for (queue in eventQueues)
1664             [queue postEvent:event];
1665         [eventQueuesLock unlock];
1667         macdrv_release_event(event);
1668     }
1670     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1671     {
1672         NSApplicationTerminateReply ret = NSTerminateNow;
1673         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1674         NSAppleEventDescriptor* desc = [m currentAppleEvent];
1675         macdrv_event* event;
1676         WineEventQueue* queue;
1678         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1679         event->deliver = 1;
1680         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1681         {
1682             case kAELogOut:
1683             case kAEReallyLogOut:
1684                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1685                 break;
1686             case kAEShowRestartDialog:
1687                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1688                 break;
1689             case kAEShowShutdownDialog:
1690                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1691                 break;
1692             default:
1693                 event->app_quit_requested.reason = QUIT_REASON_NONE;
1694                 break;
1695         }
1697         [eventQueuesLock lock];
1699         if ([eventQueues count])
1700         {
1701             for (queue in eventQueues)
1702                 [queue postEvent:event];
1703             ret = NSTerminateLater;
1704         }
1706         [eventQueuesLock unlock];
1708         macdrv_release_event(event);
1710         return ret;
1711     }
1713     - (void)applicationWillResignActive:(NSNotification *)notification
1714     {
1715         [self deactivateCursorClipping];
1717         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1718             WineWindow* window = obj;
1719             NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1720             if ([window level] > level)
1721                 [window setLevel:level];
1722         }];
1723     }
1725 /***********************************************************************
1726  *              PerformRequest
1728  * Run-loop-source perform callback.  Pull request blocks from the
1729  * array of queued requests and invoke them.
1730  */
1731 static void PerformRequest(void *info)
1733     WineApplicationController* controller = [WineApplicationController sharedController];
1735     for (;;)
1736     {
1737         __block dispatch_block_t block;
1739         dispatch_sync(controller->requestsManipQueue, ^{
1740             if ([controller->requests count])
1741             {
1742                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1743                 [controller->requests removeObjectAtIndex:0];
1744             }
1745             else
1746                 block = nil;
1747         });
1749         if (!block)
1750             break;
1752         block();
1753         [block release];
1754     }
1757 /***********************************************************************
1758  *              OnMainThreadAsync
1760  * Run a block on the main thread asynchronously.
1761  */
1762 void OnMainThreadAsync(dispatch_block_t block)
1764     WineApplicationController* controller = [WineApplicationController sharedController];
1766     block = [block copy];
1767     dispatch_sync(controller->requestsManipQueue, ^{
1768         [controller->requests addObject:block];
1769     });
1770     [block release];
1771     CFRunLoopSourceSignal(controller->requestSource);
1772     CFRunLoopWakeUp(CFRunLoopGetMain());
1775 @end
1777 /***********************************************************************
1778  *              LogError
1779  */
1780 void LogError(const char* func, NSString* format, ...)
1782     va_list args;
1783     va_start(args, format);
1784     LogErrorv(func, format, args);
1785     va_end(args);
1788 /***********************************************************************
1789  *              LogErrorv
1790  */
1791 void LogErrorv(const char* func, NSString* format, va_list args)
1793     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1795     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1796     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1797     [message release];
1799     [pool release];
1802 /***********************************************************************
1803  *              macdrv_window_rejected_focus
1805  * Pass focus to the next window that hasn't already rejected this same
1806  * WINDOW_GOT_FOCUS event.
1807  */
1808 void macdrv_window_rejected_focus(const macdrv_event *event)
1810     OnMainThread(^{
1811         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1812     });
1815 /***********************************************************************
1816  *              macdrv_get_keyboard_layout
1818  * Returns the keyboard layout uchr data.
1819  */
1820 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1822     __block CFDataRef result = NULL;
1824     OnMainThread(^{
1825         TISInputSourceRef inputSource;
1827         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1828         if (inputSource)
1829         {
1830             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1831                                 kTISPropertyUnicodeKeyLayoutData);
1832             result = CFDataCreateCopy(NULL, uchr);
1833             CFRelease(inputSource);
1835             *keyboard_type = [WineApplicationController sharedController].keyboardType;
1836             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1837         }
1838     });
1840     return result;
1843 /***********************************************************************
1844  *              macdrv_beep
1846  * Play the beep sound configured by the user in System Preferences.
1847  */
1848 void macdrv_beep(void)
1850     OnMainThreadAsync(^{
1851         NSBeep();
1852     });
1855 /***********************************************************************
1856  *              macdrv_set_display_mode
1857  */
1858 int macdrv_set_display_mode(const struct macdrv_display* display,
1859                             CGDisplayModeRef display_mode)
1861     __block int ret;
1863     OnMainThread(^{
1864         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1865     });
1867     return ret;
1870 /***********************************************************************
1871  *              macdrv_set_cursor
1873  * Set the cursor.
1875  * If name is non-NULL, it is a selector for a class method on NSCursor
1876  * identifying the cursor to set.  In that case, frames is ignored.  If
1877  * name is NULL, then frames is used.
1879  * frames is an array of dictionaries.  Each dictionary is a frame of
1880  * an animated cursor.  Under the key "image" is a CGImage for the
1881  * frame.  Under the key "duration" is a CFNumber time interval, in
1882  * seconds, for how long that frame is presented before proceeding to
1883  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
1884  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1885  * This is the hot spot, measured in pixels down and to the right of the
1886  * top-left corner of the image.
1888  * If the array has exactly 1 element, the cursor is static, not
1889  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
1890  */
1891 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1893     SEL sel;
1895     sel = NSSelectorFromString((NSString*)name);
1896     if (sel)
1897     {
1898         OnMainThreadAsync(^{
1899             WineApplicationController* controller = [WineApplicationController sharedController];
1900             NSCursor* cursor = [NSCursor performSelector:sel];
1901             [controller setCursorWithFrames:nil];
1902             [cursor set];
1903             [controller unhideCursor];
1904         });
1905     }
1906     else
1907     {
1908         NSArray* nsframes = (NSArray*)frames;
1909         if ([nsframes count])
1910         {
1911             OnMainThreadAsync(^{
1912                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1913             });
1914         }
1915         else
1916         {
1917             OnMainThreadAsync(^{
1918                 WineApplicationController* controller = [WineApplicationController sharedController];
1919                 [controller setCursorWithFrames:nil];
1920                 [controller hideCursor];
1921             });
1922         }
1923     }
1926 /***********************************************************************
1927  *              macdrv_get_cursor_position
1929  * Obtains the current cursor position.  Returns zero on failure,
1930  * non-zero on success.
1931  */
1932 int macdrv_get_cursor_position(CGPoint *pos)
1934     OnMainThread(^{
1935         NSPoint location = [NSEvent mouseLocation];
1936         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1937         *pos = NSPointToCGPoint(location);
1938     });
1940     return TRUE;
1943 /***********************************************************************
1944  *              macdrv_set_cursor_position
1946  * Sets the cursor position without generating events.  Returns zero on
1947  * failure, non-zero on success.
1948  */
1949 int macdrv_set_cursor_position(CGPoint pos)
1951     __block int ret;
1953     OnMainThread(^{
1954         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1955     });
1957     return ret;
1960 /***********************************************************************
1961  *              macdrv_clip_cursor
1963  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
1964  * to or larger than the whole desktop region, the cursor is unclipped.
1965  * Returns zero on failure, non-zero on success.
1966  */
1967 int macdrv_clip_cursor(CGRect rect)
1969     __block int ret;
1971     OnMainThread(^{
1972         WineApplicationController* controller = [WineApplicationController sharedController];
1973         BOOL clipping = FALSE;
1975         if (!CGRectIsInfinite(rect))
1976         {
1977             NSRect nsrect = NSRectFromCGRect(rect);
1978             NSScreen* screen;
1980             /* Convert the rectangle from top-down coords to bottom-up. */
1981             [controller flipRect:&nsrect];
1983             clipping = FALSE;
1984             for (screen in [NSScreen screens])
1985             {
1986                 if (!NSContainsRect(nsrect, [screen frame]))
1987                 {
1988                     clipping = TRUE;
1989                     break;
1990                 }
1991             }
1992         }
1994         if (clipping)
1995             ret = [controller startClippingCursor:rect];
1996         else
1997             ret = [controller stopClippingCursor];
1998     });
2000     return ret;
2003 /***********************************************************************
2004  *              macdrv_set_application_icon
2006  * Set the application icon.  The images array contains CGImages.  If
2007  * there are more than one, then they represent different sizes or
2008  * color depths from the icon resource.  If images is NULL or empty,
2009  * restores the default application image.
2010  */
2011 void macdrv_set_application_icon(CFArrayRef images)
2013     NSArray* imageArray = (NSArray*)images;
2015     OnMainThreadAsync(^{
2016         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2017     });
2020 /***********************************************************************
2021  *              macdrv_quit_reply
2022  */
2023 void macdrv_quit_reply(int reply)
2025     OnMainThread(^{
2026         [NSApp replyToApplicationShouldTerminate:reply];
2027     });
2030 /***********************************************************************
2031  *              macdrv_using_input_method
2032  */
2033 int macdrv_using_input_method(void)
2035     __block BOOL ret;
2037     OnMainThread(^{
2038         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2039     });
2041     return ret;
2044 /***********************************************************************
2045  *              macdrv_set_mouse_capture_window
2046  */
2047 void macdrv_set_mouse_capture_window(macdrv_window window)
2049     WineWindow* w = (WineWindow*)window;
2051     OnMainThread(^{
2052         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2053     });