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;
85 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
87 - (void) setupObservations;
88 - (void) applicationDidBecomeActive:(NSNotification *)notification;
90 static void PerformRequest(void *info);
95 @implementation WineApplicationController
97 @synthesize keyboardType, lastFlagsChanged;
98 @synthesize orderedWineWindows, applicationIcon;
99 @synthesize cursorFrames, cursorTimer;
100 @synthesize mouseCaptureWindow;
104 if (self == [WineApplicationController class])
106 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
107 @"", @"NSQuotedKeystrokeBinding",
108 @"", @"NSRepeatCountBinding",
109 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
111 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
115 + (WineApplicationController*) sharedController
117 static WineApplicationController* sharedController;
118 static dispatch_once_t once;
120 dispatch_once(&once, ^{
121 sharedController = [[self alloc] init];
124 return sharedController;
132 CFRunLoopSourceContext context = { 0 };
133 context.perform = PerformRequest;
134 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
140 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
141 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
143 requests = [[NSMutableArray alloc] init];
144 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
146 eventQueues = [[NSMutableArray alloc] init];
147 eventQueuesLock = [[NSLock alloc] init];
149 keyWindows = [[NSMutableArray alloc] init];
150 orderedWineWindows = [[NSMutableArray alloc] init];
152 originalDisplayModes = [[NSMutableDictionary alloc] init];
154 warpRecords = [[NSMutableArray alloc] init];
156 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
157 !keyWindows || !orderedWineWindows || !originalDisplayModes || !warpRecords)
163 [self setupObservations];
165 keyboardType = LMGetKbdType();
167 if ([NSApp isActive])
168 [self applicationDidBecomeActive:nil];
175 [screenFrameCGRects release];
176 [applicationIcon release];
177 [warpRecords release];
178 [cursorTimer release];
179 [cursorFrames release];
180 [originalDisplayModes release];
181 [orderedWineWindows release];
182 [keyWindows release];
183 [eventQueues release];
184 [eventQueuesLock release];
185 if (requestsManipQueue) dispatch_release(requestsManipQueue);
189 CFRunLoopSourceInvalidate(requestSource);
190 CFRelease(requestSource);
195 - (void) transformProcessToForeground
197 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
201 NSString* bundleName;
205 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
206 [NSApp activateIgnoringOtherApps:YES];
208 mainMenu = [[[NSMenu alloc] init] autorelease];
210 submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
211 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
212 if ([bundleName length])
213 title = [NSString stringWithFormat:@"Quit %@", bundleName];
216 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
217 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
218 item = [[[NSMenuItem alloc] init] autorelease];
219 [item setTitle:@"Wine"];
220 [item setSubmenu:submenu];
221 [mainMenu addItem:item];
223 submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
224 [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
225 [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
226 [submenu addItem:[NSMenuItem separatorItem]];
227 [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
228 item = [[[NSMenuItem alloc] init] autorelease];
229 [item setTitle:@"Window"];
230 [item setSubmenu:submenu];
231 [mainMenu addItem:item];
233 [NSApp setMainMenu:mainMenu];
234 [NSApp setWindowsMenu:submenu];
236 [NSApp setApplicationIconImage:self.applicationIcon];
240 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
242 PerformRequest(NULL);
248 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
249 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
251 inMode:NSDefaultRunLoopMode
254 [NSApp sendEvent:event];
258 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
259 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
264 - (BOOL) registerEventQueue:(WineEventQueue*)queue
266 [eventQueuesLock lock];
267 [eventQueues addObject:queue];
268 [eventQueuesLock unlock];
272 - (void) unregisterEventQueue:(WineEventQueue*)queue
274 [eventQueuesLock lock];
275 [eventQueues removeObjectIdenticalTo:queue];
276 [eventQueuesLock unlock];
279 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
281 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
284 - (double) ticksForEventTime:(NSTimeInterval)eventTime
286 return (eventTime + eventTimeAdjustment) * 1000;
289 /* Invalidate old focus offers across all queues. */
290 - (void) invalidateGotFocusEvents
292 WineEventQueue* queue;
296 [eventQueuesLock lock];
297 for (queue in eventQueues)
299 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
302 [eventQueuesLock unlock];
305 - (void) windowGotFocus:(WineWindow*)window
309 [self invalidateGotFocusEvents];
311 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
312 event->window_got_focus.serial = windowFocusSerial;
314 event->window_got_focus.tried_windows = [triedWindows retain];
316 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
317 [window.queue postEvent:event];
318 macdrv_release_event(event);
321 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
323 if (event->window_got_focus.serial == windowFocusSerial)
325 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
326 [triedWindows addObject:(WineWindow*)event->window];
327 for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWineWindows]])
329 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
331 [window makeKeyWindow];
339 - (void) keyboardSelectionDidChange
341 TISInputSourceRef inputSource;
343 inputSourceIsInputMethodValid = FALSE;
345 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
349 uchr = TISGetInputSourceProperty(inputSource,
350 kTISPropertyUnicodeKeyLayoutData);
354 WineEventQueue* queue;
356 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
357 event->keyboard_changed.keyboard_type = self.keyboardType;
358 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
359 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
361 if (event->keyboard_changed.uchr)
363 [eventQueuesLock lock];
365 for (queue in eventQueues)
366 [queue postEvent:event];
368 [eventQueuesLock unlock];
371 macdrv_release_event(event);
374 CFRelease(inputSource);
378 - (CGFloat) primaryScreenHeight
380 if (!primaryScreenHeightValid)
382 NSArray* screens = [NSScreen screens];
383 NSUInteger count = [screens count];
390 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
391 primaryScreenHeightValid = TRUE;
393 size = count * sizeof(CGRect);
394 if (!screenFrameCGRects)
395 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
397 [screenFrameCGRects setLength:size];
399 rect = [screenFrameCGRects mutableBytes];
400 for (screen in screens)
402 CGRect temp = NSRectToCGRect([screen frame]);
403 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
408 return 1280; /* arbitrary value */
411 return primaryScreenHeight;
414 - (NSPoint) flippedMouseLocation:(NSPoint)point
416 /* This relies on the fact that Cocoa's mouse location points are
417 actually off by one (precisely because they were flipped from
418 Quartz screen coordinates using this same technique). */
419 point.y = [self primaryScreenHeight] - point.y;
423 - (void) flipRect:(NSRect*)rect
425 // We don't use -primaryScreenHeight here so there's no chance of having
426 // out-of-date cached info. This method is called infrequently enough
427 // that getting the screen height each time is not prohibitively expensive.
428 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
431 - (void) wineWindow:(WineWindow*)window
432 ordered:(NSWindowOrderingMode)order
433 relativeTo:(WineWindow*)otherWindow
441 [orderedWineWindows removeObjectIdenticalTo:window];
444 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
445 if (index == NSNotFound)
451 for (otherWindow in orderedWineWindows)
453 if ([otherWindow levelWhenActive] <= [window levelWhenActive])
458 [orderedWineWindows insertObject:window atIndex:index];
463 [orderedWineWindows removeObjectIdenticalTo:window];
466 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
467 if (index == NSNotFound)
468 index = [orderedWineWindows count];
473 for (otherWindow in orderedWineWindows)
475 if ([otherWindow levelWhenActive] < [window levelWhenActive])
480 [orderedWineWindows insertObject:window atIndex:index];
489 - (WineWindow*) frontWineWindow
491 NSNumber* windowNumber;
492 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
494 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
495 if ([window isKindOfClass:[WineWindow class]] && [window screen])
496 return (WineWindow*)window;
502 - (void) sendDisplaysChanged:(BOOL)activating
505 WineEventQueue* queue;
507 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
508 event->displays_changed.activating = activating;
510 [eventQueuesLock lock];
512 // If we're activating, then we just need one of our threads to get the
513 // event, so it can send it directly to the desktop window. Otherwise,
514 // we need all of the threads to get it because we don't know which owns
515 // the desktop window and only that one will do anything with it.
516 if (activating) event->deliver = 1;
518 for (queue in eventQueues)
519 [queue postEvent:event];
520 [eventQueuesLock unlock];
522 macdrv_release_event(event);
525 // We can compare two modes directly using CFEqual, but that may require that
526 // they are identical to a level that we don't need. In particular, when the
527 // OS switches between the integrated and discrete GPUs, the set of display
528 // modes can change in subtle ways. We're interested in whether two modes
529 // match in their most salient features, even if they aren't identical.
530 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
532 NSString *encoding1, *encoding2;
533 uint32_t ioflags1, ioflags2, different;
534 double refresh1, refresh2;
536 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
537 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
539 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
540 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
541 if (![encoding1 isEqualToString:encoding2]) return FALSE;
543 ioflags1 = CGDisplayModeGetIOFlags(mode1);
544 ioflags2 = CGDisplayModeGetIOFlags(mode2);
545 different = ioflags1 ^ ioflags2;
546 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
547 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
550 refresh1 = CGDisplayModeGetRefreshRate(mode1);
551 if (refresh1 == 0) refresh1 = 60;
552 refresh2 = CGDisplayModeGetRefreshRate(mode2);
553 if (refresh2 == 0) refresh2 = 60;
554 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
559 - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
561 CGDisplayModeRef ret = NULL;
562 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
563 for (id candidateModeObject in modes)
565 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
566 if ([self mode:candidateMode matchesMode:mode])
575 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
578 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
579 CGDisplayModeRef currentMode, originalMode;
581 currentMode = CGDisplayCopyDisplayMode(displayID);
582 if (!currentMode) // Invalid display ID
585 if ([self mode:mode matchesMode:currentMode]) // Already there!
587 CGDisplayModeRelease(currentMode);
591 mode = [self modeMatchingMode:mode forDisplay:displayID];
594 CGDisplayModeRelease(currentMode);
598 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
600 originalMode = currentMode;
602 if ([self mode:mode matchesMode:originalMode])
604 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
606 CGRestorePermanentDisplayConfiguration();
607 CGReleaseAllDisplays();
608 [originalDisplayModes removeAllObjects];
611 else // ... otherwise, try to restore just the one display
613 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
615 [originalDisplayModes removeObjectForKey:displayIDKey];
622 if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
624 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
626 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
629 else if (![originalDisplayModes count])
631 CGRestorePermanentDisplayConfiguration();
632 CGReleaseAllDisplays();
637 CGDisplayModeRelease(currentMode);
641 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
642 [(WineWindow*)obj adjustWindowLevel];
649 - (BOOL) areDisplaysCaptured
651 return ([originalDisplayModes count] > 0);
663 - (void) unhideCursor
668 cursorHidden = FALSE;
674 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
675 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
676 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
677 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
681 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
682 hotSpot = CGPointZero;
683 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
690 - (void) nextCursorFrame:(NSTimer*)theTimer
693 NSTimeInterval duration;
697 if (cursorFrame >= [cursorFrames count])
701 frame = [cursorFrames objectAtIndex:cursorFrame];
702 duration = [[frame objectForKey:@"duration"] doubleValue];
703 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
704 [cursorTimer setFireDate:date];
707 - (void) setCursorWithFrames:(NSArray*)frames
709 if (self.cursorFrames == frames)
712 self.cursorFrames = frames;
714 [cursorTimer invalidate];
715 self.cursorTimer = nil;
719 if ([frames count] > 1)
721 NSDictionary* frame = [frames objectAtIndex:0];
722 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
723 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
724 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
727 selector:@selector(nextCursorFrame:)
729 repeats:YES] autorelease];
730 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
737 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
739 NSImage* nsimage = nil;
743 NSSize bestSize = NSZeroSize;
746 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
748 for (image in images)
750 CGImageRef cgimage = (CGImageRef)image;
751 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
754 NSSize size = [imageRep size];
756 [nsimage addRepresentation:imageRep];
759 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
764 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
765 [nsimage setSize:bestSize];
770 self.applicationIcon = nsimage;
771 [NSApp setApplicationIconImage:nsimage];
774 - (void) handleCommandTab
776 if ([NSApp isActive])
778 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
779 NSRunningApplication* app;
780 NSRunningApplication* otherValidApp = nil;
782 if ([originalDisplayModes count])
784 CGRestorePermanentDisplayConfiguration();
785 CGReleaseAllDisplays();
786 [originalDisplayModes removeAllObjects];
789 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
791 if (![app isEqual:thisApp] && !app.terminated &&
792 app.activationPolicy == NSApplicationActivationPolicyRegular)
796 // There's another visible app. Just hide ourselves and let
797 // the system activate the other app.
807 // Didn't find a visible GUI app. Try the Finder or, if that's not
808 // running, the first hidden GUI app. If even that doesn't work, we
809 // just fail to switch and remain the active app.
810 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
811 if (!app) app = otherValidApp;
813 [app activateWithOptions:0];
818 * ---------- Cursor clipping methods ----------
820 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
821 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
822 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
823 * general case, we leverage that. We disassociate mouse movements from
824 * the cursor position and then move the cursor manually, keeping it within
825 * the clipping rectangle.
827 * Moving the cursor manually isn't enough. We need to modify the event
828 * stream so that the events have the new location, too. We need to do
829 * this at a point before the events enter Cocoa, so that Cocoa will assign
830 * the correct window to the event. So, we install a Quartz event tap to
833 * Also, there's a complication when we move the cursor. We use
834 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
835 * events, but the change of cursor position is incorporated into the
836 * deltas of the next mouse move event. When the mouse is disassociated
837 * from the cursor position, we need the deltas to only reflect actual
838 * device movement, not programmatic changes. So, the event tap cancels
839 * out the change caused by our calls to CGWarpMouseCursorPosition().
841 - (void) clipCursorLocation:(CGPoint*)location
843 if (location->x < CGRectGetMinX(cursorClipRect))
844 location->x = CGRectGetMinX(cursorClipRect);
845 if (location->y < CGRectGetMinY(cursorClipRect))
846 location->y = CGRectGetMinY(cursorClipRect);
847 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
848 location->x = CGRectGetMaxX(cursorClipRect) - 1;
849 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
850 location->y = CGRectGetMaxY(cursorClipRect) - 1;
853 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
858 oldLocation = *currentLocation;
860 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
862 if (!CGPointEqualToPoint(oldLocation, *newLocation))
864 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
867 warpRecord.from = oldLocation;
868 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
870 /* Actually move the cursor. */
871 err = CGWarpMouseCursorPosition(*newLocation);
872 if (err != kCGErrorSuccess)
875 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
876 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
878 if (!CGPointEqualToPoint(oldLocation, *newLocation))
880 warpRecord.to = *newLocation;
881 [warpRecords addObject:warpRecord];
888 - (BOOL) isMouseMoveEventType:(CGEventType)type
892 case kCGEventMouseMoved:
893 case kCGEventLeftMouseDragged:
894 case kCGEventRightMouseDragged:
895 case kCGEventOtherMouseDragged:
902 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
904 int warpsFinished = 0;
905 for (WarpRecord* warpRecord in warpRecords)
907 if (warpRecord.timeAfter < eventTime ||
908 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
914 return warpsFinished;
917 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
918 type:(CGEventType)type
919 event:(CGEventRef)event
921 CGEventTimestamp eventTime;
922 CGPoint eventLocation, cursorLocation;
924 if (type == kCGEventTapDisabledByUserInput)
926 if (type == kCGEventTapDisabledByTimeout)
928 CGEventTapEnable(cursorClippingEventTap, TRUE);
935 eventTime = CGEventGetTimestamp(event);
936 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
938 eventLocation = CGEventGetLocation(event);
940 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
942 if ([self isMouseMoveEventType:type])
944 double deltaX, deltaY;
945 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
948 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
949 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
951 for (i = 0; i < warpsFinished; i++)
953 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
954 deltaX -= warpRecord.to.x - warpRecord.from.x;
955 deltaY -= warpRecord.to.y - warpRecord.from.y;
956 [warpRecords removeObjectAtIndex:0];
961 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
962 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
965 synthesizedLocation.x += deltaX;
966 synthesizedLocation.y += deltaY;
969 // If the event is destined for another process, don't clip it. This may
970 // happen if the user activates Exposé or Mission Control. In that case,
971 // our app does not resign active status, so clipping is still in effect,
972 // but the cursor should not actually be clipped.
974 // In addition, the fact that mouse moves may have been delivered to a
975 // different process means we have to treat the next one we receive as
976 // absolute rather than relative.
977 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
978 [self clipCursorLocation:&synthesizedLocation];
980 lastSetCursorPositionTime = lastEventTapEventTime;
982 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
983 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
984 CGEventSetLocation(event, synthesizedLocation);
989 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
990 CGEventRef event, void *refcon)
992 WineApplicationController* controller = refcon;
993 return [controller eventTapWithProxy:proxy type:type event:event];
996 - (BOOL) installEventTap
998 ProcessSerialNumber psn;
1000 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1001 CGEventMaskBit(kCGEventLeftMouseUp) |
1002 CGEventMaskBit(kCGEventRightMouseDown) |
1003 CGEventMaskBit(kCGEventRightMouseUp) |
1004 CGEventMaskBit(kCGEventMouseMoved) |
1005 CGEventMaskBit(kCGEventLeftMouseDragged) |
1006 CGEventMaskBit(kCGEventRightMouseDragged) |
1007 CGEventMaskBit(kCGEventOtherMouseDown) |
1008 CGEventMaskBit(kCGEventOtherMouseUp) |
1009 CGEventMaskBit(kCGEventOtherMouseDragged) |
1010 CGEventMaskBit(kCGEventScrollWheel);
1011 CFRunLoopSourceRef source;
1013 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1015 if (cursorClippingEventTap)
1018 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1019 // framework with dlsym() because the Win32 function of the same name
1021 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1025 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1026 if (!pGetCurrentProcess)
1028 dlclose(appServices);
1032 err = pGetCurrentProcess(&psn);
1033 dlclose(appServices);
1037 // We create an annotated session event tap rather than a process-specific
1038 // event tap because we need to programmatically move the cursor even when
1039 // mouse moves are directed to other processes. We disable our tap when
1040 // other processes are active, but things like Exposé are handled by other
1041 // processes even when we remain active.
1042 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1043 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1044 if (!cursorClippingEventTap)
1047 CGEventTapEnable(cursorClippingEventTap, FALSE);
1049 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1052 CFRelease(cursorClippingEventTap);
1053 cursorClippingEventTap = NULL;
1057 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1062 - (BOOL) setCursorPosition:(CGPoint)pos
1068 [self clipCursorLocation:&pos];
1070 ret = [self warpCursorTo:&pos from:NULL];
1071 synthesizedLocation = pos;
1074 // We want to discard mouse-move events that have already been
1075 // through the event tap, because it's too late to account for
1076 // the setting of the cursor position with them. However, the
1077 // events that may be queued with times after that but before
1078 // the above warp can still be used. So, use the last event
1079 // tap event time so that -sendEvent: doesn't discard them.
1080 lastSetCursorPositionTime = lastEventTapEventTime;
1085 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1088 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1090 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1091 // the mouse from the cursor position for 0.25 seconds. This means
1092 // that mouse movement during that interval doesn't move the cursor
1093 // and events carry a constant location (the warped-to position)
1094 // even though they have delta values. This screws us up because
1095 // the accumulated deltas we send to Wine don't match any eventual
1096 // absolute position we send (like with a button press). We can
1097 // work around this by simply forcibly reassociating the mouse and
1099 CGAssociateMouseAndMouseCursorPosition(true);
1105 WineEventQueue* queue;
1107 // Discard all pending mouse move events.
1108 [eventQueuesLock lock];
1109 for (queue in eventQueues)
1111 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1112 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1114 [queue resetMouseEventPositions:pos];
1116 [eventQueuesLock unlock];
1122 - (void) activateCursorClipping
1126 CGEventTapEnable(cursorClippingEventTap, TRUE);
1127 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1131 - (void) deactivateCursorClipping
1135 CGEventTapEnable(cursorClippingEventTap, FALSE);
1136 [warpRecords removeAllObjects];
1137 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1141 - (BOOL) startClippingCursor:(CGRect)rect
1145 if (!cursorClippingEventTap && ![self installEventTap])
1148 err = CGAssociateMouseAndMouseCursorPosition(false);
1149 if (err != kCGErrorSuccess)
1152 clippingCursor = TRUE;
1153 cursorClipRect = rect;
1154 if ([NSApp isActive])
1155 [self activateCursorClipping];
1160 - (BOOL) stopClippingCursor
1162 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1163 if (err != kCGErrorSuccess)
1166 [self deactivateCursorClipping];
1167 clippingCursor = FALSE;
1172 - (void) handleMouseMove:(NSEvent*)anEvent
1174 WineWindow* targetWindow;
1175 BOOL drag = [anEvent type] != NSMouseMoved;
1177 if (mouseCaptureWindow)
1178 targetWindow = mouseCaptureWindow;
1180 targetWindow = (WineWindow*)[anEvent window];
1183 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1184 event indicates its window is the main window, even if the cursor is
1185 over a different window. Find the actual WineWindow that is under the
1186 cursor and post the event as being for that window. */
1187 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1188 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1189 NSInteger windowUnderNumber;
1191 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1192 belowWindowWithWindowNumber:0];
1193 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1196 if ([targetWindow isKindOfClass:[WineWindow class]])
1198 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1199 macdrv_event* event;
1202 // If we recently warped the cursor (other than in our cursor-clipping
1203 // event tap), discard mouse move events until we see an event which is
1204 // later than that time.
1205 if (lastSetCursorPositionTime)
1207 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1210 lastSetCursorPositionTime = 0;
1211 forceNextMouseMoveAbsolute = TRUE;
1214 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1217 forceNextMouseMoveAbsolute = FALSE;
1221 // Send absolute move events if the cursor is in the interior of
1222 // its range. Only send relative moves if the cursor is pinned to
1223 // the boundaries of where it can go. We compute the position
1224 // that's one additional point in the direction of movement. If
1225 // that is outside of the clipping rect or desktop region (the
1226 // union of the screen frames), then we figure the cursor would
1227 // have moved outside if it could but it was pinned.
1228 CGPoint computedPoint = point;
1229 CGFloat deltaX = [anEvent deltaX];
1230 CGFloat deltaY = [anEvent deltaY];
1234 else if (deltaX < -0.001)
1239 else if (deltaY < -0.001)
1242 // Assume cursor is pinned for now
1244 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1246 const CGRect* rects;
1247 NSUInteger count, i;
1249 // Caches screenFrameCGRects if necessary
1250 [self primaryScreenHeight];
1252 rects = [screenFrameCGRects bytes];
1253 count = [screenFrameCGRects length] / sizeof(rects[0]);
1255 for (i = 0; i < count; i++)
1257 if (CGRectContainsPoint(rects[i], computedPoint))
1269 [self clipCursorLocation:&point];
1271 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1272 event->mouse_moved.x = point.x;
1273 event->mouse_moved.y = point.y;
1275 mouseMoveDeltaX = 0;
1276 mouseMoveDeltaY = 0;
1280 /* Add event delta to accumulated delta error */
1281 /* deltaY is already flipped */
1282 mouseMoveDeltaX += [anEvent deltaX];
1283 mouseMoveDeltaY += [anEvent deltaY];
1285 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1286 event->mouse_moved.x = mouseMoveDeltaX;
1287 event->mouse_moved.y = mouseMoveDeltaY;
1289 /* Keep the remainder after integer truncation. */
1290 mouseMoveDeltaX -= event->mouse_moved.x;
1291 mouseMoveDeltaY -= event->mouse_moved.y;
1294 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1296 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1297 event->mouse_moved.drag = drag;
1299 [targetWindow.queue postEvent:event];
1302 macdrv_release_event(event);
1304 lastTargetWindow = targetWindow;
1306 else if (lastTargetWindow)
1308 [[NSCursor arrowCursor] set];
1309 [self unhideCursor];
1310 lastTargetWindow = nil;
1314 - (void) handleMouseButton:(NSEvent*)theEvent
1318 if (mouseCaptureWindow)
1319 window = mouseCaptureWindow;
1321 window = (WineWindow*)[theEvent window];
1323 if ([window isKindOfClass:[WineWindow class]])
1325 NSEventType type = [theEvent type];
1326 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1327 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1331 [self clipCursorLocation:&pt];
1335 if (mouseCaptureWindow)
1339 // Test if the click was in the window's content area.
1340 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1341 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1342 process = NSPointInRect(nspoint, contentRect);
1343 if (process && [window styleMask] & NSResizableWindowMask)
1345 // Ignore clicks in the grow box (resize widget).
1346 HIPoint origin = { 0, 0 };
1347 HIThemeGrowBoxDrawInfo info = { 0 };
1351 info.kind = kHIThemeGrowBoxKindNormal;
1352 info.direction = kThemeGrowRight | kThemeGrowDown;
1353 if ([window styleMask] & NSUtilityWindowMask)
1354 info.size = kHIThemeGrowBoxSizeSmall;
1356 info.size = kHIThemeGrowBoxSizeNormal;
1358 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1359 if (status == noErr)
1361 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1362 NSMinY(contentRect),
1364 bounds.size.height);
1365 process = !NSPointInRect(nspoint, growBox);
1370 unmatchedMouseDowns |= NSEventMaskFromType(type);
1374 NSEventType downType = type - 1;
1375 NSUInteger downMask = NSEventMaskFromType(downType);
1376 process = (unmatchedMouseDowns & downMask) != 0;
1377 unmatchedMouseDowns &= ~downMask;
1382 macdrv_event* event;
1384 event = macdrv_create_event(MOUSE_BUTTON, window);
1385 event->mouse_button.button = [theEvent buttonNumber];
1386 event->mouse_button.pressed = pressed;
1387 event->mouse_button.x = pt.x;
1388 event->mouse_button.y = pt.y;
1389 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1391 [window.queue postEvent:event];
1393 macdrv_release_event(event);
1397 // Since mouse button events deliver absolute cursor position, the
1398 // accumulating delta from move events is invalidated. Make sure
1399 // next mouse move event starts over from an absolute baseline.
1400 // Also, it's at least possible that the title bar widgets (e.g. close
1401 // button, etc.) could enter an internal event loop on a mouse down that
1402 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1403 // dragged events and, after that, any notion of the cursor position
1404 // computed from accumulating deltas would be wrong.
1405 forceNextMouseMoveAbsolute = TRUE;
1408 - (void) handleScrollWheel:(NSEvent*)theEvent
1412 if (mouseCaptureWindow)
1413 window = mouseCaptureWindow;
1415 window = (WineWindow*)[theEvent window];
1417 if ([window isKindOfClass:[WineWindow class]])
1419 CGEventRef cgevent = [theEvent CGEvent];
1420 CGPoint pt = CGEventGetLocation(cgevent);
1424 [self clipCursorLocation:&pt];
1426 if (mouseCaptureWindow)
1430 // Only process the event if it was in the window's content area.
1431 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1432 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1433 process = NSPointInRect(nspoint, contentRect);
1438 macdrv_event* event;
1440 BOOL continuous = FALSE;
1442 event = macdrv_create_event(MOUSE_SCROLL, window);
1443 event->mouse_scroll.x = pt.x;
1444 event->mouse_scroll.y = pt.y;
1445 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1447 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1451 /* Continuous scroll wheel events come from high-precision scrolling
1452 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1453 For these, we can get more precise data from the CGEvent API. */
1454 /* Axis 1 is vertical, axis 2 is horizontal. */
1455 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1456 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1460 double pixelsPerLine = 10;
1461 CGEventSourceRef source;
1463 /* The non-continuous values are in units of "lines", not pixels. */
1464 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1466 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1470 x = pixelsPerLine * [theEvent deltaX];
1471 y = pixelsPerLine * [theEvent deltaY];
1474 /* Mac: negative is right or down, positive is left or up.
1475 Win32: negative is left or down, positive is right or up.
1476 So, negate the X scroll value to translate. */
1479 /* The x,y values so far are in pixels. Win32 expects to receive some
1480 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1481 6 times the pixel value. */
1482 event->mouse_scroll.x_scroll = 6 * x;
1483 event->mouse_scroll.y_scroll = 6 * y;
1487 /* For non-continuous "clicky" wheels, if there was any motion, make
1488 sure there was at least WHEEL_DELTA motion. This is so, at slow
1489 speeds where the system's acceleration curve is actually reducing the
1490 scroll distance, the user is sure to get some action out of each click.
1491 For example, this is important for rotating though weapons in a
1492 first-person shooter. */
1493 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1494 event->mouse_scroll.x_scroll = 120;
1495 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1496 event->mouse_scroll.x_scroll = -120;
1498 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1499 event->mouse_scroll.y_scroll = 120;
1500 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1501 event->mouse_scroll.y_scroll = -120;
1504 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1505 [window.queue postEvent:event];
1507 macdrv_release_event(event);
1509 // Since scroll wheel events deliver absolute cursor position, the
1510 // accumulating delta from move events is invalidated. Make sure next
1511 // mouse move event starts over from an absolute baseline.
1512 forceNextMouseMoveAbsolute = TRUE;
1517 // Returns TRUE if the event was handled and caller should do nothing more
1518 // with it. Returns FALSE if the caller should process it as normal and
1519 // then call -didSendEvent:.
1520 - (BOOL) handleEvent:(NSEvent*)anEvent
1523 NSEventType type = [anEvent type];
1525 if (type == NSFlagsChanged)
1526 self.lastFlagsChanged = anEvent;
1527 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1528 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1530 [self handleMouseMove:anEvent];
1531 ret = mouseCaptureWindow != nil;
1533 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1534 type == NSRightMouseDown || type == NSRightMouseUp ||
1535 type == NSOtherMouseDown || type == NSOtherMouseUp)
1537 [self handleMouseButton:anEvent];
1538 ret = mouseCaptureWindow != nil;
1540 else if (type == NSScrollWheel)
1542 [self handleScrollWheel:anEvent];
1543 ret = mouseCaptureWindow != nil;
1549 - (void) didSendEvent:(NSEvent*)anEvent
1551 NSEventType type = [anEvent type];
1553 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1555 NSUInteger modifiers = [anEvent modifierFlags];
1556 if ((modifiers & NSCommandKeyMask) &&
1557 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1559 // Command-Tab and Command-Shift-Tab would normally be intercepted
1560 // by the system to switch applications. If we're seeing it, it's
1561 // presumably because we've captured the displays, preventing
1562 // normal application switching. Do it manually.
1563 [self handleCommandTab];
1568 - (void) setupObservations
1570 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1572 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1575 usingBlock:^(NSNotification *note){
1576 NSWindow* window = [note object];
1577 [keyWindows removeObjectIdenticalTo:window];
1578 [keyWindows insertObject:window atIndex:0];
1581 [nc addObserverForName:NSWindowWillCloseNotification
1583 queue:[NSOperationQueue mainQueue]
1584 usingBlock:^(NSNotification *note){
1585 NSWindow* window = [note object];
1586 [keyWindows removeObjectIdenticalTo:window];
1587 [orderedWineWindows removeObjectIdenticalTo:window];
1588 if (window == lastTargetWindow)
1589 lastTargetWindow = nil;
1590 if (window == self.mouseCaptureWindow)
1591 self.mouseCaptureWindow = nil;
1594 [nc addObserver:self
1595 selector:@selector(keyboardSelectionDidChange)
1596 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1599 /* The above notification isn't sent unless the NSTextInputContext
1600 class has initialized itself. Poke it. */
1601 [NSTextInputContext self];
1604 - (BOOL) inputSourceIsInputMethod
1606 if (!inputSourceIsInputMethodValid)
1608 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1611 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1612 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1613 CFRelease(inputSource);
1616 inputSourceIsInputMethod = FALSE;
1617 inputSourceIsInputMethodValid = TRUE;
1620 return inputSourceIsInputMethod;
1625 * ---------- NSApplicationDelegate methods ----------
1627 - (void)applicationDidBecomeActive:(NSNotification *)notification
1629 [self activateCursorClipping];
1631 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1632 WineWindow* window = obj;
1633 if ([window levelWhenActive] != [window level])
1634 [window setLevel:[window levelWhenActive]];
1637 if (![self frontWineWindow])
1639 for (WineWindow* window in [NSApp windows])
1641 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1643 [window deminiaturize:self];
1649 // If a Wine process terminates abruptly while it has the display captured
1650 // and switched to a different resolution, Mac OS X will uncapture the
1651 // displays and switch their resolutions back. However, the other Wine
1652 // processes won't have their notion of the desktop rect changed back.
1653 // This can lead them to refuse to draw or acknowledge clicks in certain
1654 // portions of their windows.
1656 // To solve this, we synthesize a displays-changed event whenever we're
1657 // activated. This will provoke a re-synchronization of Wine's notion of
1658 // the desktop rect with the actual state.
1659 [self sendDisplaysChanged:TRUE];
1661 // The cursor probably moved while we were inactive. Accumulated mouse
1662 // movement deltas are invalidated. Make sure the next mouse move event
1663 // starts over from an absolute baseline.
1664 forceNextMouseMoveAbsolute = TRUE;
1667 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1669 primaryScreenHeightValid = FALSE;
1670 [self sendDisplaysChanged:FALSE];
1672 // When the display configuration changes, the cursor position may jump.
1673 // Accumulated mouse movement deltas are invalidated. Make sure the next
1674 // mouse move event starts over from an absolute baseline.
1675 forceNextMouseMoveAbsolute = TRUE;
1678 - (void)applicationDidResignActive:(NSNotification *)notification
1680 macdrv_event* event;
1681 WineEventQueue* queue;
1683 [self invalidateGotFocusEvents];
1685 event = macdrv_create_event(APP_DEACTIVATED, nil);
1687 [eventQueuesLock lock];
1688 for (queue in eventQueues)
1689 [queue postEvent:event];
1690 [eventQueuesLock unlock];
1692 macdrv_release_event(event);
1695 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1697 NSApplicationTerminateReply ret = NSTerminateNow;
1698 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1699 NSAppleEventDescriptor* desc = [m currentAppleEvent];
1700 macdrv_event* event;
1701 WineEventQueue* queue;
1703 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1705 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1708 case kAEReallyLogOut:
1709 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1711 case kAEShowRestartDialog:
1712 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1714 case kAEShowShutdownDialog:
1715 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1718 event->app_quit_requested.reason = QUIT_REASON_NONE;
1722 [eventQueuesLock lock];
1724 if ([eventQueues count])
1726 for (queue in eventQueues)
1727 [queue postEvent:event];
1728 ret = NSTerminateLater;
1731 [eventQueuesLock unlock];
1733 macdrv_release_event(event);
1738 - (void)applicationWillResignActive:(NSNotification *)notification
1740 [self deactivateCursorClipping];
1742 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1743 WineWindow* window = obj;
1744 NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1745 if ([window level] > level)
1746 [window setLevel:level];
1750 /***********************************************************************
1753 * Run-loop-source perform callback. Pull request blocks from the
1754 * array of queued requests and invoke them.
1756 static void PerformRequest(void *info)
1758 WineApplicationController* controller = [WineApplicationController sharedController];
1762 __block dispatch_block_t block;
1764 dispatch_sync(controller->requestsManipQueue, ^{
1765 if ([controller->requests count])
1767 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1768 [controller->requests removeObjectAtIndex:0];
1782 /***********************************************************************
1785 * Run a block on the main thread asynchronously.
1787 void OnMainThreadAsync(dispatch_block_t block)
1789 WineApplicationController* controller = [WineApplicationController sharedController];
1791 block = [block copy];
1792 dispatch_sync(controller->requestsManipQueue, ^{
1793 [controller->requests addObject:block];
1796 CFRunLoopSourceSignal(controller->requestSource);
1797 CFRunLoopWakeUp(CFRunLoopGetMain());
1802 /***********************************************************************
1805 void LogError(const char* func, NSString* format, ...)
1808 va_start(args, format);
1809 LogErrorv(func, format, args);
1813 /***********************************************************************
1816 void LogErrorv(const char* func, NSString* format, va_list args)
1818 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1820 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1821 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1827 /***********************************************************************
1828 * macdrv_window_rejected_focus
1830 * Pass focus to the next window that hasn't already rejected this same
1831 * WINDOW_GOT_FOCUS event.
1833 void macdrv_window_rejected_focus(const macdrv_event *event)
1836 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1840 /***********************************************************************
1841 * macdrv_get_keyboard_layout
1843 * Returns the keyboard layout uchr data.
1845 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1847 __block CFDataRef result = NULL;
1850 TISInputSourceRef inputSource;
1852 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1855 CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1856 kTISPropertyUnicodeKeyLayoutData);
1857 result = CFDataCreateCopy(NULL, uchr);
1858 CFRelease(inputSource);
1860 *keyboard_type = [WineApplicationController sharedController].keyboardType;
1861 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1868 /***********************************************************************
1871 * Play the beep sound configured by the user in System Preferences.
1873 void macdrv_beep(void)
1875 OnMainThreadAsync(^{
1880 /***********************************************************************
1881 * macdrv_set_display_mode
1883 int macdrv_set_display_mode(const struct macdrv_display* display,
1884 CGDisplayModeRef display_mode)
1889 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1895 /***********************************************************************
1900 * If name is non-NULL, it is a selector for a class method on NSCursor
1901 * identifying the cursor to set. In that case, frames is ignored. If
1902 * name is NULL, then frames is used.
1904 * frames is an array of dictionaries. Each dictionary is a frame of
1905 * an animated cursor. Under the key "image" is a CGImage for the
1906 * frame. Under the key "duration" is a CFNumber time interval, in
1907 * seconds, for how long that frame is presented before proceeding to
1908 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
1909 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1910 * This is the hot spot, measured in pixels down and to the right of the
1911 * top-left corner of the image.
1913 * If the array has exactly 1 element, the cursor is static, not
1914 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
1916 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1920 sel = NSSelectorFromString((NSString*)name);
1923 OnMainThreadAsync(^{
1924 WineApplicationController* controller = [WineApplicationController sharedController];
1925 NSCursor* cursor = [NSCursor performSelector:sel];
1926 [controller setCursorWithFrames:nil];
1928 [controller unhideCursor];
1933 NSArray* nsframes = (NSArray*)frames;
1934 if ([nsframes count])
1936 OnMainThreadAsync(^{
1937 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1942 OnMainThreadAsync(^{
1943 WineApplicationController* controller = [WineApplicationController sharedController];
1944 [controller setCursorWithFrames:nil];
1945 [controller hideCursor];
1951 /***********************************************************************
1952 * macdrv_get_cursor_position
1954 * Obtains the current cursor position. Returns zero on failure,
1955 * non-zero on success.
1957 int macdrv_get_cursor_position(CGPoint *pos)
1960 NSPoint location = [NSEvent mouseLocation];
1961 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1962 *pos = NSPointToCGPoint(location);
1968 /***********************************************************************
1969 * macdrv_set_cursor_position
1971 * Sets the cursor position without generating events. Returns zero on
1972 * failure, non-zero on success.
1974 int macdrv_set_cursor_position(CGPoint pos)
1979 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1985 /***********************************************************************
1986 * macdrv_clip_cursor
1988 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
1989 * to or larger than the whole desktop region, the cursor is unclipped.
1990 * Returns zero on failure, non-zero on success.
1992 int macdrv_clip_cursor(CGRect rect)
1997 WineApplicationController* controller = [WineApplicationController sharedController];
1998 BOOL clipping = FALSE;
2000 if (!CGRectIsInfinite(rect))
2002 NSRect nsrect = NSRectFromCGRect(rect);
2005 /* Convert the rectangle from top-down coords to bottom-up. */
2006 [controller flipRect:&nsrect];
2009 for (screen in [NSScreen screens])
2011 if (!NSContainsRect(nsrect, [screen frame]))
2020 ret = [controller startClippingCursor:rect];
2022 ret = [controller stopClippingCursor];
2028 /***********************************************************************
2029 * macdrv_set_application_icon
2031 * Set the application icon. The images array contains CGImages. If
2032 * there are more than one, then they represent different sizes or
2033 * color depths from the icon resource. If images is NULL or empty,
2034 * restores the default application image.
2036 void macdrv_set_application_icon(CFArrayRef images)
2038 NSArray* imageArray = (NSArray*)images;
2040 OnMainThreadAsync(^{
2041 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2045 /***********************************************************************
2048 void macdrv_quit_reply(int reply)
2051 [NSApp replyToApplicationShouldTerminate:reply];
2055 /***********************************************************************
2056 * macdrv_using_input_method
2058 int macdrv_using_input_method(void)
2063 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2069 /***********************************************************************
2070 * macdrv_set_mouse_capture_window
2072 void macdrv_set_mouse_capture_window(macdrv_window window)
2074 WineWindow* w = (WineWindow*)window;
2077 [[WineApplicationController sharedController] setMouseCaptureWindow:w];