2 * MACDRV Cocoa application class
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
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.
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.
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
21 #import <Carbon/Carbon.h>
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
35 @implementation WineApplication
37 @synthesize wineController;
39 - (void) sendEvent:(NSEvent*)anEvent
41 if (![wineController handleEvent:anEvent])
43 [super sendEvent:anEvent];
44 [wineController didSendEvent:anEvent];
48 - (void) setWineController:(WineApplicationController*)newController
50 wineController = newController;
51 [self setDelegate:wineController];
57 @interface WarpRecord : NSObject
59 CGEventTimestamp timeBefore, timeAfter;
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
71 @implementation WarpRecord
73 @synthesize timeBefore, timeAfter, from, to;
78 @interface WineApplicationController ()
80 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
81 @property (copy, nonatomic) NSArray* cursorFrames;
82 @property (retain, nonatomic) NSTimer* cursorTimer;
83 @property (retain, nonatomic) NSImage* applicationIcon;
84 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
86 - (void) setupObservations;
87 - (void) applicationDidBecomeActive:(NSNotification *)notification;
89 static void PerformRequest(void *info);
94 @implementation WineApplicationController
96 @synthesize keyboardType, lastFlagsChanged;
97 @synthesize orderedWineWindows, applicationIcon;
98 @synthesize cursorFrames, cursorTimer;
102 if (self == [WineApplicationController class])
104 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
105 @"", @"NSQuotedKeystrokeBinding",
106 @"", @"NSRepeatCountBinding",
107 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
109 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
113 + (WineApplicationController*) sharedController
115 static WineApplicationController* sharedController;
116 static dispatch_once_t once;
118 dispatch_once(&once, ^{
119 sharedController = [[self alloc] init];
122 return sharedController;
130 CFRunLoopSourceContext context = { 0 };
131 context.perform = PerformRequest;
132 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
138 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
139 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
141 requests = [[NSMutableArray alloc] init];
142 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
144 eventQueues = [[NSMutableArray alloc] init];
145 eventQueuesLock = [[NSLock alloc] init];
147 keyWindows = [[NSMutableArray alloc] init];
148 orderedWineWindows = [[NSMutableArray alloc] init];
150 originalDisplayModes = [[NSMutableDictionary alloc] init];
152 warpRecords = [[NSMutableArray alloc] init];
154 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
155 !keyWindows || !orderedWineWindows || !originalDisplayModes || !warpRecords)
161 [self setupObservations];
163 keyboardType = LMGetKbdType();
165 if ([NSApp isActive])
166 [self applicationDidBecomeActive:nil];
173 [screenFrameCGRects release];
174 [applicationIcon release];
175 [warpRecords release];
176 [cursorTimer release];
177 [cursorFrames release];
178 [originalDisplayModes release];
179 [orderedWineWindows release];
180 [keyWindows release];
181 [eventQueues release];
182 [eventQueuesLock release];
183 if (requestsManipQueue) dispatch_release(requestsManipQueue);
187 CFRunLoopSourceInvalidate(requestSource);
188 CFRelease(requestSource);
193 - (void) transformProcessToForeground
195 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
199 NSString* bundleName;
203 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
204 [NSApp activateIgnoringOtherApps:YES];
206 mainMenu = [[[NSMenu alloc] init] autorelease];
208 submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
209 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
210 if ([bundleName length])
211 title = [NSString stringWithFormat:@"Quit %@", bundleName];
214 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
215 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
216 item = [[[NSMenuItem alloc] init] autorelease];
217 [item setTitle:@"Wine"];
218 [item setSubmenu:submenu];
219 [mainMenu addItem:item];
221 submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
222 [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
223 [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
224 [submenu addItem:[NSMenuItem separatorItem]];
225 [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
226 item = [[[NSMenuItem alloc] init] autorelease];
227 [item setTitle:@"Window"];
228 [item setSubmenu:submenu];
229 [mainMenu addItem:item];
231 [NSApp setMainMenu:mainMenu];
232 [NSApp setWindowsMenu:submenu];
234 [NSApp setApplicationIconImage:self.applicationIcon];
238 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
240 PerformRequest(NULL);
246 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
247 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
249 inMode:NSDefaultRunLoopMode
252 [NSApp sendEvent:event];
256 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
257 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
262 - (BOOL) registerEventQueue:(WineEventQueue*)queue
264 [eventQueuesLock lock];
265 [eventQueues addObject:queue];
266 [eventQueuesLock unlock];
270 - (void) unregisterEventQueue:(WineEventQueue*)queue
272 [eventQueuesLock lock];
273 [eventQueues removeObjectIdenticalTo:queue];
274 [eventQueuesLock unlock];
277 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
279 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
282 - (double) ticksForEventTime:(NSTimeInterval)eventTime
284 return (eventTime + eventTimeAdjustment) * 1000;
287 /* Invalidate old focus offers across all queues. */
288 - (void) invalidateGotFocusEvents
290 WineEventQueue* queue;
294 [eventQueuesLock lock];
295 for (queue in eventQueues)
297 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
300 [eventQueuesLock unlock];
303 - (void) windowGotFocus:(WineWindow*)window
307 [self invalidateGotFocusEvents];
309 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
310 event->window_got_focus.serial = windowFocusSerial;
312 event->window_got_focus.tried_windows = [triedWindows retain];
314 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
315 [window.queue postEvent:event];
316 macdrv_release_event(event);
319 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
321 if (event->window_got_focus.serial == windowFocusSerial)
323 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
324 [triedWindows addObject:(WineWindow*)event->window];
325 for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWineWindows]])
327 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
329 [window makeKeyWindow];
337 - (void) keyboardSelectionDidChange
339 TISInputSourceRef inputSource;
341 inputSourceIsInputMethodValid = FALSE;
343 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
347 uchr = TISGetInputSourceProperty(inputSource,
348 kTISPropertyUnicodeKeyLayoutData);
352 WineEventQueue* queue;
354 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
355 event->keyboard_changed.keyboard_type = self.keyboardType;
356 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
357 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
359 if (event->keyboard_changed.uchr)
361 [eventQueuesLock lock];
363 for (queue in eventQueues)
364 [queue postEvent:event];
366 [eventQueuesLock unlock];
369 macdrv_release_event(event);
372 CFRelease(inputSource);
376 - (CGFloat) primaryScreenHeight
378 if (!primaryScreenHeightValid)
380 NSArray* screens = [NSScreen screens];
381 NSUInteger count = [screens count];
388 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
389 primaryScreenHeightValid = TRUE;
391 size = count * sizeof(CGRect);
392 if (!screenFrameCGRects)
393 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
395 [screenFrameCGRects setLength:size];
397 rect = [screenFrameCGRects mutableBytes];
398 for (screen in screens)
400 CGRect temp = NSRectToCGRect([screen frame]);
401 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
406 return 1280; /* arbitrary value */
409 return primaryScreenHeight;
412 - (NSPoint) flippedMouseLocation:(NSPoint)point
414 /* This relies on the fact that Cocoa's mouse location points are
415 actually off by one (precisely because they were flipped from
416 Quartz screen coordinates using this same technique). */
417 point.y = [self primaryScreenHeight] - point.y;
421 - (void) flipRect:(NSRect*)rect
423 // We don't use -primaryScreenHeight here so there's no chance of having
424 // out-of-date cached info. This method is called infrequently enough
425 // that getting the screen height each time is not prohibitively expensive.
426 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
429 - (void) wineWindow:(WineWindow*)window
430 ordered:(NSWindowOrderingMode)order
431 relativeTo:(WineWindow*)otherWindow
439 [orderedWineWindows removeObjectIdenticalTo:window];
442 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
443 if (index == NSNotFound)
449 for (otherWindow in orderedWineWindows)
451 if ([otherWindow levelWhenActive] <= [window levelWhenActive])
456 [orderedWineWindows insertObject:window atIndex:index];
461 [orderedWineWindows removeObjectIdenticalTo:window];
464 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
465 if (index == NSNotFound)
466 index = [orderedWineWindows count];
471 for (otherWindow in orderedWineWindows)
473 if ([otherWindow levelWhenActive] < [window levelWhenActive])
478 [orderedWineWindows insertObject:window atIndex:index];
487 - (void) sendDisplaysChanged:(BOOL)activating
490 WineEventQueue* queue;
492 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
493 event->displays_changed.activating = activating;
495 [eventQueuesLock lock];
497 // If we're activating, then we just need one of our threads to get the
498 // event, so it can send it directly to the desktop window. Otherwise,
499 // we need all of the threads to get it because we don't know which owns
500 // the desktop window and only that one will do anything with it.
501 if (activating) event->deliver = 1;
503 for (queue in eventQueues)
504 [queue postEvent:event];
505 [eventQueuesLock unlock];
507 macdrv_release_event(event);
510 // We can compare two modes directly using CFEqual, but that may require that
511 // they are identical to a level that we don't need. In particular, when the
512 // OS switches between the integrated and discrete GPUs, the set of display
513 // modes can change in subtle ways. We're interested in whether two modes
514 // match in their most salient features, even if they aren't identical.
515 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
517 NSString *encoding1, *encoding2;
518 uint32_t ioflags1, ioflags2, different;
519 double refresh1, refresh2;
521 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
522 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
524 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
525 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
526 if (![encoding1 isEqualToString:encoding2]) return FALSE;
528 ioflags1 = CGDisplayModeGetIOFlags(mode1);
529 ioflags2 = CGDisplayModeGetIOFlags(mode2);
530 different = ioflags1 ^ ioflags2;
531 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
532 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
535 refresh1 = CGDisplayModeGetRefreshRate(mode1);
536 if (refresh1 == 0) refresh1 = 60;
537 refresh2 = CGDisplayModeGetRefreshRate(mode2);
538 if (refresh2 == 0) refresh2 = 60;
539 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
544 - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
546 CGDisplayModeRef ret = NULL;
547 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
548 for (id candidateModeObject in modes)
550 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
551 if ([self mode:candidateMode matchesMode:mode])
560 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
563 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
564 CGDisplayModeRef currentMode, originalMode;
566 currentMode = CGDisplayCopyDisplayMode(displayID);
567 if (!currentMode) // Invalid display ID
570 if ([self mode:mode matchesMode:currentMode]) // Already there!
572 CGDisplayModeRelease(currentMode);
576 mode = [self modeMatchingMode:mode forDisplay:displayID];
579 CGDisplayModeRelease(currentMode);
583 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
585 originalMode = currentMode;
587 if ([self mode:mode matchesMode:originalMode])
589 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
591 CGRestorePermanentDisplayConfiguration();
592 CGReleaseAllDisplays();
593 [originalDisplayModes removeAllObjects];
596 else // ... otherwise, try to restore just the one display
598 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
600 [originalDisplayModes removeObjectForKey:displayIDKey];
607 if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
609 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
611 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
614 else if (![originalDisplayModes count])
616 CGRestorePermanentDisplayConfiguration();
617 CGReleaseAllDisplays();
622 CGDisplayModeRelease(currentMode);
626 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
627 [(WineWindow*)obj adjustWindowLevel];
634 - (BOOL) areDisplaysCaptured
636 return ([originalDisplayModes count] > 0);
648 - (void) unhideCursor
653 cursorHidden = FALSE;
659 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
660 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
661 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
662 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
666 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
667 hotSpot = CGPointZero;
668 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
675 - (void) nextCursorFrame:(NSTimer*)theTimer
678 NSTimeInterval duration;
682 if (cursorFrame >= [cursorFrames count])
686 frame = [cursorFrames objectAtIndex:cursorFrame];
687 duration = [[frame objectForKey:@"duration"] doubleValue];
688 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
689 [cursorTimer setFireDate:date];
692 - (void) setCursorWithFrames:(NSArray*)frames
694 if (self.cursorFrames == frames)
697 self.cursorFrames = frames;
699 [cursorTimer invalidate];
700 self.cursorTimer = nil;
704 if ([frames count] > 1)
706 NSDictionary* frame = [frames objectAtIndex:0];
707 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
708 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
709 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
712 selector:@selector(nextCursorFrame:)
714 repeats:YES] autorelease];
715 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
722 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
724 NSImage* nsimage = nil;
728 NSSize bestSize = NSZeroSize;
731 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
733 for (image in images)
735 CGImageRef cgimage = (CGImageRef)image;
736 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
739 NSSize size = [imageRep size];
741 [nsimage addRepresentation:imageRep];
744 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
749 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
750 [nsimage setSize:bestSize];
755 self.applicationIcon = nsimage;
756 [NSApp setApplicationIconImage:nsimage];
759 - (void) handleCommandTab
761 if ([NSApp isActive])
763 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
764 NSRunningApplication* app;
765 NSRunningApplication* otherValidApp = nil;
767 if ([originalDisplayModes count])
769 CGRestorePermanentDisplayConfiguration();
770 CGReleaseAllDisplays();
771 [originalDisplayModes removeAllObjects];
774 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
776 if (![app isEqual:thisApp] && !app.terminated &&
777 app.activationPolicy == NSApplicationActivationPolicyRegular)
781 // There's another visible app. Just hide ourselves and let
782 // the system activate the other app.
792 // Didn't find a visible GUI app. Try the Finder or, if that's not
793 // running, the first hidden GUI app. If even that doesn't work, we
794 // just fail to switch and remain the active app.
795 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
796 if (!app) app = otherValidApp;
798 [app activateWithOptions:0];
803 * ---------- Cursor clipping methods ----------
805 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
806 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
807 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
808 * general case, we leverage that. We disassociate mouse movements from
809 * the cursor position and then move the cursor manually, keeping it within
810 * the clipping rectangle.
812 * Moving the cursor manually isn't enough. We need to modify the event
813 * stream so that the events have the new location, too. We need to do
814 * this at a point before the events enter Cocoa, so that Cocoa will assign
815 * the correct window to the event. So, we install a Quartz event tap to
818 * Also, there's a complication when we move the cursor. We use
819 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
820 * events, but the change of cursor position is incorporated into the
821 * deltas of the next mouse move event. When the mouse is disassociated
822 * from the cursor position, we need the deltas to only reflect actual
823 * device movement, not programmatic changes. So, the event tap cancels
824 * out the change caused by our calls to CGWarpMouseCursorPosition().
826 - (void) clipCursorLocation:(CGPoint*)location
828 if (location->x < CGRectGetMinX(cursorClipRect))
829 location->x = CGRectGetMinX(cursorClipRect);
830 if (location->y < CGRectGetMinY(cursorClipRect))
831 location->y = CGRectGetMinY(cursorClipRect);
832 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
833 location->x = CGRectGetMaxX(cursorClipRect) - 1;
834 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
835 location->y = CGRectGetMaxY(cursorClipRect) - 1;
838 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
843 oldLocation = *currentLocation;
845 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
847 if (!CGPointEqualToPoint(oldLocation, *newLocation))
849 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
852 warpRecord.from = oldLocation;
853 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
855 /* Actually move the cursor. */
856 err = CGWarpMouseCursorPosition(*newLocation);
857 if (err != kCGErrorSuccess)
860 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
861 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
863 if (!CGPointEqualToPoint(oldLocation, *newLocation))
865 warpRecord.to = *newLocation;
866 [warpRecords addObject:warpRecord];
873 - (BOOL) isMouseMoveEventType:(CGEventType)type
877 case kCGEventMouseMoved:
878 case kCGEventLeftMouseDragged:
879 case kCGEventRightMouseDragged:
880 case kCGEventOtherMouseDragged:
887 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
889 int warpsFinished = 0;
890 for (WarpRecord* warpRecord in warpRecords)
892 if (warpRecord.timeAfter < eventTime ||
893 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
899 return warpsFinished;
902 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
903 type:(CGEventType)type
904 event:(CGEventRef)event
906 CGEventTimestamp eventTime;
907 CGPoint eventLocation, cursorLocation;
909 if (type == kCGEventTapDisabledByUserInput)
911 if (type == kCGEventTapDisabledByTimeout)
913 CGEventTapEnable(cursorClippingEventTap, TRUE);
920 eventTime = CGEventGetTimestamp(event);
921 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
923 eventLocation = CGEventGetLocation(event);
925 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
927 if ([self isMouseMoveEventType:type])
929 double deltaX, deltaY;
930 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
933 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
934 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
936 for (i = 0; i < warpsFinished; i++)
938 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
939 deltaX -= warpRecord.to.x - warpRecord.from.x;
940 deltaY -= warpRecord.to.y - warpRecord.from.y;
941 [warpRecords removeObjectAtIndex:0];
946 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
947 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
950 synthesizedLocation.x += deltaX;
951 synthesizedLocation.y += deltaY;
954 // If the event is destined for another process, don't clip it. This may
955 // happen if the user activates Exposé or Mission Control. In that case,
956 // our app does not resign active status, so clipping is still in effect,
957 // but the cursor should not actually be clipped.
959 // In addition, the fact that mouse moves may have been delivered to a
960 // different process means we have to treat the next one we receive as
961 // absolute rather than relative.
962 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
963 [self clipCursorLocation:&synthesizedLocation];
965 lastSetCursorPositionTime = lastEventTapEventTime;
967 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
968 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
969 CGEventSetLocation(event, synthesizedLocation);
974 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
975 CGEventRef event, void *refcon)
977 WineApplicationController* controller = refcon;
978 return [controller eventTapWithProxy:proxy type:type event:event];
981 - (BOOL) installEventTap
983 ProcessSerialNumber psn;
985 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
986 CGEventMaskBit(kCGEventLeftMouseUp) |
987 CGEventMaskBit(kCGEventRightMouseDown) |
988 CGEventMaskBit(kCGEventRightMouseUp) |
989 CGEventMaskBit(kCGEventMouseMoved) |
990 CGEventMaskBit(kCGEventLeftMouseDragged) |
991 CGEventMaskBit(kCGEventRightMouseDragged) |
992 CGEventMaskBit(kCGEventOtherMouseDown) |
993 CGEventMaskBit(kCGEventOtherMouseUp) |
994 CGEventMaskBit(kCGEventOtherMouseDragged) |
995 CGEventMaskBit(kCGEventScrollWheel);
996 CFRunLoopSourceRef source;
998 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1000 if (cursorClippingEventTap)
1003 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1004 // framework with dlsym() because the Win32 function of the same name
1006 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1010 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1011 if (!pGetCurrentProcess)
1013 dlclose(appServices);
1017 err = pGetCurrentProcess(&psn);
1018 dlclose(appServices);
1022 // We create an annotated session event tap rather than a process-specific
1023 // event tap because we need to programmatically move the cursor even when
1024 // mouse moves are directed to other processes. We disable our tap when
1025 // other processes are active, but things like Exposé are handled by other
1026 // processes even when we remain active.
1027 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1028 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1029 if (!cursorClippingEventTap)
1032 CGEventTapEnable(cursorClippingEventTap, FALSE);
1034 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1037 CFRelease(cursorClippingEventTap);
1038 cursorClippingEventTap = NULL;
1042 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1047 - (BOOL) setCursorPosition:(CGPoint)pos
1053 [self clipCursorLocation:&pos];
1055 ret = [self warpCursorTo:&pos from:NULL];
1056 synthesizedLocation = pos;
1059 // We want to discard mouse-move events that have already been
1060 // through the event tap, because it's too late to account for
1061 // the setting of the cursor position with them. However, the
1062 // events that may be queued with times after that but before
1063 // the above warp can still be used. So, use the last event
1064 // tap event time so that -sendEvent: doesn't discard them.
1065 lastSetCursorPositionTime = lastEventTapEventTime;
1070 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1073 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1075 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1076 // the mouse from the cursor position for 0.25 seconds. This means
1077 // that mouse movement during that interval doesn't move the cursor
1078 // and events carry a constant location (the warped-to position)
1079 // even though they have delta values. This screws us up because
1080 // the accumulated deltas we send to Wine don't match any eventual
1081 // absolute position we send (like with a button press). We can
1082 // work around this by simply forcibly reassociating the mouse and
1084 CGAssociateMouseAndMouseCursorPosition(true);
1090 WineEventQueue* queue;
1092 // Discard all pending mouse move events.
1093 [eventQueuesLock lock];
1094 for (queue in eventQueues)
1096 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1097 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1099 [queue resetMouseEventPositions:pos];
1101 [eventQueuesLock unlock];
1107 - (void) activateCursorClipping
1111 CGEventTapEnable(cursorClippingEventTap, TRUE);
1112 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1116 - (void) deactivateCursorClipping
1120 CGEventTapEnable(cursorClippingEventTap, FALSE);
1121 [warpRecords removeAllObjects];
1122 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1126 - (BOOL) startClippingCursor:(CGRect)rect
1130 if (!cursorClippingEventTap && ![self installEventTap])
1133 err = CGAssociateMouseAndMouseCursorPosition(false);
1134 if (err != kCGErrorSuccess)
1137 clippingCursor = TRUE;
1138 cursorClipRect = rect;
1139 if ([NSApp isActive])
1140 [self activateCursorClipping];
1145 - (BOOL) stopClippingCursor
1147 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1148 if (err != kCGErrorSuccess)
1151 [self deactivateCursorClipping];
1152 clippingCursor = FALSE;
1157 - (void) handleMouseMove:(NSEvent*)anEvent
1159 WineWindow* targetWindow;
1160 BOOL drag = [anEvent type] != NSMouseMoved;
1162 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1163 event indicates its window is the main window, even if the cursor is
1164 over a different window. Find the actual WineWindow that is under the
1165 cursor and post the event as being for that window. */
1168 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1169 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1170 NSInteger windowUnderNumber;
1172 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1173 belowWindowWithWindowNumber:0];
1174 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1177 targetWindow = (WineWindow*)[anEvent window];
1179 if ([targetWindow isKindOfClass:[WineWindow class]])
1181 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1182 macdrv_event* event;
1185 // If we recently warped the cursor (other than in our cursor-clipping
1186 // event tap), discard mouse move events until we see an event which is
1187 // later than that time.
1188 if (lastSetCursorPositionTime)
1190 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1193 lastSetCursorPositionTime = 0;
1194 forceNextMouseMoveAbsolute = TRUE;
1197 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1200 forceNextMouseMoveAbsolute = FALSE;
1204 // Send absolute move events if the cursor is in the interior of
1205 // its range. Only send relative moves if the cursor is pinned to
1206 // the boundaries of where it can go. We compute the position
1207 // that's one additional point in the direction of movement. If
1208 // that is outside of the clipping rect or desktop region (the
1209 // union of the screen frames), then we figure the cursor would
1210 // have moved outside if it could but it was pinned.
1211 CGPoint computedPoint = point;
1212 CGFloat deltaX = [anEvent deltaX];
1213 CGFloat deltaY = [anEvent deltaY];
1217 else if (deltaX < -0.001)
1222 else if (deltaY < -0.001)
1225 // Assume cursor is pinned for now
1227 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1229 const CGRect* rects;
1230 NSUInteger count, i;
1232 // Caches screenFrameCGRects if necessary
1233 [self primaryScreenHeight];
1235 rects = [screenFrameCGRects bytes];
1236 count = [screenFrameCGRects length] / sizeof(rects[0]);
1238 for (i = 0; i < count; i++)
1240 if (CGRectContainsPoint(rects[i], computedPoint))
1252 [self clipCursorLocation:&point];
1254 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1255 event->mouse_moved.x = point.x;
1256 event->mouse_moved.y = point.y;
1258 mouseMoveDeltaX = 0;
1259 mouseMoveDeltaY = 0;
1263 /* Add event delta to accumulated delta error */
1264 /* deltaY is already flipped */
1265 mouseMoveDeltaX += [anEvent deltaX];
1266 mouseMoveDeltaY += [anEvent deltaY];
1268 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1269 event->mouse_moved.x = mouseMoveDeltaX;
1270 event->mouse_moved.y = mouseMoveDeltaY;
1272 /* Keep the remainder after integer truncation. */
1273 mouseMoveDeltaX -= event->mouse_moved.x;
1274 mouseMoveDeltaY -= event->mouse_moved.y;
1277 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1279 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1280 event->mouse_moved.drag = drag;
1282 [targetWindow.queue postEvent:event];
1285 macdrv_release_event(event);
1287 lastTargetWindow = targetWindow;
1289 else if (lastTargetWindow)
1291 [[NSCursor arrowCursor] set];
1292 [self unhideCursor];
1293 lastTargetWindow = nil;
1297 - (void) handleMouseButton:(NSEvent*)theEvent
1299 WineWindow* window = (WineWindow*)[theEvent window];
1301 if ([window isKindOfClass:[WineWindow class]])
1303 NSEventType type = [theEvent type];
1304 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1305 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1309 [self clipCursorLocation:&pt];
1313 // Test if the click was in the window's content area.
1314 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1315 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1316 process = NSPointInRect(nspoint, contentRect);
1317 if (process && [window styleMask] & NSResizableWindowMask)
1319 // Ignore clicks in the grow box (resize widget).
1320 HIPoint origin = { 0, 0 };
1321 HIThemeGrowBoxDrawInfo info = { 0 };
1325 info.kind = kHIThemeGrowBoxKindNormal;
1326 info.direction = kThemeGrowRight | kThemeGrowDown;
1327 if ([window styleMask] & NSUtilityWindowMask)
1328 info.size = kHIThemeGrowBoxSizeSmall;
1330 info.size = kHIThemeGrowBoxSizeNormal;
1332 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1333 if (status == noErr)
1335 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1336 NSMinY(contentRect),
1338 bounds.size.height);
1339 process = !NSPointInRect(nspoint, growBox);
1343 unmatchedMouseDowns |= NSEventMaskFromType(type);
1347 NSEventType downType = type - 1;
1348 NSUInteger downMask = NSEventMaskFromType(downType);
1349 process = (unmatchedMouseDowns & downMask) != 0;
1350 unmatchedMouseDowns &= ~downMask;
1355 macdrv_event* event;
1357 event = macdrv_create_event(MOUSE_BUTTON, window);
1358 event->mouse_button.button = [theEvent buttonNumber];
1359 event->mouse_button.pressed = pressed;
1360 event->mouse_button.x = pt.x;
1361 event->mouse_button.y = pt.y;
1362 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1364 [window.queue postEvent:event];
1366 macdrv_release_event(event);
1370 // Since mouse button events deliver absolute cursor position, the
1371 // accumulating delta from move events is invalidated. Make sure
1372 // next mouse move event starts over from an absolute baseline.
1373 // Also, it's at least possible that the title bar widgets (e.g. close
1374 // button, etc.) could enter an internal event loop on a mouse down that
1375 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1376 // dragged events and, after that, any notion of the cursor position
1377 // computed from accumulating deltas would be wrong.
1378 forceNextMouseMoveAbsolute = TRUE;
1381 - (void) handleScrollWheel:(NSEvent*)theEvent
1383 WineWindow* window = (WineWindow*)[theEvent window];
1385 if ([window isKindOfClass:[WineWindow class]])
1387 CGEventRef cgevent = [theEvent CGEvent];
1388 CGPoint pt = CGEventGetLocation(cgevent);
1393 [self clipCursorLocation:&pt];
1395 nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1396 contentRect = [window contentRectForFrameRect:[window frame]];
1398 // Only process the event if it was in the window's content area.
1399 if (NSPointInRect(nspoint, contentRect))
1401 macdrv_event* event;
1403 BOOL continuous = FALSE;
1405 event = macdrv_create_event(MOUSE_SCROLL, window);
1406 event->mouse_scroll.x = pt.x;
1407 event->mouse_scroll.y = pt.y;
1408 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1410 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1414 /* Continuous scroll wheel events come from high-precision scrolling
1415 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1416 For these, we can get more precise data from the CGEvent API. */
1417 /* Axis 1 is vertical, axis 2 is horizontal. */
1418 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1419 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1423 double pixelsPerLine = 10;
1424 CGEventSourceRef source;
1426 /* The non-continuous values are in units of "lines", not pixels. */
1427 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1429 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1433 x = pixelsPerLine * [theEvent deltaX];
1434 y = pixelsPerLine * [theEvent deltaY];
1437 /* Mac: negative is right or down, positive is left or up.
1438 Win32: negative is left or down, positive is right or up.
1439 So, negate the X scroll value to translate. */
1442 /* The x,y values so far are in pixels. Win32 expects to receive some
1443 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1444 6 times the pixel value. */
1445 event->mouse_scroll.x_scroll = 6 * x;
1446 event->mouse_scroll.y_scroll = 6 * y;
1450 /* For non-continuous "clicky" wheels, if there was any motion, make
1451 sure there was at least WHEEL_DELTA motion. This is so, at slow
1452 speeds where the system's acceleration curve is actually reducing the
1453 scroll distance, the user is sure to get some action out of each click.
1454 For example, this is important for rotating though weapons in a
1455 first-person shooter. */
1456 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1457 event->mouse_scroll.x_scroll = 120;
1458 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1459 event->mouse_scroll.x_scroll = -120;
1461 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1462 event->mouse_scroll.y_scroll = 120;
1463 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1464 event->mouse_scroll.y_scroll = -120;
1467 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1468 [window.queue postEvent:event];
1470 macdrv_release_event(event);
1472 // Since scroll wheel events deliver absolute cursor position, the
1473 // accumulating delta from move events is invalidated. Make sure next
1474 // mouse move event starts over from an absolute baseline.
1475 forceNextMouseMoveAbsolute = TRUE;
1480 // Returns TRUE if the event was handled and caller should do nothing more
1481 // with it. Returns FALSE if the caller should process it as normal and
1482 // then call -didSendEvent:.
1483 - (BOOL) handleEvent:(NSEvent*)anEvent
1485 if ([anEvent type] == NSFlagsChanged)
1486 self.lastFlagsChanged = anEvent;
1490 - (void) didSendEvent:(NSEvent*)anEvent
1492 NSEventType type = [anEvent type];
1494 if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1495 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1497 [self handleMouseMove:anEvent];
1499 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1500 type == NSRightMouseDown || type == NSRightMouseUp ||
1501 type == NSOtherMouseDown || type == NSOtherMouseUp)
1503 [self handleMouseButton:anEvent];
1505 else if (type == NSScrollWheel)
1507 [self handleScrollWheel:anEvent];
1509 else if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1511 NSUInteger modifiers = [anEvent modifierFlags];
1512 if ((modifiers & NSCommandKeyMask) &&
1513 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1515 // Command-Tab and Command-Shift-Tab would normally be intercepted
1516 // by the system to switch applications. If we're seeing it, it's
1517 // presumably because we've captured the displays, preventing
1518 // normal application switching. Do it manually.
1519 [self handleCommandTab];
1524 - (void) setupObservations
1526 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1528 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1531 usingBlock:^(NSNotification *note){
1532 NSWindow* window = [note object];
1533 [keyWindows removeObjectIdenticalTo:window];
1534 [keyWindows insertObject:window atIndex:0];
1537 [nc addObserverForName:NSWindowWillCloseNotification
1539 queue:[NSOperationQueue mainQueue]
1540 usingBlock:^(NSNotification *note){
1541 NSWindow* window = [note object];
1542 [keyWindows removeObjectIdenticalTo:window];
1543 [orderedWineWindows removeObjectIdenticalTo:window];
1544 if (window == lastTargetWindow)
1545 lastTargetWindow = nil;
1548 [nc addObserver:self
1549 selector:@selector(keyboardSelectionDidChange)
1550 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1553 /* The above notification isn't sent unless the NSTextInputContext
1554 class has initialized itself. Poke it. */
1555 [NSTextInputContext self];
1558 - (BOOL) inputSourceIsInputMethod
1560 if (!inputSourceIsInputMethodValid)
1562 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1565 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1566 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1567 CFRelease(inputSource);
1570 inputSourceIsInputMethod = FALSE;
1571 inputSourceIsInputMethodValid = TRUE;
1574 return inputSourceIsInputMethod;
1579 * ---------- NSApplicationDelegate methods ----------
1581 - (void)applicationDidBecomeActive:(NSNotification *)notification
1583 [self activateCursorClipping];
1585 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1586 WineWindow* window = obj;
1587 if ([window levelWhenActive] != [window level])
1588 [window setLevel:[window levelWhenActive]];
1591 // If a Wine process terminates abruptly while it has the display captured
1592 // and switched to a different resolution, Mac OS X will uncapture the
1593 // displays and switch their resolutions back. However, the other Wine
1594 // processes won't have their notion of the desktop rect changed back.
1595 // This can lead them to refuse to draw or acknowledge clicks in certain
1596 // portions of their windows.
1598 // To solve this, we synthesize a displays-changed event whenever we're
1599 // activated. This will provoke a re-synchronization of Wine's notion of
1600 // the desktop rect with the actual state.
1601 [self sendDisplaysChanged:TRUE];
1603 // The cursor probably moved while we were inactive. Accumulated mouse
1604 // movement deltas are invalidated. Make sure the next mouse move event
1605 // starts over from an absolute baseline.
1606 forceNextMouseMoveAbsolute = TRUE;
1609 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1611 primaryScreenHeightValid = FALSE;
1612 [self sendDisplaysChanged:FALSE];
1614 // When the display configuration changes, the cursor position may jump.
1615 // Accumulated mouse movement deltas are invalidated. Make sure the next
1616 // mouse move event starts over from an absolute baseline.
1617 forceNextMouseMoveAbsolute = TRUE;
1620 - (void)applicationDidResignActive:(NSNotification *)notification
1622 macdrv_event* event;
1623 WineEventQueue* queue;
1625 [self invalidateGotFocusEvents];
1627 event = macdrv_create_event(APP_DEACTIVATED, nil);
1629 [eventQueuesLock lock];
1630 for (queue in eventQueues)
1631 [queue postEvent:event];
1632 [eventQueuesLock unlock];
1634 macdrv_release_event(event);
1637 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1639 NSApplicationTerminateReply ret = NSTerminateNow;
1640 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1641 NSAppleEventDescriptor* desc = [m currentAppleEvent];
1642 macdrv_event* event;
1643 WineEventQueue* queue;
1645 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1647 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1650 case kAEReallyLogOut:
1651 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1653 case kAEShowRestartDialog:
1654 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1656 case kAEShowShutdownDialog:
1657 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1660 event->app_quit_requested.reason = QUIT_REASON_NONE;
1664 [eventQueuesLock lock];
1666 if ([eventQueues count])
1668 for (queue in eventQueues)
1669 [queue postEvent:event];
1670 ret = NSTerminateLater;
1673 [eventQueuesLock unlock];
1675 macdrv_release_event(event);
1680 - (void)applicationWillResignActive:(NSNotification *)notification
1682 [self deactivateCursorClipping];
1684 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1685 WineWindow* window = obj;
1686 NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1687 if ([window level] > level)
1688 [window setLevel:level];
1692 /***********************************************************************
1695 * Run-loop-source perform callback. Pull request blocks from the
1696 * array of queued requests and invoke them.
1698 static void PerformRequest(void *info)
1700 WineApplicationController* controller = [WineApplicationController sharedController];
1704 __block dispatch_block_t block;
1706 dispatch_sync(controller->requestsManipQueue, ^{
1707 if ([controller->requests count])
1709 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1710 [controller->requests removeObjectAtIndex:0];
1724 /***********************************************************************
1727 * Run a block on the main thread asynchronously.
1729 void OnMainThreadAsync(dispatch_block_t block)
1731 WineApplicationController* controller = [WineApplicationController sharedController];
1733 block = [block copy];
1734 dispatch_sync(controller->requestsManipQueue, ^{
1735 [controller->requests addObject:block];
1738 CFRunLoopSourceSignal(controller->requestSource);
1739 CFRunLoopWakeUp(CFRunLoopGetMain());
1744 /***********************************************************************
1747 void LogError(const char* func, NSString* format, ...)
1750 va_start(args, format);
1751 LogErrorv(func, format, args);
1755 /***********************************************************************
1758 void LogErrorv(const char* func, NSString* format, va_list args)
1760 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1762 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1763 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1769 /***********************************************************************
1770 * macdrv_window_rejected_focus
1772 * Pass focus to the next window that hasn't already rejected this same
1773 * WINDOW_GOT_FOCUS event.
1775 void macdrv_window_rejected_focus(const macdrv_event *event)
1778 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1782 /***********************************************************************
1783 * macdrv_get_keyboard_layout
1785 * Returns the keyboard layout uchr data.
1787 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1789 __block CFDataRef result = NULL;
1792 TISInputSourceRef inputSource;
1794 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1797 CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1798 kTISPropertyUnicodeKeyLayoutData);
1799 result = CFDataCreateCopy(NULL, uchr);
1800 CFRelease(inputSource);
1802 *keyboard_type = [WineApplicationController sharedController].keyboardType;
1803 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1810 /***********************************************************************
1813 * Play the beep sound configured by the user in System Preferences.
1815 void macdrv_beep(void)
1817 OnMainThreadAsync(^{
1822 /***********************************************************************
1823 * macdrv_set_display_mode
1825 int macdrv_set_display_mode(const struct macdrv_display* display,
1826 CGDisplayModeRef display_mode)
1831 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1837 /***********************************************************************
1842 * If name is non-NULL, it is a selector for a class method on NSCursor
1843 * identifying the cursor to set. In that case, frames is ignored. If
1844 * name is NULL, then frames is used.
1846 * frames is an array of dictionaries. Each dictionary is a frame of
1847 * an animated cursor. Under the key "image" is a CGImage for the
1848 * frame. Under the key "duration" is a CFNumber time interval, in
1849 * seconds, for how long that frame is presented before proceeding to
1850 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
1851 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1852 * This is the hot spot, measured in pixels down and to the right of the
1853 * top-left corner of the image.
1855 * If the array has exactly 1 element, the cursor is static, not
1856 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
1858 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1862 sel = NSSelectorFromString((NSString*)name);
1865 OnMainThreadAsync(^{
1866 WineApplicationController* controller = [WineApplicationController sharedController];
1867 NSCursor* cursor = [NSCursor performSelector:sel];
1868 [controller setCursorWithFrames:nil];
1870 [controller unhideCursor];
1875 NSArray* nsframes = (NSArray*)frames;
1876 if ([nsframes count])
1878 OnMainThreadAsync(^{
1879 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1884 OnMainThreadAsync(^{
1885 WineApplicationController* controller = [WineApplicationController sharedController];
1886 [controller setCursorWithFrames:nil];
1887 [controller hideCursor];
1893 /***********************************************************************
1894 * macdrv_get_cursor_position
1896 * Obtains the current cursor position. Returns zero on failure,
1897 * non-zero on success.
1899 int macdrv_get_cursor_position(CGPoint *pos)
1902 NSPoint location = [NSEvent mouseLocation];
1903 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1904 *pos = NSPointToCGPoint(location);
1910 /***********************************************************************
1911 * macdrv_set_cursor_position
1913 * Sets the cursor position without generating events. Returns zero on
1914 * failure, non-zero on success.
1916 int macdrv_set_cursor_position(CGPoint pos)
1921 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1927 /***********************************************************************
1928 * macdrv_clip_cursor
1930 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
1931 * to or larger than the whole desktop region, the cursor is unclipped.
1932 * Returns zero on failure, non-zero on success.
1934 int macdrv_clip_cursor(CGRect rect)
1939 WineApplicationController* controller = [WineApplicationController sharedController];
1940 BOOL clipping = FALSE;
1942 if (!CGRectIsInfinite(rect))
1944 NSRect nsrect = NSRectFromCGRect(rect);
1947 /* Convert the rectangle from top-down coords to bottom-up. */
1948 [controller flipRect:&nsrect];
1951 for (screen in [NSScreen screens])
1953 if (!NSContainsRect(nsrect, [screen frame]))
1962 ret = [controller startClippingCursor:rect];
1964 ret = [controller stopClippingCursor];
1970 /***********************************************************************
1971 * macdrv_set_application_icon
1973 * Set the application icon. The images array contains CGImages. If
1974 * there are more than one, then they represent different sizes or
1975 * color depths from the icon resource. If images is NULL or empty,
1976 * restores the default application image.
1978 void macdrv_set_application_icon(CFArrayRef images)
1980 NSArray* imageArray = (NSArray*)images;
1982 OnMainThreadAsync(^{
1983 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
1987 /***********************************************************************
1990 void macdrv_quit_reply(int reply)
1993 [NSApp replyToApplicationShouldTerminate:reply];
1997 /***********************************************************************
1998 * macdrv_using_input_method
2000 int macdrv_using_input_method(void)
2005 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];