urlmon: Recognize <body> tag in FindMimeFromData function.
[wine/multimedia.git] / dlls / winemac.drv / cocoa_app.m
blobd454513d34d58a9e12df775f9baed814782ff1c8
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) NSCursor* cursor;
84 @property (retain, nonatomic) NSImage* applicationIcon;
85 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
86 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
88     - (void) setupObservations;
89     - (void) applicationDidBecomeActive:(NSNotification *)notification;
91     static void PerformRequest(void *info);
93 @end
96 @implementation WineApplicationController
98     @synthesize keyboardType, lastFlagsChanged;
99     @synthesize applicationIcon;
100     @synthesize cursorFrames, cursorTimer, cursor;
101     @synthesize mouseCaptureWindow;
103     @synthesize clippingCursor;
105     + (void) initialize
106     {
107         if (self == [WineApplicationController class])
108         {
109             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
110                                       @"", @"NSQuotedKeystrokeBinding",
111                                       @"", @"NSRepeatCountBinding",
112                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
113                                       nil];
114             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
115         }
116     }
118     + (WineApplicationController*) sharedController
119     {
120         static WineApplicationController* sharedController;
121         static dispatch_once_t once;
123         dispatch_once(&once, ^{
124             sharedController = [[self alloc] init];
125         });
127         return sharedController;
128     }
130     - (id) init
131     {
132         self = [super init];
133         if (self != nil)
134         {
135             CFRunLoopSourceContext context = { 0 };
136             context.perform = PerformRequest;
137             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
138             if (!requestSource)
139             {
140                 [self release];
141                 return nil;
142             }
143             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
144             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
146             requests =  [[NSMutableArray alloc] init];
147             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
149             eventQueues = [[NSMutableArray alloc] init];
150             eventQueuesLock = [[NSLock alloc] init];
152             keyWindows = [[NSMutableArray alloc] init];
154             originalDisplayModes = [[NSMutableDictionary alloc] init];
155             latentDisplayModes = [[NSMutableDictionary alloc] init];
157             warpRecords = [[NSMutableArray alloc] init];
159             windowsBeingDragged = [[NSMutableSet alloc] init];
161             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
162                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
163             {
164                 [self release];
165                 return nil;
166             }
168             [self setupObservations];
170             keyboardType = LMGetKbdType();
172             if ([NSApp isActive])
173                 [self applicationDidBecomeActive:nil];
174         }
175         return self;
176     }
178     - (void) dealloc
179     {
180         [windowsBeingDragged release];
181         [cursor release];
182         [screenFrameCGRects release];
183         [applicationIcon release];
184         [warpRecords release];
185         [cursorTimer release];
186         [cursorFrames release];
187         [latentDisplayModes release];
188         [originalDisplayModes release];
189         [keyWindows release];
190         [eventQueues release];
191         [eventQueuesLock release];
192         if (requestsManipQueue) dispatch_release(requestsManipQueue);
193         [requests release];
194         if (requestSource)
195         {
196             CFRunLoopSourceInvalidate(requestSource);
197             CFRelease(requestSource);
198         }
199         [super dealloc];
200     }
202     - (void) transformProcessToForeground
203     {
204         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
205         {
206             NSMenu* mainMenu;
207             NSMenu* submenu;
208             NSString* bundleName;
209             NSString* title;
210             NSMenuItem* item;
212             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
213             [NSApp activateIgnoringOtherApps:YES];
215             mainMenu = [[[NSMenu alloc] init] autorelease];
217             // Application menu
218             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
219             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
221             if ([bundleName length])
222                 title = [NSString stringWithFormat:@"Hide %@", bundleName];
223             else
224                 title = @"Hide";
225             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
227             item = [submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
228             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
230             item = [submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
232             [submenu addItem:[NSMenuItem separatorItem]];
234             if ([bundleName length])
235                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
236             else
237                 title = @"Quit";
238             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
239             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
240             item = [[[NSMenuItem alloc] init] autorelease];
241             [item setTitle:@"Wine"];
242             [item setSubmenu:submenu];
243             [mainMenu addItem:item];
245             // Window menu
246             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
247             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
248             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
249             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
250             {
251                 item = [submenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
252                 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
253             }
254             [submenu addItem:[NSMenuItem separatorItem]];
255             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
256             item = [[[NSMenuItem alloc] init] autorelease];
257             [item setTitle:@"Window"];
258             [item setSubmenu:submenu];
259             [mainMenu addItem:item];
261             [NSApp setMainMenu:mainMenu];
262             [NSApp setWindowsMenu:submenu];
264             [NSApp setApplicationIconImage:self.applicationIcon];
265         }
266     }
268     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
269     {
270         PerformRequest(NULL);
272         do
273         {
274             if (processEvents)
275             {
276                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
277                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
278                                                     untilDate:timeout
279                                                        inMode:NSDefaultRunLoopMode
280                                                       dequeue:YES];
281                 if (event)
282                     [NSApp sendEvent:event];
283                 [pool release];
284             }
285             else
286                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
287         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
289         return *done;
290     }
292     - (BOOL) registerEventQueue:(WineEventQueue*)queue
293     {
294         [eventQueuesLock lock];
295         [eventQueues addObject:queue];
296         [eventQueuesLock unlock];
297         return TRUE;
298     }
300     - (void) unregisterEventQueue:(WineEventQueue*)queue
301     {
302         [eventQueuesLock lock];
303         [eventQueues removeObjectIdenticalTo:queue];
304         [eventQueuesLock unlock];
305     }
307     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
308     {
309         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
310     }
312     - (double) ticksForEventTime:(NSTimeInterval)eventTime
313     {
314         return (eventTime + eventTimeAdjustment) * 1000;
315     }
317     /* Invalidate old focus offers across all queues. */
318     - (void) invalidateGotFocusEvents
319     {
320         WineEventQueue* queue;
322         windowFocusSerial++;
324         [eventQueuesLock lock];
325         for (queue in eventQueues)
326         {
327             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
328                                    forWindow:nil];
329         }
330         [eventQueuesLock unlock];
331     }
333     - (void) windowGotFocus:(WineWindow*)window
334     {
335         macdrv_event* event;
337         [self invalidateGotFocusEvents];
339         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
340         event->window_got_focus.serial = windowFocusSerial;
341         if (triedWindows)
342             event->window_got_focus.tried_windows = [triedWindows retain];
343         else
344             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
345         [window.queue postEvent:event];
346         macdrv_release_event(event);
347     }
349     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
350     {
351         if (event->window_got_focus.serial == windowFocusSerial)
352         {
353             NSMutableArray* windows = [keyWindows mutableCopy];
354             NSNumber* windowNumber;
355             WineWindow* window;
357             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
358             {
359                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
360                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
361                     ![windows containsObject:window])
362                     [windows addObject:window];
363             }
365             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
366             [triedWindows addObject:(WineWindow*)event->window];
367             for (window in windows)
368             {
369                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
370                 {
371                     [window makeKeyWindow];
372                     break;
373                 }
374             }
375             triedWindows = nil;
376             [windows release];
377         }
378     }
380     - (void) keyboardSelectionDidChange
381     {
382         TISInputSourceRef inputSourceLayout;
384         inputSourceIsInputMethodValid = FALSE;
386         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
387         if (inputSourceLayout)
388         {
389             CFDataRef uchr;
390             uchr = TISGetInputSourceProperty(inputSourceLayout,
391                     kTISPropertyUnicodeKeyLayoutData);
392             if (uchr)
393             {
394                 macdrv_event* event;
395                 WineEventQueue* queue;
397                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
398                 event->keyboard_changed.keyboard_type = self.keyboardType;
399                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
400                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
401                 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
403                 if (event->keyboard_changed.uchr)
404                 {
405                     [eventQueuesLock lock];
407                     for (queue in eventQueues)
408                         [queue postEvent:event];
410                     [eventQueuesLock unlock];
411                 }
413                 macdrv_release_event(event);
414             }
416             CFRelease(inputSourceLayout);
417         }
418     }
420     - (void) enabledKeyboardInputSourcesChanged
421     {
422         macdrv_layout_list_needs_update = TRUE;
423     }
425     - (CGFloat) primaryScreenHeight
426     {
427         if (!primaryScreenHeightValid)
428         {
429             NSArray* screens = [NSScreen screens];
430             NSUInteger count = [screens count];
431             if (count)
432             {
433                 NSUInteger size;
434                 CGRect* rect;
435                 NSScreen* screen;
437                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
438                 primaryScreenHeightValid = TRUE;
440                 size = count * sizeof(CGRect);
441                 if (!screenFrameCGRects)
442                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
443                 else
444                     [screenFrameCGRects setLength:size];
446                 rect = [screenFrameCGRects mutableBytes];
447                 for (screen in screens)
448                 {
449                     CGRect temp = NSRectToCGRect([screen frame]);
450                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
451                     *rect++ = temp;
452                 }
453             }
454             else
455                 return 1280; /* arbitrary value */
456         }
458         return primaryScreenHeight;
459     }
461     - (NSPoint) flippedMouseLocation:(NSPoint)point
462     {
463         /* This relies on the fact that Cocoa's mouse location points are
464            actually off by one (precisely because they were flipped from
465            Quartz screen coordinates using this same technique). */
466         point.y = [self primaryScreenHeight] - point.y;
467         return point;
468     }
470     - (void) flipRect:(NSRect*)rect
471     {
472         // We don't use -primaryScreenHeight here so there's no chance of having
473         // out-of-date cached info.  This method is called infrequently enough
474         // that getting the screen height each time is not prohibitively expensive.
475         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
476     }
478     - (WineWindow*) frontWineWindow
479     {
480         NSNumber* windowNumber;
481         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
482         {
483             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
484             if ([window isKindOfClass:[WineWindow class]] && [window screen])
485                 return (WineWindow*)window;
486         }
488         return nil;
489     }
491     - (void) adjustWindowLevels:(BOOL)active
492     {
493         NSArray* windowNumbers;
494         NSMutableArray* wineWindows;
495         NSNumber* windowNumber;
496         NSUInteger nextFloatingIndex = 0;
497         __block NSInteger maxLevel = NSIntegerMin;
498         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
499         __block WineWindow* prev = nil;
500         WineWindow* window;
502         if ([NSApp isHidden]) return;
504         windowNumbers = [NSWindow windowNumbersWithOptions:0];
505         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
507         // For the most part, we rely on the window server's ordering of the windows
508         // to be authoritative.  The one exception is if the "floating" property of
509         // one of the windows has been changed, it may be in the wrong level and thus
510         // in the order.  This method is what's supposed to fix that up.  So build
511         // a list of Wine windows sorted first by floating-ness and then by order
512         // as indicated by the window server.
513         for (windowNumber in windowNumbers)
514         {
515             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
516             if ([window isKindOfClass:[WineWindow class]])
517             {
518                 if (window.floating)
519                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
520                 else
521                     [wineWindows addObject:window];
522             }
523         }
525         NSDisableScreenUpdates();
527         // Go from back to front so that all windows in front of one which is
528         // elevated for full-screen are also elevated.
529         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
530                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
531             WineWindow* window = (WineWindow*)obj;
532             NSInteger origLevel = [window level];
533             NSInteger newLevel = [window minimumLevelForActive:active];
535             if (newLevel < maxLevel)
536                 newLevel = maxLevel;
537             else
538                 maxLevel = newLevel;
540             if (!window.floating && maxNonfloatingLevel < newLevel)
541                 maxNonfloatingLevel = newLevel;
543             if (newLevel != origLevel)
544             {
545                 [window setLevel:newLevel];
547                 // -setLevel: puts the window at the front of its new level.  If
548                 // we decreased the level, that's good (it was in front of that
549                 // level before, so it should still be now).  But if we increased
550                 // the level, the window should be toward the back (but still
551                 // ahead of the previous windows we did this to).
552                 if (origLevel < newLevel)
553                 {
554                     if (prev)
555                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
556                     else
557                         [window orderBack:nil];
558                 }
559             }
561             prev = window;
562         }];
564         NSEnableScreenUpdates();
566         [wineWindows release];
568         // The above took care of the visible windows on the current space.  That
569         // leaves windows on other spaces, minimized windows, and windows which
570         // are not ordered in.  We want to leave windows on other spaces alone
571         // so the space remains just as they left it (when viewed in Exposé or
572         // Mission Control, for example).  We'll adjust the window levels again
573         // after we switch to another space, anyway.  Windows which aren't
574         // ordered in will be handled when we order them in.  Minimized windows
575         // on the current space should be set to the level they would have gotten
576         // if they were at the front of the windows with the same floating-ness,
577         // because that's where they'll go if/when they are unminimized.  Again,
578         // for good measure we'll adjust window levels again when a window is
579         // unminimized, too.
580         for (window in [NSApp windows])
581         {
582             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
583                 [window isOnActiveSpace])
584             {
585                 NSInteger origLevel = [window level];
586                 NSInteger newLevel = [window minimumLevelForActive:YES];
587                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
589                 if (newLevel < maxLevelForType)
590                     newLevel = maxLevelForType;
592                 if (newLevel != origLevel)
593                     [window setLevel:newLevel];
594             }
595         }
596     }
598     - (void) adjustWindowLevels
599     {
600         [self adjustWindowLevels:[NSApp isActive]];
601     }
603     - (void) updateFullscreenWindows
604     {
605         if (capture_displays_for_fullscreen && [NSApp isActive])
606         {
607             BOOL anyFullscreen = FALSE;
608             NSNumber* windowNumber;
609             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
610             {
611                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
612                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
613                 {
614                     anyFullscreen = TRUE;
615                     break;
616                 }
617             }
619             if (anyFullscreen)
620             {
621                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
622                     displaysCapturedForFullscreen = TRUE;
623             }
624             else if (displaysCapturedForFullscreen)
625             {
626                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
627                     displaysCapturedForFullscreen = FALSE;
628             }
629         }
630     }
632     - (void) activeSpaceDidChange
633     {
634         [self updateFullscreenWindows];
635         [self adjustWindowLevels];
636     }
638     - (void) sendDisplaysChanged:(BOOL)activating
639     {
640         macdrv_event* event;
641         WineEventQueue* queue;
643         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
644         event->displays_changed.activating = activating;
646         [eventQueuesLock lock];
648         // If we're activating, then we just need one of our threads to get the
649         // event, so it can send it directly to the desktop window.  Otherwise,
650         // we need all of the threads to get it because we don't know which owns
651         // the desktop window and only that one will do anything with it.
652         if (activating) event->deliver = 1;
654         for (queue in eventQueues)
655             [queue postEvent:event];
656         [eventQueuesLock unlock];
658         macdrv_release_event(event);
659     }
661     // We can compare two modes directly using CFEqual, but that may require that
662     // they are identical to a level that we don't need.  In particular, when the
663     // OS switches between the integrated and discrete GPUs, the set of display
664     // modes can change in subtle ways.  We're interested in whether two modes
665     // match in their most salient features, even if they aren't identical.
666     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
667     {
668         NSString *encoding1, *encoding2;
669         uint32_t ioflags1, ioflags2, different;
670         double refresh1, refresh2;
672         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
673         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
675         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
676         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
677         if (![encoding1 isEqualToString:encoding2]) return FALSE;
679         ioflags1 = CGDisplayModeGetIOFlags(mode1);
680         ioflags2 = CGDisplayModeGetIOFlags(mode2);
681         different = ioflags1 ^ ioflags2;
682         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
683                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
684             return FALSE;
686         refresh1 = CGDisplayModeGetRefreshRate(mode1);
687         if (refresh1 == 0) refresh1 = 60;
688         refresh2 = CGDisplayModeGetRefreshRate(mode2);
689         if (refresh2 == 0) refresh2 = 60;
690         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
692         return TRUE;
693     }
695     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
696     {
697         CGDisplayModeRef ret = NULL;
698         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
699         for (id candidateModeObject in modes)
700         {
701             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
702             if ([self mode:candidateMode matchesMode:mode])
703             {
704                 ret = candidateMode;
705                 break;
706             }
707         }
708         return ret;
709     }
711     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
712     {
713         BOOL ret = FALSE;
714         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
715         CGDisplayModeRef originalMode;
717         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
719         if (originalMode && [self mode:mode matchesMode:originalMode])
720         {
721             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
722             {
723                 CGRestorePermanentDisplayConfiguration();
724                 if (!displaysCapturedForFullscreen)
725                     CGReleaseAllDisplays();
726                 [originalDisplayModes removeAllObjects];
727                 ret = TRUE;
728             }
729             else // ... otherwise, try to restore just the one display
730             {
731                 mode = [self modeMatchingMode:mode forDisplay:displayID];
732                 if (mode && CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
733                 {
734                     [originalDisplayModes removeObjectForKey:displayIDKey];
735                     ret = TRUE;
736                 }
737             }
738         }
739         else
740         {
741             BOOL active = [NSApp isActive];
742             CGDisplayModeRef currentMode;
744             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
745             if (!currentMode)
746                 currentMode = CGDisplayCopyDisplayMode(displayID);
747             if (!currentMode) // Invalid display ID
748                 return FALSE;
750             if ([self mode:mode matchesMode:currentMode]) // Already there!
751             {
752                 CGDisplayModeRelease(currentMode);
753                 return TRUE;
754             }
756             CGDisplayModeRelease(currentMode);
757             currentMode = NULL;
759             mode = [self modeMatchingMode:mode forDisplay:displayID];
760             if (!mode)
761                 return FALSE;
763             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
764                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
765             {
766                 if (active)
767                 {
768                     // If we get here, we have the displays captured.  If we don't
769                     // know the original mode of the display, the current mode must
770                     // be the original.  We should re-query the current mode since
771                     // another process could have changed it between when we last
772                     // checked and when we captured the displays.
773                     if (!originalMode)
774                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
776                     if (originalMode)
777                         ret = (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr);
778                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
779                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
780                     else if (![originalDisplayModes count])
781                     {
782                         CGRestorePermanentDisplayConfiguration();
783                         if (!displaysCapturedForFullscreen)
784                             CGReleaseAllDisplays();
785                     }
787                     if (currentMode)
788                         CGDisplayModeRelease(currentMode);
789                 }
790                 else
791                 {
792                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
793                     ret = TRUE;
794                 }
795             }
796         }
798         if (ret)
799             [self adjustWindowLevels];
801         return ret;
802     }
804     - (BOOL) areDisplaysCaptured
805     {
806         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
807     }
809     - (void) updateCursor:(BOOL)force
810     {
811         if (force || lastTargetWindow)
812         {
813             if (clientWantsCursorHidden && !cursorHidden)
814             {
815                 [NSCursor hide];
816                 cursorHidden = TRUE;
817             }
819             if (!cursorIsCurrent)
820             {
821                 [cursor set];
822                 cursorIsCurrent = TRUE;
823             }
825             if (!clientWantsCursorHidden && cursorHidden)
826             {
827                 [NSCursor unhide];
828                 cursorHidden = FALSE;
829             }
830         }
831         else
832         {
833             if (cursorIsCurrent)
834             {
835                 [[NSCursor arrowCursor] set];
836                 cursorIsCurrent = FALSE;
837             }
838             if (cursorHidden)
839             {
840                 [NSCursor unhide];
841                 cursorHidden = FALSE;
842             }
843         }
844     }
846     - (void) hideCursor
847     {
848         if (!clientWantsCursorHidden)
849         {
850             clientWantsCursorHidden = TRUE;
851             [self updateCursor:TRUE];
852         }
853     }
855     - (void) unhideCursor
856     {
857         if (clientWantsCursorHidden)
858         {
859             clientWantsCursorHidden = FALSE;
860             [self updateCursor:FALSE];
861         }
862     }
864     - (void) setCursor:(NSCursor*)newCursor
865     {
866         if (newCursor != cursor)
867         {
868             [cursor release];
869             cursor = [newCursor retain];
870             cursorIsCurrent = FALSE;
871             [self updateCursor:FALSE];
872         }
873     }
875     - (void) setCursor
876     {
877         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
878         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
879         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
880         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
881         CGPoint hotSpot;
883         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
884             hotSpot = CGPointZero;
885         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
886         [image release];
887         [self unhideCursor];
888     }
890     - (void) nextCursorFrame:(NSTimer*)theTimer
891     {
892         NSDictionary* frame;
893         NSTimeInterval duration;
894         NSDate* date;
896         cursorFrame++;
897         if (cursorFrame >= [cursorFrames count])
898             cursorFrame = 0;
899         [self setCursor];
901         frame = [cursorFrames objectAtIndex:cursorFrame];
902         duration = [[frame objectForKey:@"duration"] doubleValue];
903         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
904         [cursorTimer setFireDate:date];
905     }
907     - (void) setCursorWithFrames:(NSArray*)frames
908     {
909         if (self.cursorFrames == frames)
910             return;
912         self.cursorFrames = frames;
913         cursorFrame = 0;
914         [cursorTimer invalidate];
915         self.cursorTimer = nil;
917         if ([frames count])
918         {
919             if ([frames count] > 1)
920             {
921                 NSDictionary* frame = [frames objectAtIndex:0];
922                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
923                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
924                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
925                                                              interval:1000000
926                                                                target:self
927                                                              selector:@selector(nextCursorFrame:)
928                                                              userInfo:nil
929                                                               repeats:YES] autorelease];
930                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
931             }
933             [self setCursor];
934         }
935     }
937     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
938     {
939         NSImage* nsimage = nil;
941         if ([images count])
942         {
943             NSSize bestSize = NSZeroSize;
944             id image;
946             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
948             for (image in images)
949             {
950                 CGImageRef cgimage = (CGImageRef)image;
951                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
952                 if (imageRep)
953                 {
954                     NSSize size = [imageRep size];
956                     [nsimage addRepresentation:imageRep];
957                     [imageRep release];
959                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
960                         bestSize = size;
961                 }
962             }
964             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
965                 [nsimage setSize:bestSize];
966             else
967                 nsimage = nil;
968         }
970         self.applicationIcon = nsimage;
971     }
973     - (void) handleCommandTab
974     {
975         if ([NSApp isActive])
976         {
977             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
978             NSRunningApplication* app;
979             NSRunningApplication* otherValidApp = nil;
981             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
982             {
983                 NSNumber* displayID;
984                 for (displayID in originalDisplayModes)
985                 {
986                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
987                     [latentDisplayModes setObject:(id)mode forKey:displayID];
988                     CGDisplayModeRelease(mode);
989                 }
991                 CGRestorePermanentDisplayConfiguration();
992                 CGReleaseAllDisplays();
993                 [originalDisplayModes removeAllObjects];
994                 displaysCapturedForFullscreen = FALSE;
995             }
997             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
998             {
999                 if (![app isEqual:thisApp] && !app.terminated &&
1000                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1001                 {
1002                     if (!app.hidden)
1003                     {
1004                         // There's another visible app.  Just hide ourselves and let
1005                         // the system activate the other app.
1006                         [NSApp hide:self];
1007                         return;
1008                     }
1010                     if (!otherValidApp)
1011                         otherValidApp = app;
1012                 }
1013             }
1015             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1016             // running, the first hidden GUI app.  If even that doesn't work, we
1017             // just fail to switch and remain the active app.
1018             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1019             if (!app) app = otherValidApp;
1020             [app unhide];
1021             [app activateWithOptions:0];
1022         }
1023     }
1025     /*
1026      * ---------- Cursor clipping methods ----------
1027      *
1028      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1029      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1030      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1031      * general case, we leverage that.  We disassociate mouse movements from
1032      * the cursor position and then move the cursor manually, keeping it within
1033      * the clipping rectangle.
1034      *
1035      * Moving the cursor manually isn't enough.  We need to modify the event
1036      * stream so that the events have the new location, too.  We need to do
1037      * this at a point before the events enter Cocoa, so that Cocoa will assign
1038      * the correct window to the event.  So, we install a Quartz event tap to
1039      * do that.
1040      *
1041      * Also, there's a complication when we move the cursor.  We use
1042      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1043      * events, but the change of cursor position is incorporated into the
1044      * deltas of the next mouse move event.  When the mouse is disassociated
1045      * from the cursor position, we need the deltas to only reflect actual
1046      * device movement, not programmatic changes.  So, the event tap cancels
1047      * out the change caused by our calls to CGWarpMouseCursorPosition().
1048      */
1049     - (void) clipCursorLocation:(CGPoint*)location
1050     {
1051         if (location->x < CGRectGetMinX(cursorClipRect))
1052             location->x = CGRectGetMinX(cursorClipRect);
1053         if (location->y < CGRectGetMinY(cursorClipRect))
1054             location->y = CGRectGetMinY(cursorClipRect);
1055         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1056             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1057         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1058             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1059     }
1061     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1062     {
1063         CGPoint oldLocation;
1065         if (currentLocation)
1066             oldLocation = *currentLocation;
1067         else
1068             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1070         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1071         {
1072             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1073             CGError err;
1075             warpRecord.from = oldLocation;
1076             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1078             /* Actually move the cursor. */
1079             err = CGWarpMouseCursorPosition(*newLocation);
1080             if (err != kCGErrorSuccess)
1081                 return FALSE;
1083             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1084             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1086             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1087             {
1088                 warpRecord.to = *newLocation;
1089                 [warpRecords addObject:warpRecord];
1090             }
1091         }
1093         return TRUE;
1094     }
1096     - (BOOL) isMouseMoveEventType:(CGEventType)type
1097     {
1098         switch(type)
1099         {
1100         case kCGEventMouseMoved:
1101         case kCGEventLeftMouseDragged:
1102         case kCGEventRightMouseDragged:
1103         case kCGEventOtherMouseDragged:
1104             return TRUE;
1105         }
1107         return FALSE;
1108     }
1110     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1111     {
1112         int warpsFinished = 0;
1113         for (WarpRecord* warpRecord in warpRecords)
1114         {
1115             if (warpRecord.timeAfter < eventTime ||
1116                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1117                 warpsFinished++;
1118             else
1119                 break;
1120         }
1122         return warpsFinished;
1123     }
1125     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1126                                 type:(CGEventType)type
1127                                event:(CGEventRef)event
1128     {
1129         CGEventTimestamp eventTime;
1130         CGPoint eventLocation, cursorLocation;
1132         if (type == kCGEventTapDisabledByUserInput)
1133             return event;
1134         if (type == kCGEventTapDisabledByTimeout)
1135         {
1136             CGEventTapEnable(cursorClippingEventTap, TRUE);
1137             return event;
1138         }
1140         if (!clippingCursor)
1141             return event;
1143         eventTime = CGEventGetTimestamp(event);
1144         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1146         eventLocation = CGEventGetLocation(event);
1148         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1150         if ([self isMouseMoveEventType:type])
1151         {
1152             double deltaX, deltaY;
1153             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1154             int i;
1156             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1157             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1159             for (i = 0; i < warpsFinished; i++)
1160             {
1161                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1162                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1163                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1164                 [warpRecords removeObjectAtIndex:0];
1165             }
1167             if (warpsFinished)
1168             {
1169                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1170                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1171             }
1173             synthesizedLocation.x += deltaX;
1174             synthesizedLocation.y += deltaY;
1175         }
1177         // If the event is destined for another process, don't clip it.  This may
1178         // happen if the user activates Exposé or Mission Control.  In that case,
1179         // our app does not resign active status, so clipping is still in effect,
1180         // but the cursor should not actually be clipped.
1181         //
1182         // In addition, the fact that mouse moves may have been delivered to a
1183         // different process means we have to treat the next one we receive as
1184         // absolute rather than relative.
1185         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1186             [self clipCursorLocation:&synthesizedLocation];
1187         else
1188             lastSetCursorPositionTime = lastEventTapEventTime;
1190         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1191         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1192             CGEventSetLocation(event, synthesizedLocation);
1194         return event;
1195     }
1197     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1198                                        CGEventRef event, void *refcon)
1199     {
1200         WineApplicationController* controller = refcon;
1201         return [controller eventTapWithProxy:proxy type:type event:event];
1202     }
1204     - (BOOL) installEventTap
1205     {
1206         ProcessSerialNumber psn;
1207         OSErr err;
1208         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1209                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1210                            CGEventMaskBit(kCGEventRightMouseDown)       |
1211                            CGEventMaskBit(kCGEventRightMouseUp)         |
1212                            CGEventMaskBit(kCGEventMouseMoved)           |
1213                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1214                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1215                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1216                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1217                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1218                            CGEventMaskBit(kCGEventScrollWheel);
1219         CFRunLoopSourceRef source;
1220         void* appServices;
1221         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1223         if (cursorClippingEventTap)
1224             return TRUE;
1226         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1227         // framework with dlsym() because the Win32 function of the same name
1228         // obscures it.
1229         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1230         if (!appServices)
1231             return FALSE;
1233         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1234         if (!pGetCurrentProcess)
1235         {
1236             dlclose(appServices);
1237             return FALSE;
1238         }
1240         err = pGetCurrentProcess(&psn);
1241         dlclose(appServices);
1242         if (err != noErr)
1243             return FALSE;
1245         // We create an annotated session event tap rather than a process-specific
1246         // event tap because we need to programmatically move the cursor even when
1247         // mouse moves are directed to other processes.  We disable our tap when
1248         // other processes are active, but things like Exposé are handled by other
1249         // processes even when we remain active.
1250         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1251             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1252         if (!cursorClippingEventTap)
1253             return FALSE;
1255         CGEventTapEnable(cursorClippingEventTap, FALSE);
1257         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1258         if (!source)
1259         {
1260             CFRelease(cursorClippingEventTap);
1261             cursorClippingEventTap = NULL;
1262             return FALSE;
1263         }
1265         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1266         CFRelease(source);
1267         return TRUE;
1268     }
1270     - (BOOL) setCursorPosition:(CGPoint)pos
1271     {
1272         BOOL ret;
1274         if ([windowsBeingDragged count])
1275             ret = FALSE;
1276         else if (clippingCursor)
1277         {
1278             [self clipCursorLocation:&pos];
1280             ret = [self warpCursorTo:&pos from:NULL];
1281             synthesizedLocation = pos;
1282             if (ret)
1283             {
1284                 // We want to discard mouse-move events that have already been
1285                 // through the event tap, because it's too late to account for
1286                 // the setting of the cursor position with them.  However, the
1287                 // events that may be queued with times after that but before
1288                 // the above warp can still be used.  So, use the last event
1289                 // tap event time so that -sendEvent: doesn't discard them.
1290                 lastSetCursorPositionTime = lastEventTapEventTime;
1291             }
1292         }
1293         else
1294         {
1295             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1296             if (ret)
1297             {
1298                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1300                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1301                 // the mouse from the cursor position for 0.25 seconds.  This means
1302                 // that mouse movement during that interval doesn't move the cursor
1303                 // and events carry a constant location (the warped-to position)
1304                 // even though they have delta values.  This screws us up because
1305                 // the accumulated deltas we send to Wine don't match any eventual
1306                 // absolute position we send (like with a button press).  We can
1307                 // work around this by simply forcibly reassociating the mouse and
1308                 // cursor position.
1309                 CGAssociateMouseAndMouseCursorPosition(true);
1310             }
1311         }
1313         if (ret)
1314         {
1315             WineEventQueue* queue;
1317             // Discard all pending mouse move events.
1318             [eventQueuesLock lock];
1319             for (queue in eventQueues)
1320             {
1321                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1322                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1323                                        forWindow:nil];
1324                 [queue resetMouseEventPositions:pos];
1325             }
1326             [eventQueuesLock unlock];
1327         }
1329         return ret;
1330     }
1332     - (void) activateCursorClipping
1333     {
1334         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1335         {
1336             CGEventTapEnable(cursorClippingEventTap, TRUE);
1337             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1338         }
1339     }
1341     - (void) deactivateCursorClipping
1342     {
1343         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1344         {
1345             CGEventTapEnable(cursorClippingEventTap, FALSE);
1346             [warpRecords removeAllObjects];
1347             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1348         }
1349     }
1351     - (void) updateCursorClippingState
1352     {
1353         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1354             [self activateCursorClipping];
1355         else
1356             [self deactivateCursorClipping];
1357     }
1359     - (void) updateWindowsForCursorClipping
1360     {
1361         WineWindow* window;
1362         for (window in [NSApp windows])
1363         {
1364             if ([window isKindOfClass:[WineWindow class]])
1365                 [window updateForCursorClipping];
1366         }
1367     }
1369     - (BOOL) startClippingCursor:(CGRect)rect
1370     {
1371         CGError err;
1373         if (!cursorClippingEventTap && ![self installEventTap])
1374             return FALSE;
1376         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1377             CGEventTapIsEnabled(cursorClippingEventTap))
1378             return TRUE;
1380         err = CGAssociateMouseAndMouseCursorPosition(false);
1381         if (err != kCGErrorSuccess)
1382             return FALSE;
1384         clippingCursor = TRUE;
1385         cursorClipRect = rect;
1386         [self updateCursorClippingState];
1387         [self updateWindowsForCursorClipping];
1389         return TRUE;
1390     }
1392     - (BOOL) stopClippingCursor
1393     {
1394         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1395         if (err != kCGErrorSuccess)
1396             return FALSE;
1398         clippingCursor = FALSE;
1399         [self updateCursorClippingState];
1400         [self updateWindowsForCursorClipping];
1402         return TRUE;
1403     }
1405     - (BOOL) isKeyPressed:(uint16_t)keyCode
1406     {
1407         int bits = sizeof(pressedKeyCodes[0]) * 8;
1408         int index = keyCode / bits;
1409         uint32_t mask = 1 << (keyCode % bits);
1410         return (pressedKeyCodes[index] & mask) != 0;
1411     }
1413     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1414     {
1415         int bits = sizeof(pressedKeyCodes[0]) * 8;
1416         int index = keyCode / bits;
1417         uint32_t mask = 1 << (keyCode % bits);
1418         if (pressed)
1419             pressedKeyCodes[index] |= mask;
1420         else
1421             pressedKeyCodes[index] &= ~mask;
1422     }
1424     - (void) handleMouseMove:(NSEvent*)anEvent
1425     {
1426         WineWindow* targetWindow;
1427         BOOL drag = [anEvent type] != NSMouseMoved;
1429         if ([windowsBeingDragged count])
1430             targetWindow = nil;
1431         else if (mouseCaptureWindow)
1432             targetWindow = mouseCaptureWindow;
1433         else if (drag)
1434             targetWindow = (WineWindow*)[anEvent window];
1435         else
1436         {
1437             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1438                event indicates its window is the main window, even if the cursor is
1439                over a different window.  Find the actual WineWindow that is under the
1440                cursor and post the event as being for that window. */
1441             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1442             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1443             NSInteger windowUnderNumber;
1445             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1446                                   belowWindowWithWindowNumber:0];
1447             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1448             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1449                 targetWindow = nil;
1450         }
1452         if ([targetWindow isKindOfClass:[WineWindow class]])
1453         {
1454             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1455             macdrv_event* event;
1456             BOOL absolute;
1458             // If we recently warped the cursor (other than in our cursor-clipping
1459             // event tap), discard mouse move events until we see an event which is
1460             // later than that time.
1461             if (lastSetCursorPositionTime)
1462             {
1463                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1464                     return;
1466                 lastSetCursorPositionTime = 0;
1467                 forceNextMouseMoveAbsolute = TRUE;
1468             }
1470             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1471             {
1472                 absolute = TRUE;
1473                 forceNextMouseMoveAbsolute = FALSE;
1474             }
1475             else
1476             {
1477                 // Send absolute move events if the cursor is in the interior of
1478                 // its range.  Only send relative moves if the cursor is pinned to
1479                 // the boundaries of where it can go.  We compute the position
1480                 // that's one additional point in the direction of movement.  If
1481                 // that is outside of the clipping rect or desktop region (the
1482                 // union of the screen frames), then we figure the cursor would
1483                 // have moved outside if it could but it was pinned.
1484                 CGPoint computedPoint = point;
1485                 CGFloat deltaX = [anEvent deltaX];
1486                 CGFloat deltaY = [anEvent deltaY];
1488                 if (deltaX > 0.001)
1489                     computedPoint.x++;
1490                 else if (deltaX < -0.001)
1491                     computedPoint.x--;
1493                 if (deltaY > 0.001)
1494                     computedPoint.y++;
1495                 else if (deltaY < -0.001)
1496                     computedPoint.y--;
1498                 // Assume cursor is pinned for now
1499                 absolute = FALSE;
1500                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1501                 {
1502                     const CGRect* rects;
1503                     NSUInteger count, i;
1505                     // Caches screenFrameCGRects if necessary
1506                     [self primaryScreenHeight];
1508                     rects = [screenFrameCGRects bytes];
1509                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1511                     for (i = 0; i < count; i++)
1512                     {
1513                         if (CGRectContainsPoint(rects[i], computedPoint))
1514                         {
1515                             absolute = TRUE;
1516                             break;
1517                         }
1518                     }
1519                 }
1520             }
1522             if (absolute)
1523             {
1524                 if (clippingCursor)
1525                     [self clipCursorLocation:&point];
1527                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1528                 event->mouse_moved.x = point.x;
1529                 event->mouse_moved.y = point.y;
1531                 mouseMoveDeltaX = 0;
1532                 mouseMoveDeltaY = 0;
1533             }
1534             else
1535             {
1536                 /* Add event delta to accumulated delta error */
1537                 /* deltaY is already flipped */
1538                 mouseMoveDeltaX += [anEvent deltaX];
1539                 mouseMoveDeltaY += [anEvent deltaY];
1541                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1542                 event->mouse_moved.x = mouseMoveDeltaX;
1543                 event->mouse_moved.y = mouseMoveDeltaY;
1545                 /* Keep the remainder after integer truncation. */
1546                 mouseMoveDeltaX -= event->mouse_moved.x;
1547                 mouseMoveDeltaY -= event->mouse_moved.y;
1548             }
1550             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1551             {
1552                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1553                 event->mouse_moved.drag = drag;
1555                 [targetWindow.queue postEvent:event];
1556             }
1558             macdrv_release_event(event);
1560             lastTargetWindow = targetWindow;
1561         }
1562         else
1563             lastTargetWindow = nil;
1565         [self updateCursor:FALSE];
1566     }
1568     - (void) handleMouseButton:(NSEvent*)theEvent
1569     {
1570         WineWindow* window = (WineWindow*)[theEvent window];
1571         NSEventType type = [theEvent type];
1572         BOOL broughtWindowForward = FALSE;
1574         if ([window isKindOfClass:[WineWindow class]] &&
1575             !window.disabled && !window.noActivate &&
1576             type == NSLeftMouseDown &&
1577             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1578         {
1579             NSWindowButton windowButton;
1581             broughtWindowForward = TRUE;
1583             /* Any left-click on our window anyplace other than the close or
1584                minimize buttons will bring it forward. */
1585             for (windowButton = NSWindowCloseButton;
1586                  windowButton <= NSWindowMiniaturizeButton;
1587                  windowButton++)
1588             {
1589                 NSButton* button = [window standardWindowButton:windowButton];
1590                 if (button)
1591                 {
1592                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1593                     if ([button mouse:point inRect:[button bounds]])
1594                     {
1595                         broughtWindowForward = FALSE;
1596                         break;
1597                     }
1598                 }
1599             }
1600         }
1602         if ([windowsBeingDragged count])
1603             window = nil;
1604         else if (mouseCaptureWindow)
1605             window = mouseCaptureWindow;
1607         if ([window isKindOfClass:[WineWindow class]])
1608         {
1609             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1610             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1611             BOOL process;
1613             if (clippingCursor)
1614                 [self clipCursorLocation:&pt];
1616             if (pressed)
1617             {
1618                 if (mouseCaptureWindow)
1619                     process = TRUE;
1620                 else
1621                 {
1622                     // Test if the click was in the window's content area.
1623                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1624                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1625                     process = NSMouseInRect(nspoint, contentRect, NO);
1626                     if (process && [window styleMask] & NSResizableWindowMask)
1627                     {
1628                         // Ignore clicks in the grow box (resize widget).
1629                         HIPoint origin = { 0, 0 };
1630                         HIThemeGrowBoxDrawInfo info = { 0 };
1631                         HIRect bounds;
1632                         OSStatus status;
1634                         info.kind = kHIThemeGrowBoxKindNormal;
1635                         info.direction = kThemeGrowRight | kThemeGrowDown;
1636                         if ([window styleMask] & NSUtilityWindowMask)
1637                             info.size = kHIThemeGrowBoxSizeSmall;
1638                         else
1639                             info.size = kHIThemeGrowBoxSizeNormal;
1641                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1642                         if (status == noErr)
1643                         {
1644                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1645                                                         NSMinY(contentRect),
1646                                                         bounds.size.width,
1647                                                         bounds.size.height);
1648                             process = !NSMouseInRect(nspoint, growBox, NO);
1649                         }
1650                     }
1651                 }
1652                 if (process)
1653                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1654             }
1655             else
1656             {
1657                 NSEventType downType = type - 1;
1658                 NSUInteger downMask = NSEventMaskFromType(downType);
1659                 process = (unmatchedMouseDowns & downMask) != 0;
1660                 unmatchedMouseDowns &= ~downMask;
1661             }
1663             if (process)
1664             {
1665                 macdrv_event* event;
1667                 event = macdrv_create_event(MOUSE_BUTTON, window);
1668                 event->mouse_button.button = [theEvent buttonNumber];
1669                 event->mouse_button.pressed = pressed;
1670                 event->mouse_button.x = pt.x;
1671                 event->mouse_button.y = pt.y;
1672                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1674                 [window.queue postEvent:event];
1676                 macdrv_release_event(event);
1677             }
1678             else if (broughtWindowForward)
1679             {
1680                 [[window ancestorWineWindow] postBroughtForwardEvent];
1681                 if (![window isKeyWindow])
1682                     [self windowGotFocus:window];
1683             }
1684         }
1686         // Since mouse button events deliver absolute cursor position, the
1687         // accumulating delta from move events is invalidated.  Make sure
1688         // next mouse move event starts over from an absolute baseline.
1689         // Also, it's at least possible that the title bar widgets (e.g. close
1690         // button, etc.) could enter an internal event loop on a mouse down that
1691         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1692         // dragged events and, after that, any notion of the cursor position
1693         // computed from accumulating deltas would be wrong.
1694         forceNextMouseMoveAbsolute = TRUE;
1695     }
1697     - (void) handleScrollWheel:(NSEvent*)theEvent
1698     {
1699         WineWindow* window;
1701         if (mouseCaptureWindow)
1702             window = mouseCaptureWindow;
1703         else
1704             window = (WineWindow*)[theEvent window];
1706         if ([window isKindOfClass:[WineWindow class]])
1707         {
1708             CGEventRef cgevent = [theEvent CGEvent];
1709             CGPoint pt = CGEventGetLocation(cgevent);
1710             BOOL process;
1712             if (clippingCursor)
1713                 [self clipCursorLocation:&pt];
1715             if (mouseCaptureWindow)
1716                 process = TRUE;
1717             else
1718             {
1719                 // Only process the event if it was in the window's content area.
1720                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1721                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1722                 process = NSMouseInRect(nspoint, contentRect, NO);
1723             }
1725             if (process)
1726             {
1727                 macdrv_event* event;
1728                 double x, y;
1729                 BOOL continuous = FALSE;
1731                 event = macdrv_create_event(MOUSE_SCROLL, window);
1732                 event->mouse_scroll.x = pt.x;
1733                 event->mouse_scroll.y = pt.y;
1734                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1736                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1737                 {
1738                     continuous = TRUE;
1740                     /* Continuous scroll wheel events come from high-precision scrolling
1741                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1742                        For these, we can get more precise data from the CGEvent API. */
1743                     /* Axis 1 is vertical, axis 2 is horizontal. */
1744                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1745                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1746                 }
1747                 else
1748                 {
1749                     double pixelsPerLine = 10;
1750                     CGEventSourceRef source;
1752                     /* The non-continuous values are in units of "lines", not pixels. */
1753                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1754                     {
1755                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1756                         CFRelease(source);
1757                     }
1759                     x = pixelsPerLine * [theEvent deltaX];
1760                     y = pixelsPerLine * [theEvent deltaY];
1761                 }
1763                 /* Mac: negative is right or down, positive is left or up.
1764                    Win32: negative is left or down, positive is right or up.
1765                    So, negate the X scroll value to translate. */
1766                 x = -x;
1768                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1769                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1770                    6 times the pixel value. */
1771                 x *= 6;
1772                 y *= 6;
1774                 if (use_precise_scrolling)
1775                 {
1776                     event->mouse_scroll.x_scroll = x;
1777                     event->mouse_scroll.y_scroll = y;
1779                     if (!continuous)
1780                     {
1781                         /* For non-continuous "clicky" wheels, if there was any motion, make
1782                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1783                            speeds where the system's acceleration curve is actually reducing the
1784                            scroll distance, the user is sure to get some action out of each click.
1785                            For example, this is important for rotating though weapons in a
1786                            first-person shooter. */
1787                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1788                             event->mouse_scroll.x_scroll = 120;
1789                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1790                             event->mouse_scroll.x_scroll = -120;
1792                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1793                             event->mouse_scroll.y_scroll = 120;
1794                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1795                             event->mouse_scroll.y_scroll = -120;
1796                     }
1797                 }
1798                 else
1799                 {
1800                     /* If it's been a while since the last scroll event or if the scrolling has
1801                        reversed direction, reset the accumulated scroll value. */
1802                     if ([theEvent timestamp] - lastScrollTime > 1)
1803                         accumScrollX = accumScrollY = 0;
1804                     else
1805                     {
1806                         /* The accumulated scroll value is in the opposite direction/sign of the last
1807                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1808                            that direction.  We accumulate by adding in the scroll amount and then, if
1809                            it has the same sign as the scroll value, we subtract any whole or partial
1810                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1811                            scroll direction if the accumulated debt and the new scroll value have the
1812                            same sign. */
1813                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1814                             accumScrollX = 0;
1815                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1816                             accumScrollY = 0;
1817                     }
1818                     lastScrollTime = [theEvent timestamp];
1820                     accumScrollX += x;
1821                     accumScrollY += y;
1823                     if (accumScrollX > 0 && x > 0)
1824                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1825                     if (accumScrollX < 0 && x < 0)
1826                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1827                     if (accumScrollY > 0 && y > 0)
1828                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1829                     if (accumScrollY < 0 && y < 0)
1830                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1832                     accumScrollX -= event->mouse_scroll.x_scroll;
1833                     accumScrollY -= event->mouse_scroll.y_scroll;
1834                 }
1836                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1837                     [window.queue postEvent:event];
1839                 macdrv_release_event(event);
1841                 // Since scroll wheel events deliver absolute cursor position, the
1842                 // accumulating delta from move events is invalidated.  Make sure next
1843                 // mouse move event starts over from an absolute baseline.
1844                 forceNextMouseMoveAbsolute = TRUE;
1845             }
1846         }
1847     }
1849     // Returns TRUE if the event was handled and caller should do nothing more
1850     // with it.  Returns FALSE if the caller should process it as normal and
1851     // then call -didSendEvent:.
1852     - (BOOL) handleEvent:(NSEvent*)anEvent
1853     {
1854         BOOL ret = FALSE;
1855         NSEventType type = [anEvent type];
1857         if (type == NSFlagsChanged)
1858             self.lastFlagsChanged = anEvent;
1859         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1860                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1861         {
1862             [self handleMouseMove:anEvent];
1863             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1864         }
1865         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1866                  type == NSRightMouseDown || type == NSRightMouseUp ||
1867                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1868         {
1869             [self handleMouseButton:anEvent];
1870             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1871         }
1872         else if (type == NSScrollWheel)
1873         {
1874             [self handleScrollWheel:anEvent];
1875             ret = mouseCaptureWindow != nil;
1876         }
1877         else if (type == NSKeyUp)
1878         {
1879             uint16_t keyCode = [anEvent keyCode];
1880             if ([self isKeyPressed:keyCode])
1881             {
1882                 WineWindow* window = (WineWindow*)[anEvent window];
1883                 [self noteKey:keyCode pressed:FALSE];
1884                 if ([window isKindOfClass:[WineWindow class]])
1885                     [window postKeyEvent:anEvent];
1886             }
1887         }
1888         else if (type == NSAppKitDefined)
1889         {
1890             short subtype = [anEvent subtype];
1892             // These subtypes are not documented but they appear to mean
1893             // "a window is being dragged" and "a window is no longer being
1894             // dragged", respectively.
1895             if (subtype == 20 || subtype == 21)
1896             {
1897                 WineWindow* window = (WineWindow*)[anEvent window];
1898                 if ([window isKindOfClass:[WineWindow class]])
1899                 {
1900                     macdrv_event* event;
1901                     int eventType;
1903                     if (subtype == 20)
1904                     {
1905                         [windowsBeingDragged addObject:window];
1906                         eventType = WINDOW_DRAG_BEGIN;
1907                     }
1908                     else
1909                     {
1910                         [windowsBeingDragged removeObject:window];
1911                         eventType = WINDOW_DRAG_END;
1912                     }
1913                     [self updateCursorClippingState];
1915                     event = macdrv_create_event(eventType, window);
1916                     [window.queue postEvent:event];
1917                     macdrv_release_event(event);
1918                 }
1919             }
1920         }
1922         return ret;
1923     }
1925     - (void) didSendEvent:(NSEvent*)anEvent
1926     {
1927         NSEventType type = [anEvent type];
1929         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1930         {
1931             NSUInteger modifiers = [anEvent modifierFlags];
1932             if ((modifiers & NSCommandKeyMask) &&
1933                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1934             {
1935                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1936                 // by the system to switch applications.  If we're seeing it, it's
1937                 // presumably because we've captured the displays, preventing
1938                 // normal application switching.  Do it manually.
1939                 [self handleCommandTab];
1940             }
1941         }
1942     }
1944     - (void) setupObservations
1945     {
1946         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1947         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1948         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1950         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1951                         object:nil
1952                          queue:nil
1953                     usingBlock:^(NSNotification *note){
1954             NSWindow* window = [note object];
1955             [keyWindows removeObjectIdenticalTo:window];
1956             [keyWindows insertObject:window atIndex:0];
1957         }];
1959         [nc addObserverForName:NSWindowWillCloseNotification
1960                         object:nil
1961                          queue:[NSOperationQueue mainQueue]
1962                     usingBlock:^(NSNotification *note){
1963             NSWindow* window = [note object];
1964             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1965                 return;
1966             [keyWindows removeObjectIdenticalTo:window];
1967             if (window == lastTargetWindow)
1968                 lastTargetWindow = nil;
1969             if (window == self.mouseCaptureWindow)
1970                 self.mouseCaptureWindow = nil;
1971             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1972             {
1973                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1974                     [self updateFullscreenWindows];
1975                 });
1976             }
1977             [windowsBeingDragged removeObject:window];
1978             [self updateCursorClippingState];
1979         }];
1981         [nc addObserver:self
1982                selector:@selector(keyboardSelectionDidChange)
1983                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1984                  object:nil];
1986         /* The above notification isn't sent unless the NSTextInputContext
1987            class has initialized itself.  Poke it. */
1988         [NSTextInputContext self];
1990         [wsnc addObserver:self
1991                  selector:@selector(activeSpaceDidChange)
1992                      name:NSWorkspaceActiveSpaceDidChangeNotification
1993                    object:nil];
1995         [nc addObserver:self
1996                selector:@selector(releaseMouseCapture)
1997                    name:NSMenuDidBeginTrackingNotification
1998                  object:nil];
2000         [dnc        addObserver:self
2001                        selector:@selector(releaseMouseCapture)
2002                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2003                          object:nil
2004              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2006         [dnc addObserver:self
2007                 selector:@selector(enabledKeyboardInputSourcesChanged)
2008                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2009                   object:nil];
2010     }
2012     - (BOOL) inputSourceIsInputMethod
2013     {
2014         if (!inputSourceIsInputMethodValid)
2015         {
2016             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2017             if (inputSource)
2018             {
2019                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2020                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2021                 CFRelease(inputSource);
2022             }
2023             else
2024                 inputSourceIsInputMethod = FALSE;
2025             inputSourceIsInputMethodValid = TRUE;
2026         }
2028         return inputSourceIsInputMethod;
2029     }
2031     - (void) releaseMouseCapture
2032     {
2033         // This might be invoked on a background thread by the distributed
2034         // notification center.  Shunt it to the main thread.
2035         if (![NSThread isMainThread])
2036         {
2037             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2038             return;
2039         }
2041         if (mouseCaptureWindow)
2042         {
2043             macdrv_event* event;
2045             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2046             [mouseCaptureWindow.queue postEvent:event];
2047             macdrv_release_event(event);
2048         }
2049     }
2051     - (void) unminimizeWindowIfNoneVisible
2052     {
2053         if (![self frontWineWindow])
2054         {
2055             for (WineWindow* window in [NSApp windows])
2056             {
2057                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2058                 {
2059                     [window deminiaturize:self];
2060                     break;
2061                 }
2062             }
2063         }
2064     }
2067     /*
2068      * ---------- NSApplicationDelegate methods ----------
2069      */
2070     - (void)applicationDidBecomeActive:(NSNotification *)notification
2071     {
2072         NSNumber* displayID;
2073         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2075         latentDisplayModes = [[NSMutableDictionary alloc] init];
2076         for (displayID in modesToRealize)
2077         {
2078             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2079             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2080         }
2082         [self updateCursorClippingState];
2084         [self updateFullscreenWindows];
2085         [self adjustWindowLevels:YES];
2087         if (beenActive)
2088             [self unminimizeWindowIfNoneVisible];
2089         beenActive = TRUE;
2091         // If a Wine process terminates abruptly while it has the display captured
2092         // and switched to a different resolution, Mac OS X will uncapture the
2093         // displays and switch their resolutions back.  However, the other Wine
2094         // processes won't have their notion of the desktop rect changed back.
2095         // This can lead them to refuse to draw or acknowledge clicks in certain
2096         // portions of their windows.
2097         //
2098         // To solve this, we synthesize a displays-changed event whenever we're
2099         // activated.  This will provoke a re-synchronization of Wine's notion of
2100         // the desktop rect with the actual state.
2101         [self sendDisplaysChanged:TRUE];
2103         // The cursor probably moved while we were inactive.  Accumulated mouse
2104         // movement deltas are invalidated.  Make sure the next mouse move event
2105         // starts over from an absolute baseline.
2106         forceNextMouseMoveAbsolute = TRUE;
2107     }
2109     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2110     {
2111         primaryScreenHeightValid = FALSE;
2112         [self sendDisplaysChanged:FALSE];
2113         [self adjustWindowLevels];
2115         // When the display configuration changes, the cursor position may jump.
2116         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2117         // mouse move event starts over from an absolute baseline.
2118         forceNextMouseMoveAbsolute = TRUE;
2119     }
2121     - (void)applicationDidResignActive:(NSNotification *)notification
2122     {
2123         macdrv_event* event;
2124         WineEventQueue* queue;
2126         [self updateCursorClippingState];
2128         [self invalidateGotFocusEvents];
2130         event = macdrv_create_event(APP_DEACTIVATED, nil);
2132         [eventQueuesLock lock];
2133         for (queue in eventQueues)
2134             [queue postEvent:event];
2135         [eventQueuesLock unlock];
2137         macdrv_release_event(event);
2139         [self releaseMouseCapture];
2140     }
2142     - (void) applicationDidUnhide:(NSNotification*)aNotification
2143     {
2144         [self adjustWindowLevels];
2145     }
2147     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2148     {
2149         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2150         // don't count as "visible windows" for this purpose.
2151         [self unminimizeWindowIfNoneVisible];
2152         return YES;
2153     }
2155     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2156     {
2157         NSApplicationTerminateReply ret = NSTerminateNow;
2158         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2159         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2160         macdrv_event* event;
2161         WineEventQueue* queue;
2163         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2164         event->deliver = 1;
2165         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2166         {
2167             case kAELogOut:
2168             case kAEReallyLogOut:
2169                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2170                 break;
2171             case kAEShowRestartDialog:
2172                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2173                 break;
2174             case kAEShowShutdownDialog:
2175                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2176                 break;
2177             default:
2178                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2179                 break;
2180         }
2182         [eventQueuesLock lock];
2184         if ([eventQueues count])
2185         {
2186             for (queue in eventQueues)
2187                 [queue postEvent:event];
2188             ret = NSTerminateLater;
2189         }
2191         [eventQueuesLock unlock];
2193         macdrv_release_event(event);
2195         return ret;
2196     }
2198     - (void)applicationWillResignActive:(NSNotification *)notification
2199     {
2200         [self adjustWindowLevels:NO];
2201     }
2203 /***********************************************************************
2204  *              PerformRequest
2206  * Run-loop-source perform callback.  Pull request blocks from the
2207  * array of queued requests and invoke them.
2208  */
2209 static void PerformRequest(void *info)
2211     WineApplicationController* controller = [WineApplicationController sharedController];
2213     for (;;)
2214     {
2215         __block dispatch_block_t block;
2217         dispatch_sync(controller->requestsManipQueue, ^{
2218             if ([controller->requests count])
2219             {
2220                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2221                 [controller->requests removeObjectAtIndex:0];
2222             }
2223             else
2224                 block = nil;
2225         });
2227         if (!block)
2228             break;
2230         block();
2231         [block release];
2232     }
2235 /***********************************************************************
2236  *              OnMainThreadAsync
2238  * Run a block on the main thread asynchronously.
2239  */
2240 void OnMainThreadAsync(dispatch_block_t block)
2242     WineApplicationController* controller = [WineApplicationController sharedController];
2244     block = [block copy];
2245     dispatch_sync(controller->requestsManipQueue, ^{
2246         [controller->requests addObject:block];
2247     });
2248     [block release];
2249     CFRunLoopSourceSignal(controller->requestSource);
2250     CFRunLoopWakeUp(CFRunLoopGetMain());
2253 @end
2255 /***********************************************************************
2256  *              LogError
2257  */
2258 void LogError(const char* func, NSString* format, ...)
2260     va_list args;
2261     va_start(args, format);
2262     LogErrorv(func, format, args);
2263     va_end(args);
2266 /***********************************************************************
2267  *              LogErrorv
2268  */
2269 void LogErrorv(const char* func, NSString* format, va_list args)
2271     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2273     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2274     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2275     [message release];
2277     [pool release];
2280 /***********************************************************************
2281  *              macdrv_window_rejected_focus
2283  * Pass focus to the next window that hasn't already rejected this same
2284  * WINDOW_GOT_FOCUS event.
2285  */
2286 void macdrv_window_rejected_focus(const macdrv_event *event)
2288     OnMainThread(^{
2289         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2290     });
2293 /***********************************************************************
2294  *              macdrv_get_input_source_info
2296  * Returns the keyboard layout uchr data, keyboard type and input source.
2297  */
2298 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2300     OnMainThread(^{
2301         TISInputSourceRef inputSourceLayout;
2303         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2304         if (inputSourceLayout)
2305         {
2306             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2307                                 kTISPropertyUnicodeKeyLayoutData);
2308             *uchr = CFDataCreateCopy(NULL, data);
2309             CFRelease(inputSourceLayout);
2311             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2312             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2313             *input_source = TISCopyCurrentKeyboardInputSource();
2314         }
2315     });
2318 /***********************************************************************
2319  *              macdrv_beep
2321  * Play the beep sound configured by the user in System Preferences.
2322  */
2323 void macdrv_beep(void)
2325     OnMainThreadAsync(^{
2326         NSBeep();
2327     });
2330 /***********************************************************************
2331  *              macdrv_set_display_mode
2332  */
2333 int macdrv_set_display_mode(const struct macdrv_display* display,
2334                             CGDisplayModeRef display_mode)
2336     __block int ret;
2338     OnMainThread(^{
2339         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2340     });
2342     return ret;
2345 /***********************************************************************
2346  *              macdrv_set_cursor
2348  * Set the cursor.
2350  * If name is non-NULL, it is a selector for a class method on NSCursor
2351  * identifying the cursor to set.  In that case, frames is ignored.  If
2352  * name is NULL, then frames is used.
2354  * frames is an array of dictionaries.  Each dictionary is a frame of
2355  * an animated cursor.  Under the key "image" is a CGImage for the
2356  * frame.  Under the key "duration" is a CFNumber time interval, in
2357  * seconds, for how long that frame is presented before proceeding to
2358  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2359  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2360  * This is the hot spot, measured in pixels down and to the right of the
2361  * top-left corner of the image.
2363  * If the array has exactly 1 element, the cursor is static, not
2364  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2365  */
2366 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2368     SEL sel;
2370     sel = NSSelectorFromString((NSString*)name);
2371     if (sel)
2372     {
2373         OnMainThreadAsync(^{
2374             WineApplicationController* controller = [WineApplicationController sharedController];
2375             [controller setCursorWithFrames:nil];
2376             controller.cursor = [NSCursor performSelector:sel];
2377             [controller unhideCursor];
2378         });
2379     }
2380     else
2381     {
2382         NSArray* nsframes = (NSArray*)frames;
2383         if ([nsframes count])
2384         {
2385             OnMainThreadAsync(^{
2386                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2387             });
2388         }
2389         else
2390         {
2391             OnMainThreadAsync(^{
2392                 WineApplicationController* controller = [WineApplicationController sharedController];
2393                 [controller setCursorWithFrames:nil];
2394                 [controller hideCursor];
2395             });
2396         }
2397     }
2400 /***********************************************************************
2401  *              macdrv_get_cursor_position
2403  * Obtains the current cursor position.  Returns zero on failure,
2404  * non-zero on success.
2405  */
2406 int macdrv_get_cursor_position(CGPoint *pos)
2408     OnMainThread(^{
2409         NSPoint location = [NSEvent mouseLocation];
2410         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2411         *pos = NSPointToCGPoint(location);
2412     });
2414     return TRUE;
2417 /***********************************************************************
2418  *              macdrv_set_cursor_position
2420  * Sets the cursor position without generating events.  Returns zero on
2421  * failure, non-zero on success.
2422  */
2423 int macdrv_set_cursor_position(CGPoint pos)
2425     __block int ret;
2427     OnMainThread(^{
2428         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2429     });
2431     return ret;
2434 /***********************************************************************
2435  *              macdrv_clip_cursor
2437  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2438  * to or larger than the whole desktop region, the cursor is unclipped.
2439  * Returns zero on failure, non-zero on success.
2440  */
2441 int macdrv_clip_cursor(CGRect rect)
2443     __block int ret;
2445     OnMainThread(^{
2446         WineApplicationController* controller = [WineApplicationController sharedController];
2447         BOOL clipping = FALSE;
2449         if (!CGRectIsInfinite(rect))
2450         {
2451             NSRect nsrect = NSRectFromCGRect(rect);
2452             NSScreen* screen;
2454             /* Convert the rectangle from top-down coords to bottom-up. */
2455             [controller flipRect:&nsrect];
2457             clipping = FALSE;
2458             for (screen in [NSScreen screens])
2459             {
2460                 if (!NSContainsRect(nsrect, [screen frame]))
2461                 {
2462                     clipping = TRUE;
2463                     break;
2464                 }
2465             }
2466         }
2468         if (clipping)
2469             ret = [controller startClippingCursor:rect];
2470         else
2471             ret = [controller stopClippingCursor];
2472     });
2474     return ret;
2477 /***********************************************************************
2478  *              macdrv_set_application_icon
2480  * Set the application icon.  The images array contains CGImages.  If
2481  * there are more than one, then they represent different sizes or
2482  * color depths from the icon resource.  If images is NULL or empty,
2483  * restores the default application image.
2484  */
2485 void macdrv_set_application_icon(CFArrayRef images)
2487     NSArray* imageArray = (NSArray*)images;
2489     OnMainThreadAsync(^{
2490         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2491     });
2494 /***********************************************************************
2495  *              macdrv_quit_reply
2496  */
2497 void macdrv_quit_reply(int reply)
2499     OnMainThread(^{
2500         [NSApp replyToApplicationShouldTerminate:reply];
2501     });
2504 /***********************************************************************
2505  *              macdrv_using_input_method
2506  */
2507 int macdrv_using_input_method(void)
2509     __block BOOL ret;
2511     OnMainThread(^{
2512         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2513     });
2515     return ret;
2518 /***********************************************************************
2519  *              macdrv_set_mouse_capture_window
2520  */
2521 void macdrv_set_mouse_capture_window(macdrv_window window)
2523     WineWindow* w = (WineWindow*)window;
2525     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2527     OnMainThread(^{
2528         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2529     });
2532 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2533 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2534 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2536 /***********************************************************************
2537  *              macdrv_create_input_source_list
2538  */
2539 CFArrayRef macdrv_create_input_source_list(void)
2541     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2543     OnMainThread(^{
2544         CFArrayRef input_list;
2545         CFDictionaryRef filter_dict;
2546         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2547         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2548         int i;
2550         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2551                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2552         input_list = TISCreateInputSourceList(filter_dict, false);
2554         for (i = 0; i < CFArrayGetCount(input_list); i++)
2555         {
2556             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2557             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2558             CFDictionaryRef entry;
2559             const void *input_keys[3] = { macdrv_input_source_input_key,
2560                                           macdrv_input_source_type_key,
2561                                           macdrv_input_source_lang_key };
2562             const void *input_values[3];
2564             input_values[0] = input;
2565             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2566             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2568             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2569                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2571             CFArrayAppendValue(ret, entry);
2572             CFRelease(entry);
2573         }
2574         CFRelease(input_list);
2575         CFRelease(filter_dict);
2576     });
2578     return ret;
2581 int macdrv_select_input_source(TISInputSourceRef input_source)
2583     __block int ret = FALSE;
2585     OnMainThread(^{
2586         ret = (TISSelectInputSource(input_source) == noErr);
2587     });
2589     return ret;