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 - (void) sendDisplaysChanged:(BOOL)activating
492 WineEventQueue* queue;
494 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
495 event->displays_changed.activating = activating;
497 [eventQueuesLock lock];
499 // If we're activating, then we just need one of our threads to get the
500 // event, so it can send it directly to the desktop window. Otherwise,
501 // we need all of the threads to get it because we don't know which owns
502 // the desktop window and only that one will do anything with it.
503 if (activating) event->deliver = 1;
505 for (queue in eventQueues)
506 [queue postEvent:event];
507 [eventQueuesLock unlock];
509 macdrv_release_event(event);
512 // We can compare two modes directly using CFEqual, but that may require that
513 // they are identical to a level that we don't need. In particular, when the
514 // OS switches between the integrated and discrete GPUs, the set of display
515 // modes can change in subtle ways. We're interested in whether two modes
516 // match in their most salient features, even if they aren't identical.
517 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
519 NSString *encoding1, *encoding2;
520 uint32_t ioflags1, ioflags2, different;
521 double refresh1, refresh2;
523 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
524 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
526 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
527 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
528 if (![encoding1 isEqualToString:encoding2]) return FALSE;
530 ioflags1 = CGDisplayModeGetIOFlags(mode1);
531 ioflags2 = CGDisplayModeGetIOFlags(mode2);
532 different = ioflags1 ^ ioflags2;
533 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
534 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
537 refresh1 = CGDisplayModeGetRefreshRate(mode1);
538 if (refresh1 == 0) refresh1 = 60;
539 refresh2 = CGDisplayModeGetRefreshRate(mode2);
540 if (refresh2 == 0) refresh2 = 60;
541 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
546 - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
548 CGDisplayModeRef ret = NULL;
549 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
550 for (id candidateModeObject in modes)
552 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
553 if ([self mode:candidateMode matchesMode:mode])
562 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
565 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
566 CGDisplayModeRef currentMode, originalMode;
568 currentMode = CGDisplayCopyDisplayMode(displayID);
569 if (!currentMode) // Invalid display ID
572 if ([self mode:mode matchesMode:currentMode]) // Already there!
574 CGDisplayModeRelease(currentMode);
578 mode = [self modeMatchingMode:mode forDisplay:displayID];
581 CGDisplayModeRelease(currentMode);
585 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
587 originalMode = currentMode;
589 if ([self mode:mode matchesMode:originalMode])
591 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
593 CGRestorePermanentDisplayConfiguration();
594 CGReleaseAllDisplays();
595 [originalDisplayModes removeAllObjects];
598 else // ... otherwise, try to restore just the one display
600 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
602 [originalDisplayModes removeObjectForKey:displayIDKey];
609 if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
611 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
613 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
616 else if (![originalDisplayModes count])
618 CGRestorePermanentDisplayConfiguration();
619 CGReleaseAllDisplays();
624 CGDisplayModeRelease(currentMode);
628 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
629 [(WineWindow*)obj adjustWindowLevel];
636 - (BOOL) areDisplaysCaptured
638 return ([originalDisplayModes count] > 0);
650 - (void) unhideCursor
655 cursorHidden = FALSE;
661 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
662 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
663 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
664 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
668 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
669 hotSpot = CGPointZero;
670 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
677 - (void) nextCursorFrame:(NSTimer*)theTimer
680 NSTimeInterval duration;
684 if (cursorFrame >= [cursorFrames count])
688 frame = [cursorFrames objectAtIndex:cursorFrame];
689 duration = [[frame objectForKey:@"duration"] doubleValue];
690 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
691 [cursorTimer setFireDate:date];
694 - (void) setCursorWithFrames:(NSArray*)frames
696 if (self.cursorFrames == frames)
699 self.cursorFrames = frames;
701 [cursorTimer invalidate];
702 self.cursorTimer = nil;
706 if ([frames count] > 1)
708 NSDictionary* frame = [frames objectAtIndex:0];
709 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
710 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
711 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
714 selector:@selector(nextCursorFrame:)
716 repeats:YES] autorelease];
717 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
724 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
726 NSImage* nsimage = nil;
730 NSSize bestSize = NSZeroSize;
733 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
735 for (image in images)
737 CGImageRef cgimage = (CGImageRef)image;
738 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
741 NSSize size = [imageRep size];
743 [nsimage addRepresentation:imageRep];
746 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
751 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
752 [nsimage setSize:bestSize];
757 self.applicationIcon = nsimage;
758 [NSApp setApplicationIconImage:nsimage];
761 - (void) handleCommandTab
763 if ([NSApp isActive])
765 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
766 NSRunningApplication* app;
767 NSRunningApplication* otherValidApp = nil;
769 if ([originalDisplayModes count])
771 CGRestorePermanentDisplayConfiguration();
772 CGReleaseAllDisplays();
773 [originalDisplayModes removeAllObjects];
776 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
778 if (![app isEqual:thisApp] && !app.terminated &&
779 app.activationPolicy == NSApplicationActivationPolicyRegular)
783 // There's another visible app. Just hide ourselves and let
784 // the system activate the other app.
794 // Didn't find a visible GUI app. Try the Finder or, if that's not
795 // running, the first hidden GUI app. If even that doesn't work, we
796 // just fail to switch and remain the active app.
797 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
798 if (!app) app = otherValidApp;
800 [app activateWithOptions:0];
805 * ---------- Cursor clipping methods ----------
807 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
808 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
809 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
810 * general case, we leverage that. We disassociate mouse movements from
811 * the cursor position and then move the cursor manually, keeping it within
812 * the clipping rectangle.
814 * Moving the cursor manually isn't enough. We need to modify the event
815 * stream so that the events have the new location, too. We need to do
816 * this at a point before the events enter Cocoa, so that Cocoa will assign
817 * the correct window to the event. So, we install a Quartz event tap to
820 * Also, there's a complication when we move the cursor. We use
821 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
822 * events, but the change of cursor position is incorporated into the
823 * deltas of the next mouse move event. When the mouse is disassociated
824 * from the cursor position, we need the deltas to only reflect actual
825 * device movement, not programmatic changes. So, the event tap cancels
826 * out the change caused by our calls to CGWarpMouseCursorPosition().
828 - (void) clipCursorLocation:(CGPoint*)location
830 if (location->x < CGRectGetMinX(cursorClipRect))
831 location->x = CGRectGetMinX(cursorClipRect);
832 if (location->y < CGRectGetMinY(cursorClipRect))
833 location->y = CGRectGetMinY(cursorClipRect);
834 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
835 location->x = CGRectGetMaxX(cursorClipRect) - 1;
836 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
837 location->y = CGRectGetMaxY(cursorClipRect) - 1;
840 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
845 oldLocation = *currentLocation;
847 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
849 if (!CGPointEqualToPoint(oldLocation, *newLocation))
851 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
854 warpRecord.from = oldLocation;
855 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
857 /* Actually move the cursor. */
858 err = CGWarpMouseCursorPosition(*newLocation);
859 if (err != kCGErrorSuccess)
862 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
863 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
865 if (!CGPointEqualToPoint(oldLocation, *newLocation))
867 warpRecord.to = *newLocation;
868 [warpRecords addObject:warpRecord];
875 - (BOOL) isMouseMoveEventType:(CGEventType)type
879 case kCGEventMouseMoved:
880 case kCGEventLeftMouseDragged:
881 case kCGEventRightMouseDragged:
882 case kCGEventOtherMouseDragged:
889 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
891 int warpsFinished = 0;
892 for (WarpRecord* warpRecord in warpRecords)
894 if (warpRecord.timeAfter < eventTime ||
895 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
901 return warpsFinished;
904 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
905 type:(CGEventType)type
906 event:(CGEventRef)event
908 CGEventTimestamp eventTime;
909 CGPoint eventLocation, cursorLocation;
911 if (type == kCGEventTapDisabledByUserInput)
913 if (type == kCGEventTapDisabledByTimeout)
915 CGEventTapEnable(cursorClippingEventTap, TRUE);
922 eventTime = CGEventGetTimestamp(event);
923 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
925 eventLocation = CGEventGetLocation(event);
927 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
929 if ([self isMouseMoveEventType:type])
931 double deltaX, deltaY;
932 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
935 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
936 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
938 for (i = 0; i < warpsFinished; i++)
940 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
941 deltaX -= warpRecord.to.x - warpRecord.from.x;
942 deltaY -= warpRecord.to.y - warpRecord.from.y;
943 [warpRecords removeObjectAtIndex:0];
948 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
949 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
952 synthesizedLocation.x += deltaX;
953 synthesizedLocation.y += deltaY;
956 // If the event is destined for another process, don't clip it. This may
957 // happen if the user activates Exposé or Mission Control. In that case,
958 // our app does not resign active status, so clipping is still in effect,
959 // but the cursor should not actually be clipped.
961 // In addition, the fact that mouse moves may have been delivered to a
962 // different process means we have to treat the next one we receive as
963 // absolute rather than relative.
964 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
965 [self clipCursorLocation:&synthesizedLocation];
967 lastSetCursorPositionTime = lastEventTapEventTime;
969 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
970 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
971 CGEventSetLocation(event, synthesizedLocation);
976 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
977 CGEventRef event, void *refcon)
979 WineApplicationController* controller = refcon;
980 return [controller eventTapWithProxy:proxy type:type event:event];
983 - (BOOL) installEventTap
985 ProcessSerialNumber psn;
987 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
988 CGEventMaskBit(kCGEventLeftMouseUp) |
989 CGEventMaskBit(kCGEventRightMouseDown) |
990 CGEventMaskBit(kCGEventRightMouseUp) |
991 CGEventMaskBit(kCGEventMouseMoved) |
992 CGEventMaskBit(kCGEventLeftMouseDragged) |
993 CGEventMaskBit(kCGEventRightMouseDragged) |
994 CGEventMaskBit(kCGEventOtherMouseDown) |
995 CGEventMaskBit(kCGEventOtherMouseUp) |
996 CGEventMaskBit(kCGEventOtherMouseDragged) |
997 CGEventMaskBit(kCGEventScrollWheel);
998 CFRunLoopSourceRef source;
1000 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1002 if (cursorClippingEventTap)
1005 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1006 // framework with dlsym() because the Win32 function of the same name
1008 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1012 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1013 if (!pGetCurrentProcess)
1015 dlclose(appServices);
1019 err = pGetCurrentProcess(&psn);
1020 dlclose(appServices);
1024 // We create an annotated session event tap rather than a process-specific
1025 // event tap because we need to programmatically move the cursor even when
1026 // mouse moves are directed to other processes. We disable our tap when
1027 // other processes are active, but things like Exposé are handled by other
1028 // processes even when we remain active.
1029 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1030 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1031 if (!cursorClippingEventTap)
1034 CGEventTapEnable(cursorClippingEventTap, FALSE);
1036 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1039 CFRelease(cursorClippingEventTap);
1040 cursorClippingEventTap = NULL;
1044 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1049 - (BOOL) setCursorPosition:(CGPoint)pos
1055 [self clipCursorLocation:&pos];
1057 ret = [self warpCursorTo:&pos from:NULL];
1058 synthesizedLocation = pos;
1061 // We want to discard mouse-move events that have already been
1062 // through the event tap, because it's too late to account for
1063 // the setting of the cursor position with them. However, the
1064 // events that may be queued with times after that but before
1065 // the above warp can still be used. So, use the last event
1066 // tap event time so that -sendEvent: doesn't discard them.
1067 lastSetCursorPositionTime = lastEventTapEventTime;
1072 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1075 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1077 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1078 // the mouse from the cursor position for 0.25 seconds. This means
1079 // that mouse movement during that interval doesn't move the cursor
1080 // and events carry a constant location (the warped-to position)
1081 // even though they have delta values. This screws us up because
1082 // the accumulated deltas we send to Wine don't match any eventual
1083 // absolute position we send (like with a button press). We can
1084 // work around this by simply forcibly reassociating the mouse and
1086 CGAssociateMouseAndMouseCursorPosition(true);
1092 WineEventQueue* queue;
1094 // Discard all pending mouse move events.
1095 [eventQueuesLock lock];
1096 for (queue in eventQueues)
1098 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1099 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1101 [queue resetMouseEventPositions:pos];
1103 [eventQueuesLock unlock];
1109 - (void) activateCursorClipping
1113 CGEventTapEnable(cursorClippingEventTap, TRUE);
1114 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1118 - (void) deactivateCursorClipping
1122 CGEventTapEnable(cursorClippingEventTap, FALSE);
1123 [warpRecords removeAllObjects];
1124 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1128 - (BOOL) startClippingCursor:(CGRect)rect
1132 if (!cursorClippingEventTap && ![self installEventTap])
1135 err = CGAssociateMouseAndMouseCursorPosition(false);
1136 if (err != kCGErrorSuccess)
1139 clippingCursor = TRUE;
1140 cursorClipRect = rect;
1141 if ([NSApp isActive])
1142 [self activateCursorClipping];
1147 - (BOOL) stopClippingCursor
1149 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1150 if (err != kCGErrorSuccess)
1153 [self deactivateCursorClipping];
1154 clippingCursor = FALSE;
1159 - (void) handleMouseMove:(NSEvent*)anEvent
1161 WineWindow* targetWindow;
1162 BOOL drag = [anEvent type] != NSMouseMoved;
1164 if (mouseCaptureWindow)
1165 targetWindow = mouseCaptureWindow;
1167 targetWindow = (WineWindow*)[anEvent window];
1170 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1171 event indicates its window is the main window, even if the cursor is
1172 over a different window. Find the actual WineWindow that is under the
1173 cursor and post the event as being for that window. */
1174 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1175 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1176 NSInteger windowUnderNumber;
1178 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1179 belowWindowWithWindowNumber:0];
1180 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1183 if ([targetWindow isKindOfClass:[WineWindow class]])
1185 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1186 macdrv_event* event;
1189 // If we recently warped the cursor (other than in our cursor-clipping
1190 // event tap), discard mouse move events until we see an event which is
1191 // later than that time.
1192 if (lastSetCursorPositionTime)
1194 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1197 lastSetCursorPositionTime = 0;
1198 forceNextMouseMoveAbsolute = TRUE;
1201 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1204 forceNextMouseMoveAbsolute = FALSE;
1208 // Send absolute move events if the cursor is in the interior of
1209 // its range. Only send relative moves if the cursor is pinned to
1210 // the boundaries of where it can go. We compute the position
1211 // that's one additional point in the direction of movement. If
1212 // that is outside of the clipping rect or desktop region (the
1213 // union of the screen frames), then we figure the cursor would
1214 // have moved outside if it could but it was pinned.
1215 CGPoint computedPoint = point;
1216 CGFloat deltaX = [anEvent deltaX];
1217 CGFloat deltaY = [anEvent deltaY];
1221 else if (deltaX < -0.001)
1226 else if (deltaY < -0.001)
1229 // Assume cursor is pinned for now
1231 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1233 const CGRect* rects;
1234 NSUInteger count, i;
1236 // Caches screenFrameCGRects if necessary
1237 [self primaryScreenHeight];
1239 rects = [screenFrameCGRects bytes];
1240 count = [screenFrameCGRects length] / sizeof(rects[0]);
1242 for (i = 0; i < count; i++)
1244 if (CGRectContainsPoint(rects[i], computedPoint))
1256 [self clipCursorLocation:&point];
1258 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1259 event->mouse_moved.x = point.x;
1260 event->mouse_moved.y = point.y;
1262 mouseMoveDeltaX = 0;
1263 mouseMoveDeltaY = 0;
1267 /* Add event delta to accumulated delta error */
1268 /* deltaY is already flipped */
1269 mouseMoveDeltaX += [anEvent deltaX];
1270 mouseMoveDeltaY += [anEvent deltaY];
1272 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1273 event->mouse_moved.x = mouseMoveDeltaX;
1274 event->mouse_moved.y = mouseMoveDeltaY;
1276 /* Keep the remainder after integer truncation. */
1277 mouseMoveDeltaX -= event->mouse_moved.x;
1278 mouseMoveDeltaY -= event->mouse_moved.y;
1281 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1283 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1284 event->mouse_moved.drag = drag;
1286 [targetWindow.queue postEvent:event];
1289 macdrv_release_event(event);
1291 lastTargetWindow = targetWindow;
1293 else if (lastTargetWindow)
1295 [[NSCursor arrowCursor] set];
1296 [self unhideCursor];
1297 lastTargetWindow = nil;
1301 - (void) handleMouseButton:(NSEvent*)theEvent
1305 if (mouseCaptureWindow)
1306 window = mouseCaptureWindow;
1308 window = (WineWindow*)[theEvent window];
1310 if ([window isKindOfClass:[WineWindow class]])
1312 NSEventType type = [theEvent type];
1313 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1314 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1318 [self clipCursorLocation:&pt];
1322 if (mouseCaptureWindow)
1326 // Test if the click was in the window's content area.
1327 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1328 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1329 process = NSPointInRect(nspoint, contentRect);
1330 if (process && [window styleMask] & NSResizableWindowMask)
1332 // Ignore clicks in the grow box (resize widget).
1333 HIPoint origin = { 0, 0 };
1334 HIThemeGrowBoxDrawInfo info = { 0 };
1338 info.kind = kHIThemeGrowBoxKindNormal;
1339 info.direction = kThemeGrowRight | kThemeGrowDown;
1340 if ([window styleMask] & NSUtilityWindowMask)
1341 info.size = kHIThemeGrowBoxSizeSmall;
1343 info.size = kHIThemeGrowBoxSizeNormal;
1345 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1346 if (status == noErr)
1348 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1349 NSMinY(contentRect),
1351 bounds.size.height);
1352 process = !NSPointInRect(nspoint, growBox);
1357 unmatchedMouseDowns |= NSEventMaskFromType(type);
1361 NSEventType downType = type - 1;
1362 NSUInteger downMask = NSEventMaskFromType(downType);
1363 process = (unmatchedMouseDowns & downMask) != 0;
1364 unmatchedMouseDowns &= ~downMask;
1369 macdrv_event* event;
1371 event = macdrv_create_event(MOUSE_BUTTON, window);
1372 event->mouse_button.button = [theEvent buttonNumber];
1373 event->mouse_button.pressed = pressed;
1374 event->mouse_button.x = pt.x;
1375 event->mouse_button.y = pt.y;
1376 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1378 [window.queue postEvent:event];
1380 macdrv_release_event(event);
1384 // Since mouse button events deliver absolute cursor position, the
1385 // accumulating delta from move events is invalidated. Make sure
1386 // next mouse move event starts over from an absolute baseline.
1387 // Also, it's at least possible that the title bar widgets (e.g. close
1388 // button, etc.) could enter an internal event loop on a mouse down that
1389 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1390 // dragged events and, after that, any notion of the cursor position
1391 // computed from accumulating deltas would be wrong.
1392 forceNextMouseMoveAbsolute = TRUE;
1395 - (void) handleScrollWheel:(NSEvent*)theEvent
1399 if (mouseCaptureWindow)
1400 window = mouseCaptureWindow;
1402 window = (WineWindow*)[theEvent window];
1404 if ([window isKindOfClass:[WineWindow class]])
1406 CGEventRef cgevent = [theEvent CGEvent];
1407 CGPoint pt = CGEventGetLocation(cgevent);
1411 [self clipCursorLocation:&pt];
1413 if (mouseCaptureWindow)
1417 // Only process the event if it was in the window's content area.
1418 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1419 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1420 process = NSPointInRect(nspoint, contentRect);
1425 macdrv_event* event;
1427 BOOL continuous = FALSE;
1429 event = macdrv_create_event(MOUSE_SCROLL, window);
1430 event->mouse_scroll.x = pt.x;
1431 event->mouse_scroll.y = pt.y;
1432 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1434 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1438 /* Continuous scroll wheel events come from high-precision scrolling
1439 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1440 For these, we can get more precise data from the CGEvent API. */
1441 /* Axis 1 is vertical, axis 2 is horizontal. */
1442 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1443 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1447 double pixelsPerLine = 10;
1448 CGEventSourceRef source;
1450 /* The non-continuous values are in units of "lines", not pixels. */
1451 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1453 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1457 x = pixelsPerLine * [theEvent deltaX];
1458 y = pixelsPerLine * [theEvent deltaY];
1461 /* Mac: negative is right or down, positive is left or up.
1462 Win32: negative is left or down, positive is right or up.
1463 So, negate the X scroll value to translate. */
1466 /* The x,y values so far are in pixels. Win32 expects to receive some
1467 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1468 6 times the pixel value. */
1469 event->mouse_scroll.x_scroll = 6 * x;
1470 event->mouse_scroll.y_scroll = 6 * y;
1474 /* For non-continuous "clicky" wheels, if there was any motion, make
1475 sure there was at least WHEEL_DELTA motion. This is so, at slow
1476 speeds where the system's acceleration curve is actually reducing the
1477 scroll distance, the user is sure to get some action out of each click.
1478 For example, this is important for rotating though weapons in a
1479 first-person shooter. */
1480 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1481 event->mouse_scroll.x_scroll = 120;
1482 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1483 event->mouse_scroll.x_scroll = -120;
1485 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1486 event->mouse_scroll.y_scroll = 120;
1487 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1488 event->mouse_scroll.y_scroll = -120;
1491 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1492 [window.queue postEvent:event];
1494 macdrv_release_event(event);
1496 // Since scroll wheel events deliver absolute cursor position, the
1497 // accumulating delta from move events is invalidated. Make sure next
1498 // mouse move event starts over from an absolute baseline.
1499 forceNextMouseMoveAbsolute = TRUE;
1504 // Returns TRUE if the event was handled and caller should do nothing more
1505 // with it. Returns FALSE if the caller should process it as normal and
1506 // then call -didSendEvent:.
1507 - (BOOL) handleEvent:(NSEvent*)anEvent
1510 NSEventType type = [anEvent type];
1512 if (type == NSFlagsChanged)
1513 self.lastFlagsChanged = anEvent;
1514 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1515 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1517 [self handleMouseMove:anEvent];
1518 ret = mouseCaptureWindow != nil;
1520 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1521 type == NSRightMouseDown || type == NSRightMouseUp ||
1522 type == NSOtherMouseDown || type == NSOtherMouseUp)
1524 [self handleMouseButton:anEvent];
1525 ret = mouseCaptureWindow != nil;
1527 else if (type == NSScrollWheel)
1529 [self handleScrollWheel:anEvent];
1530 ret = mouseCaptureWindow != nil;
1536 - (void) didSendEvent:(NSEvent*)anEvent
1538 NSEventType type = [anEvent type];
1540 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1542 NSUInteger modifiers = [anEvent modifierFlags];
1543 if ((modifiers & NSCommandKeyMask) &&
1544 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1546 // Command-Tab and Command-Shift-Tab would normally be intercepted
1547 // by the system to switch applications. If we're seeing it, it's
1548 // presumably because we've captured the displays, preventing
1549 // normal application switching. Do it manually.
1550 [self handleCommandTab];
1555 - (void) setupObservations
1557 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1559 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1562 usingBlock:^(NSNotification *note){
1563 NSWindow* window = [note object];
1564 [keyWindows removeObjectIdenticalTo:window];
1565 [keyWindows insertObject:window atIndex:0];
1568 [nc addObserverForName:NSWindowWillCloseNotification
1570 queue:[NSOperationQueue mainQueue]
1571 usingBlock:^(NSNotification *note){
1572 NSWindow* window = [note object];
1573 [keyWindows removeObjectIdenticalTo:window];
1574 [orderedWineWindows removeObjectIdenticalTo:window];
1575 if (window == lastTargetWindow)
1576 lastTargetWindow = nil;
1577 if (window == self.mouseCaptureWindow)
1578 self.mouseCaptureWindow = nil;
1581 [nc addObserver:self
1582 selector:@selector(keyboardSelectionDidChange)
1583 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1586 /* The above notification isn't sent unless the NSTextInputContext
1587 class has initialized itself. Poke it. */
1588 [NSTextInputContext self];
1591 - (BOOL) inputSourceIsInputMethod
1593 if (!inputSourceIsInputMethodValid)
1595 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1598 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1599 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1600 CFRelease(inputSource);
1603 inputSourceIsInputMethod = FALSE;
1604 inputSourceIsInputMethodValid = TRUE;
1607 return inputSourceIsInputMethod;
1612 * ---------- NSApplicationDelegate methods ----------
1614 - (void)applicationDidBecomeActive:(NSNotification *)notification
1616 [self activateCursorClipping];
1618 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1619 WineWindow* window = obj;
1620 if ([window levelWhenActive] != [window level])
1621 [window setLevel:[window levelWhenActive]];
1624 // If a Wine process terminates abruptly while it has the display captured
1625 // and switched to a different resolution, Mac OS X will uncapture the
1626 // displays and switch their resolutions back. However, the other Wine
1627 // processes won't have their notion of the desktop rect changed back.
1628 // This can lead them to refuse to draw or acknowledge clicks in certain
1629 // portions of their windows.
1631 // To solve this, we synthesize a displays-changed event whenever we're
1632 // activated. This will provoke a re-synchronization of Wine's notion of
1633 // the desktop rect with the actual state.
1634 [self sendDisplaysChanged:TRUE];
1636 // The cursor probably moved while we were inactive. Accumulated mouse
1637 // movement deltas are invalidated. Make sure the next mouse move event
1638 // starts over from an absolute baseline.
1639 forceNextMouseMoveAbsolute = TRUE;
1642 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1644 primaryScreenHeightValid = FALSE;
1645 [self sendDisplaysChanged:FALSE];
1647 // When the display configuration changes, the cursor position may jump.
1648 // Accumulated mouse movement deltas are invalidated. Make sure the next
1649 // mouse move event starts over from an absolute baseline.
1650 forceNextMouseMoveAbsolute = TRUE;
1653 - (void)applicationDidResignActive:(NSNotification *)notification
1655 macdrv_event* event;
1656 WineEventQueue* queue;
1658 [self invalidateGotFocusEvents];
1660 event = macdrv_create_event(APP_DEACTIVATED, nil);
1662 [eventQueuesLock lock];
1663 for (queue in eventQueues)
1664 [queue postEvent:event];
1665 [eventQueuesLock unlock];
1667 macdrv_release_event(event);
1670 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1672 NSApplicationTerminateReply ret = NSTerminateNow;
1673 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1674 NSAppleEventDescriptor* desc = [m currentAppleEvent];
1675 macdrv_event* event;
1676 WineEventQueue* queue;
1678 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1680 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1683 case kAEReallyLogOut:
1684 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1686 case kAEShowRestartDialog:
1687 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1689 case kAEShowShutdownDialog:
1690 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1693 event->app_quit_requested.reason = QUIT_REASON_NONE;
1697 [eventQueuesLock lock];
1699 if ([eventQueues count])
1701 for (queue in eventQueues)
1702 [queue postEvent:event];
1703 ret = NSTerminateLater;
1706 [eventQueuesLock unlock];
1708 macdrv_release_event(event);
1713 - (void)applicationWillResignActive:(NSNotification *)notification
1715 [self deactivateCursorClipping];
1717 [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1718 WineWindow* window = obj;
1719 NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1720 if ([window level] > level)
1721 [window setLevel:level];
1725 /***********************************************************************
1728 * Run-loop-source perform callback. Pull request blocks from the
1729 * array of queued requests and invoke them.
1731 static void PerformRequest(void *info)
1733 WineApplicationController* controller = [WineApplicationController sharedController];
1737 __block dispatch_block_t block;
1739 dispatch_sync(controller->requestsManipQueue, ^{
1740 if ([controller->requests count])
1742 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1743 [controller->requests removeObjectAtIndex:0];
1757 /***********************************************************************
1760 * Run a block on the main thread asynchronously.
1762 void OnMainThreadAsync(dispatch_block_t block)
1764 WineApplicationController* controller = [WineApplicationController sharedController];
1766 block = [block copy];
1767 dispatch_sync(controller->requestsManipQueue, ^{
1768 [controller->requests addObject:block];
1771 CFRunLoopSourceSignal(controller->requestSource);
1772 CFRunLoopWakeUp(CFRunLoopGetMain());
1777 /***********************************************************************
1780 void LogError(const char* func, NSString* format, ...)
1783 va_start(args, format);
1784 LogErrorv(func, format, args);
1788 /***********************************************************************
1791 void LogErrorv(const char* func, NSString* format, va_list args)
1793 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1795 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1796 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1802 /***********************************************************************
1803 * macdrv_window_rejected_focus
1805 * Pass focus to the next window that hasn't already rejected this same
1806 * WINDOW_GOT_FOCUS event.
1808 void macdrv_window_rejected_focus(const macdrv_event *event)
1811 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1815 /***********************************************************************
1816 * macdrv_get_keyboard_layout
1818 * Returns the keyboard layout uchr data.
1820 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1822 __block CFDataRef result = NULL;
1825 TISInputSourceRef inputSource;
1827 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1830 CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1831 kTISPropertyUnicodeKeyLayoutData);
1832 result = CFDataCreateCopy(NULL, uchr);
1833 CFRelease(inputSource);
1835 *keyboard_type = [WineApplicationController sharedController].keyboardType;
1836 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1843 /***********************************************************************
1846 * Play the beep sound configured by the user in System Preferences.
1848 void macdrv_beep(void)
1850 OnMainThreadAsync(^{
1855 /***********************************************************************
1856 * macdrv_set_display_mode
1858 int macdrv_set_display_mode(const struct macdrv_display* display,
1859 CGDisplayModeRef display_mode)
1864 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1870 /***********************************************************************
1875 * If name is non-NULL, it is a selector for a class method on NSCursor
1876 * identifying the cursor to set. In that case, frames is ignored. If
1877 * name is NULL, then frames is used.
1879 * frames is an array of dictionaries. Each dictionary is a frame of
1880 * an animated cursor. Under the key "image" is a CGImage for the
1881 * frame. Under the key "duration" is a CFNumber time interval, in
1882 * seconds, for how long that frame is presented before proceeding to
1883 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
1884 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1885 * This is the hot spot, measured in pixels down and to the right of the
1886 * top-left corner of the image.
1888 * If the array has exactly 1 element, the cursor is static, not
1889 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
1891 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1895 sel = NSSelectorFromString((NSString*)name);
1898 OnMainThreadAsync(^{
1899 WineApplicationController* controller = [WineApplicationController sharedController];
1900 NSCursor* cursor = [NSCursor performSelector:sel];
1901 [controller setCursorWithFrames:nil];
1903 [controller unhideCursor];
1908 NSArray* nsframes = (NSArray*)frames;
1909 if ([nsframes count])
1911 OnMainThreadAsync(^{
1912 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1917 OnMainThreadAsync(^{
1918 WineApplicationController* controller = [WineApplicationController sharedController];
1919 [controller setCursorWithFrames:nil];
1920 [controller hideCursor];
1926 /***********************************************************************
1927 * macdrv_get_cursor_position
1929 * Obtains the current cursor position. Returns zero on failure,
1930 * non-zero on success.
1932 int macdrv_get_cursor_position(CGPoint *pos)
1935 NSPoint location = [NSEvent mouseLocation];
1936 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1937 *pos = NSPointToCGPoint(location);
1943 /***********************************************************************
1944 * macdrv_set_cursor_position
1946 * Sets the cursor position without generating events. Returns zero on
1947 * failure, non-zero on success.
1949 int macdrv_set_cursor_position(CGPoint pos)
1954 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1960 /***********************************************************************
1961 * macdrv_clip_cursor
1963 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
1964 * to or larger than the whole desktop region, the cursor is unclipped.
1965 * Returns zero on failure, non-zero on success.
1967 int macdrv_clip_cursor(CGRect rect)
1972 WineApplicationController* controller = [WineApplicationController sharedController];
1973 BOOL clipping = FALSE;
1975 if (!CGRectIsInfinite(rect))
1977 NSRect nsrect = NSRectFromCGRect(rect);
1980 /* Convert the rectangle from top-down coords to bottom-up. */
1981 [controller flipRect:&nsrect];
1984 for (screen in [NSScreen screens])
1986 if (!NSContainsRect(nsrect, [screen frame]))
1995 ret = [controller startClippingCursor:rect];
1997 ret = [controller stopClippingCursor];
2003 /***********************************************************************
2004 * macdrv_set_application_icon
2006 * Set the application icon. The images array contains CGImages. If
2007 * there are more than one, then they represent different sizes or
2008 * color depths from the icon resource. If images is NULL or empty,
2009 * restores the default application image.
2011 void macdrv_set_application_icon(CFArrayRef images)
2013 NSArray* imageArray = (NSArray*)images;
2015 OnMainThreadAsync(^{
2016 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2020 /***********************************************************************
2023 void macdrv_quit_reply(int reply)
2026 [NSApp replyToApplicationShouldTerminate:reply];
2030 /***********************************************************************
2031 * macdrv_using_input_method
2033 int macdrv_using_input_method(void)
2038 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2044 /***********************************************************************
2045 * macdrv_set_mouse_capture_window
2047 void macdrv_set_mouse_capture_window(macdrv_window window)
2049 WineWindow* w = (WineWindow*)window;
2052 [[WineApplicationController sharedController] setMouseCaptureWindow:w];