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 /***********************************************************************
38 * Look up a localized string by its ID in the dictionary.
40 static NSString* WineLocalizedString(unsigned int stringID)
42 NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
43 return [(NSDictionary*)localized_strings objectForKey:key];
47 @implementation WineApplication
49 @synthesize wineController;
51 - (void) sendEvent:(NSEvent*)anEvent
53 if (![wineController handleEvent:anEvent])
55 [super sendEvent:anEvent];
56 [wineController didSendEvent:anEvent];
60 - (void) setWineController:(WineApplicationController*)newController
62 wineController = newController;
63 [self setDelegate:wineController];
69 @interface WarpRecord : NSObject
71 CGEventTimestamp timeBefore, timeAfter;
75 @property (nonatomic) CGEventTimestamp timeBefore;
76 @property (nonatomic) CGEventTimestamp timeAfter;
77 @property (nonatomic) CGPoint from;
78 @property (nonatomic) CGPoint to;
83 @implementation WarpRecord
85 @synthesize timeBefore, timeAfter, from, to;
90 @interface WineApplicationController ()
92 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
93 @property (copy, nonatomic) NSArray* cursorFrames;
94 @property (retain, nonatomic) NSTimer* cursorTimer;
95 @property (retain, nonatomic) NSCursor* cursor;
96 @property (retain, nonatomic) NSImage* applicationIcon;
97 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
98 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
100 - (void) setupObservations;
101 - (void) applicationDidBecomeActive:(NSNotification *)notification;
103 static void PerformRequest(void *info);
108 @implementation WineApplicationController
110 @synthesize keyboardType, lastFlagsChanged;
111 @synthesize applicationIcon;
112 @synthesize cursorFrames, cursorTimer, cursor;
113 @synthesize mouseCaptureWindow;
115 @synthesize clippingCursor;
119 if (self == [WineApplicationController class])
121 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
122 @"", @"NSQuotedKeystrokeBinding",
123 @"", @"NSRepeatCountBinding",
124 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
126 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
130 + (WineApplicationController*) sharedController
132 static WineApplicationController* sharedController;
133 static dispatch_once_t once;
135 dispatch_once(&once, ^{
136 sharedController = [[self alloc] init];
139 return sharedController;
147 CFRunLoopSourceContext context = { 0 };
148 context.perform = PerformRequest;
149 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
155 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
156 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
158 requests = [[NSMutableArray alloc] init];
159 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
161 eventQueues = [[NSMutableArray alloc] init];
162 eventQueuesLock = [[NSLock alloc] init];
164 keyWindows = [[NSMutableArray alloc] init];
166 originalDisplayModes = [[NSMutableDictionary alloc] init];
167 latentDisplayModes = [[NSMutableDictionary alloc] init];
169 warpRecords = [[NSMutableArray alloc] init];
171 windowsBeingDragged = [[NSMutableSet alloc] init];
173 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
174 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
180 [self setupObservations];
182 keyboardType = LMGetKbdType();
184 if ([NSApp isActive])
185 [self applicationDidBecomeActive:nil];
192 [windowsBeingDragged release];
194 [screenFrameCGRects release];
195 [applicationIcon release];
196 [warpRecords release];
197 [cursorTimer release];
198 [cursorFrames release];
199 [latentDisplayModes release];
200 [originalDisplayModes release];
201 [keyWindows release];
202 [eventQueues release];
203 [eventQueuesLock release];
204 if (requestsManipQueue) dispatch_release(requestsManipQueue);
208 CFRunLoopSourceInvalidate(requestSource);
209 CFRelease(requestSource);
214 - (void) transformProcessToForeground
216 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
220 NSString* bundleName;
224 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
225 [NSApp activateIgnoringOtherApps:YES];
227 mainMenu = [[[NSMenu alloc] init] autorelease];
230 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
231 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
233 if ([bundleName length])
234 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
236 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
237 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
239 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
240 action:@selector(hideOtherApplications:)
242 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
244 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
245 action:@selector(unhideAllApplications:)
248 [submenu addItem:[NSMenuItem separatorItem]];
250 if ([bundleName length])
251 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
253 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
254 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
255 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
256 item = [[[NSMenuItem alloc] init] autorelease];
257 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
258 [item setSubmenu:submenu];
259 [mainMenu addItem:item];
262 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
263 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
264 action:@selector(performMiniaturize:)
266 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
267 action:@selector(performZoom:)
269 if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
271 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
272 action:@selector(toggleFullScreen:)
274 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
276 [submenu addItem:[NSMenuItem separatorItem]];
277 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
278 action:@selector(arrangeInFront:)
280 item = [[[NSMenuItem alloc] init] autorelease];
281 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
282 [item setSubmenu:submenu];
283 [mainMenu addItem:item];
285 [NSApp setMainMenu:mainMenu];
286 [NSApp setWindowsMenu:submenu];
288 [NSApp setApplicationIconImage:self.applicationIcon];
292 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
294 PerformRequest(NULL);
300 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
301 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
303 inMode:NSDefaultRunLoopMode
306 [NSApp sendEvent:event];
310 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
311 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
316 - (BOOL) registerEventQueue:(WineEventQueue*)queue
318 [eventQueuesLock lock];
319 [eventQueues addObject:queue];
320 [eventQueuesLock unlock];
324 - (void) unregisterEventQueue:(WineEventQueue*)queue
326 [eventQueuesLock lock];
327 [eventQueues removeObjectIdenticalTo:queue];
328 [eventQueuesLock unlock];
331 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
333 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
336 - (double) ticksForEventTime:(NSTimeInterval)eventTime
338 return (eventTime + eventTimeAdjustment) * 1000;
341 /* Invalidate old focus offers across all queues. */
342 - (void) invalidateGotFocusEvents
344 WineEventQueue* queue;
348 [eventQueuesLock lock];
349 for (queue in eventQueues)
351 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
354 [eventQueuesLock unlock];
357 - (void) windowGotFocus:(WineWindow*)window
361 [self invalidateGotFocusEvents];
363 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
364 event->window_got_focus.serial = windowFocusSerial;
366 event->window_got_focus.tried_windows = [triedWindows retain];
368 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
369 [window.queue postEvent:event];
370 macdrv_release_event(event);
373 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
375 if (event->window_got_focus.serial == windowFocusSerial)
377 NSMutableArray* windows = [keyWindows mutableCopy];
378 NSNumber* windowNumber;
381 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
383 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
384 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
385 ![windows containsObject:window])
386 [windows addObject:window];
389 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
390 [triedWindows addObject:(WineWindow*)event->window];
391 for (window in windows)
393 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
395 [window makeKeyWindow];
404 - (void) keyboardSelectionDidChange
406 TISInputSourceRef inputSourceLayout;
408 inputSourceIsInputMethodValid = FALSE;
410 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
411 if (inputSourceLayout)
414 uchr = TISGetInputSourceProperty(inputSourceLayout,
415 kTISPropertyUnicodeKeyLayoutData);
419 WineEventQueue* queue;
421 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
422 event->keyboard_changed.keyboard_type = self.keyboardType;
423 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
424 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
425 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
427 if (event->keyboard_changed.uchr)
429 [eventQueuesLock lock];
431 for (queue in eventQueues)
432 [queue postEvent:event];
434 [eventQueuesLock unlock];
437 macdrv_release_event(event);
440 CFRelease(inputSourceLayout);
444 - (void) enabledKeyboardInputSourcesChanged
446 macdrv_layout_list_needs_update = TRUE;
449 - (CGFloat) primaryScreenHeight
451 if (!primaryScreenHeightValid)
453 NSArray* screens = [NSScreen screens];
454 NSUInteger count = [screens count];
461 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
462 primaryScreenHeightValid = TRUE;
464 size = count * sizeof(CGRect);
465 if (!screenFrameCGRects)
466 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
468 [screenFrameCGRects setLength:size];
470 rect = [screenFrameCGRects mutableBytes];
471 for (screen in screens)
473 CGRect temp = NSRectToCGRect([screen frame]);
474 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
479 return 1280; /* arbitrary value */
482 return primaryScreenHeight;
485 - (NSPoint) flippedMouseLocation:(NSPoint)point
487 /* This relies on the fact that Cocoa's mouse location points are
488 actually off by one (precisely because they were flipped from
489 Quartz screen coordinates using this same technique). */
490 point.y = [self primaryScreenHeight] - point.y;
494 - (void) flipRect:(NSRect*)rect
496 // We don't use -primaryScreenHeight here so there's no chance of having
497 // out-of-date cached info. This method is called infrequently enough
498 // that getting the screen height each time is not prohibitively expensive.
499 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
502 - (WineWindow*) frontWineWindow
504 NSNumber* windowNumber;
505 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
507 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
508 if ([window isKindOfClass:[WineWindow class]] && [window screen])
509 return (WineWindow*)window;
515 - (void) adjustWindowLevels:(BOOL)active
517 NSArray* windowNumbers;
518 NSMutableArray* wineWindows;
519 NSNumber* windowNumber;
520 NSUInteger nextFloatingIndex = 0;
521 __block NSInteger maxLevel = NSIntegerMin;
522 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
523 __block WineWindow* prev = nil;
526 if ([NSApp isHidden]) return;
528 windowNumbers = [NSWindow windowNumbersWithOptions:0];
529 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
531 // For the most part, we rely on the window server's ordering of the windows
532 // to be authoritative. The one exception is if the "floating" property of
533 // one of the windows has been changed, it may be in the wrong level and thus
534 // in the order. This method is what's supposed to fix that up. So build
535 // a list of Wine windows sorted first by floating-ness and then by order
536 // as indicated by the window server.
537 for (windowNumber in windowNumbers)
539 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
540 if ([window isKindOfClass:[WineWindow class]])
543 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
545 [wineWindows addObject:window];
549 NSDisableScreenUpdates();
551 // Go from back to front so that all windows in front of one which is
552 // elevated for full-screen are also elevated.
553 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
554 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
555 WineWindow* window = (WineWindow*)obj;
556 NSInteger origLevel = [window level];
557 NSInteger newLevel = [window minimumLevelForActive:active];
559 if (newLevel < maxLevel)
564 if (!window.floating && maxNonfloatingLevel < newLevel)
565 maxNonfloatingLevel = newLevel;
567 if (newLevel != origLevel)
569 [window setLevel:newLevel];
571 // -setLevel: puts the window at the front of its new level. If
572 // we decreased the level, that's good (it was in front of that
573 // level before, so it should still be now). But if we increased
574 // the level, the window should be toward the back (but still
575 // ahead of the previous windows we did this to).
576 if (origLevel < newLevel)
579 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
581 [window orderBack:nil];
588 NSEnableScreenUpdates();
590 [wineWindows release];
592 // The above took care of the visible windows on the current space. That
593 // leaves windows on other spaces, minimized windows, and windows which
594 // are not ordered in. We want to leave windows on other spaces alone
595 // so the space remains just as they left it (when viewed in Exposé or
596 // Mission Control, for example). We'll adjust the window levels again
597 // after we switch to another space, anyway. Windows which aren't
598 // ordered in will be handled when we order them in. Minimized windows
599 // on the current space should be set to the level they would have gotten
600 // if they were at the front of the windows with the same floating-ness,
601 // because that's where they'll go if/when they are unminimized. Again,
602 // for good measure we'll adjust window levels again when a window is
604 for (window in [NSApp windows])
606 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
607 [window isOnActiveSpace])
609 NSInteger origLevel = [window level];
610 NSInteger newLevel = [window minimumLevelForActive:YES];
611 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
613 if (newLevel < maxLevelForType)
614 newLevel = maxLevelForType;
616 if (newLevel != origLevel)
617 [window setLevel:newLevel];
622 - (void) adjustWindowLevels
624 [self adjustWindowLevels:[NSApp isActive]];
627 - (void) updateFullscreenWindows
629 if (capture_displays_for_fullscreen && [NSApp isActive])
631 BOOL anyFullscreen = FALSE;
632 NSNumber* windowNumber;
633 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
635 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
636 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
638 anyFullscreen = TRUE;
645 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
646 displaysCapturedForFullscreen = TRUE;
648 else if (displaysCapturedForFullscreen)
650 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
651 displaysCapturedForFullscreen = FALSE;
656 - (void) activeSpaceDidChange
658 [self updateFullscreenWindows];
659 [self adjustWindowLevels];
662 - (void) sendDisplaysChanged:(BOOL)activating
665 WineEventQueue* queue;
667 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
668 event->displays_changed.activating = activating;
670 [eventQueuesLock lock];
672 // If we're activating, then we just need one of our threads to get the
673 // event, so it can send it directly to the desktop window. Otherwise,
674 // we need all of the threads to get it because we don't know which owns
675 // the desktop window and only that one will do anything with it.
676 if (activating) event->deliver = 1;
678 for (queue in eventQueues)
679 [queue postEvent:event];
680 [eventQueuesLock unlock];
682 macdrv_release_event(event);
685 // We can compare two modes directly using CFEqual, but that may require that
686 // they are identical to a level that we don't need. In particular, when the
687 // OS switches between the integrated and discrete GPUs, the set of display
688 // modes can change in subtle ways. We're interested in whether two modes
689 // match in their most salient features, even if they aren't identical.
690 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
692 NSString *encoding1, *encoding2;
693 uint32_t ioflags1, ioflags2, different;
694 double refresh1, refresh2;
696 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
697 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
699 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
700 if (CGDisplayModeGetPixelWidth != NULL &&
701 CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
702 if (CGDisplayModeGetPixelHeight != NULL &&
703 CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
706 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
707 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
708 if (![encoding1 isEqualToString:encoding2]) return FALSE;
710 ioflags1 = CGDisplayModeGetIOFlags(mode1);
711 ioflags2 = CGDisplayModeGetIOFlags(mode2);
712 different = ioflags1 ^ ioflags2;
713 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
714 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
717 refresh1 = CGDisplayModeGetRefreshRate(mode1);
718 if (refresh1 == 0) refresh1 = 60;
719 refresh2 = CGDisplayModeGetRefreshRate(mode2);
720 if (refresh2 == 0) refresh2 = 60;
721 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
726 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
728 NSMutableArray* ret = [NSMutableArray array];
729 NSDictionary* options = nil;
731 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
732 if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
733 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
734 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
737 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
738 for (id candidateModeObject in modes)
740 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
741 if ([self mode:candidateMode matchesMode:mode])
742 [ret addObject:candidateModeObject];
747 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
750 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
751 CGDisplayModeRef originalMode;
753 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
755 if (originalMode && [self mode:mode matchesMode:originalMode])
757 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
759 CGRestorePermanentDisplayConfiguration();
760 if (!displaysCapturedForFullscreen)
761 CGReleaseAllDisplays();
762 [originalDisplayModes removeAllObjects];
765 else // ... otherwise, try to restore just the one display
767 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
769 mode = (CGDisplayModeRef)modeObject;
770 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
772 [originalDisplayModes removeObjectForKey:displayIDKey];
781 BOOL active = [NSApp isActive];
782 CGDisplayModeRef currentMode;
785 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
787 currentMode = CGDisplayCopyDisplayMode(displayID);
788 if (!currentMode) // Invalid display ID
791 if ([self mode:mode matchesMode:currentMode]) // Already there!
793 CGDisplayModeRelease(currentMode);
797 CGDisplayModeRelease(currentMode);
800 modes = [self modesMatchingMode:mode forDisplay:displayID];
804 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
805 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
809 // If we get here, we have the displays captured. If we don't
810 // know the original mode of the display, the current mode must
811 // be the original. We should re-query the current mode since
812 // another process could have changed it between when we last
813 // checked and when we captured the displays.
815 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
819 for (id modeObject in modes)
821 mode = (CGDisplayModeRef)modeObject;
822 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
829 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
830 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
831 else if (![originalDisplayModes count])
833 CGRestorePermanentDisplayConfiguration();
834 if (!displaysCapturedForFullscreen)
835 CGReleaseAllDisplays();
839 CGDisplayModeRelease(currentMode);
843 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
850 [self adjustWindowLevels];
855 - (BOOL) areDisplaysCaptured
857 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
860 - (void) updateCursor:(BOOL)force
862 if (force || lastTargetWindow)
864 if (clientWantsCursorHidden && !cursorHidden)
870 if (!cursorIsCurrent)
873 cursorIsCurrent = TRUE;
876 if (!clientWantsCursorHidden && cursorHidden)
879 cursorHidden = FALSE;
886 [[NSCursor arrowCursor] set];
887 cursorIsCurrent = FALSE;
892 cursorHidden = FALSE;
899 if (!clientWantsCursorHidden)
901 clientWantsCursorHidden = TRUE;
902 [self updateCursor:TRUE];
906 - (void) unhideCursor
908 if (clientWantsCursorHidden)
910 clientWantsCursorHidden = FALSE;
911 [self updateCursor:FALSE];
915 - (void) setCursor:(NSCursor*)newCursor
917 if (newCursor != cursor)
920 cursor = [newCursor retain];
921 cursorIsCurrent = FALSE;
922 [self updateCursor:FALSE];
928 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
929 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
930 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
931 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
934 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
935 hotSpot = CGPointZero;
936 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
941 - (void) nextCursorFrame:(NSTimer*)theTimer
944 NSTimeInterval duration;
948 if (cursorFrame >= [cursorFrames count])
952 frame = [cursorFrames objectAtIndex:cursorFrame];
953 duration = [[frame objectForKey:@"duration"] doubleValue];
954 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
955 [cursorTimer setFireDate:date];
958 - (void) setCursorWithFrames:(NSArray*)frames
960 if (self.cursorFrames == frames)
963 self.cursorFrames = frames;
965 [cursorTimer invalidate];
966 self.cursorTimer = nil;
970 if ([frames count] > 1)
972 NSDictionary* frame = [frames objectAtIndex:0];
973 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
974 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
975 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
978 selector:@selector(nextCursorFrame:)
980 repeats:YES] autorelease];
981 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
988 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
990 NSImage* nsimage = nil;
994 NSSize bestSize = NSZeroSize;
997 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
999 for (image in images)
1001 CGImageRef cgimage = (CGImageRef)image;
1002 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1005 NSSize size = [imageRep size];
1007 [nsimage addRepresentation:imageRep];
1010 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1015 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1016 [nsimage setSize:bestSize];
1021 self.applicationIcon = nsimage;
1024 - (void) handleCommandTab
1026 if ([NSApp isActive])
1028 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1029 NSRunningApplication* app;
1030 NSRunningApplication* otherValidApp = nil;
1032 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1034 NSNumber* displayID;
1035 for (displayID in originalDisplayModes)
1037 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1038 [latentDisplayModes setObject:(id)mode forKey:displayID];
1039 CGDisplayModeRelease(mode);
1042 CGRestorePermanentDisplayConfiguration();
1043 CGReleaseAllDisplays();
1044 [originalDisplayModes removeAllObjects];
1045 displaysCapturedForFullscreen = FALSE;
1048 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1050 if (![app isEqual:thisApp] && !app.terminated &&
1051 app.activationPolicy == NSApplicationActivationPolicyRegular)
1055 // There's another visible app. Just hide ourselves and let
1056 // the system activate the other app.
1062 otherValidApp = app;
1066 // Didn't find a visible GUI app. Try the Finder or, if that's not
1067 // running, the first hidden GUI app. If even that doesn't work, we
1068 // just fail to switch and remain the active app.
1069 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1070 if (!app) app = otherValidApp;
1072 [app activateWithOptions:0];
1077 * ---------- Cursor clipping methods ----------
1079 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1080 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1081 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1082 * general case, we leverage that. We disassociate mouse movements from
1083 * the cursor position and then move the cursor manually, keeping it within
1084 * the clipping rectangle.
1086 * Moving the cursor manually isn't enough. We need to modify the event
1087 * stream so that the events have the new location, too. We need to do
1088 * this at a point before the events enter Cocoa, so that Cocoa will assign
1089 * the correct window to the event. So, we install a Quartz event tap to
1092 * Also, there's a complication when we move the cursor. We use
1093 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1094 * events, but the change of cursor position is incorporated into the
1095 * deltas of the next mouse move event. When the mouse is disassociated
1096 * from the cursor position, we need the deltas to only reflect actual
1097 * device movement, not programmatic changes. So, the event tap cancels
1098 * out the change caused by our calls to CGWarpMouseCursorPosition().
1100 - (void) clipCursorLocation:(CGPoint*)location
1102 if (location->x < CGRectGetMinX(cursorClipRect))
1103 location->x = CGRectGetMinX(cursorClipRect);
1104 if (location->y < CGRectGetMinY(cursorClipRect))
1105 location->y = CGRectGetMinY(cursorClipRect);
1106 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1107 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1108 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1109 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1112 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1114 CGPoint oldLocation;
1116 if (currentLocation)
1117 oldLocation = *currentLocation;
1119 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1121 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1123 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1126 warpRecord.from = oldLocation;
1127 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1129 /* Actually move the cursor. */
1130 err = CGWarpMouseCursorPosition(*newLocation);
1131 if (err != kCGErrorSuccess)
1134 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1135 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1137 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1139 warpRecord.to = *newLocation;
1140 [warpRecords addObject:warpRecord];
1147 - (BOOL) isMouseMoveEventType:(CGEventType)type
1151 case kCGEventMouseMoved:
1152 case kCGEventLeftMouseDragged:
1153 case kCGEventRightMouseDragged:
1154 case kCGEventOtherMouseDragged:
1161 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1163 int warpsFinished = 0;
1164 for (WarpRecord* warpRecord in warpRecords)
1166 if (warpRecord.timeAfter < eventTime ||
1167 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1173 return warpsFinished;
1176 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1177 type:(CGEventType)type
1178 event:(CGEventRef)event
1180 CGEventTimestamp eventTime;
1181 CGPoint eventLocation, cursorLocation;
1183 if (type == kCGEventTapDisabledByUserInput)
1185 if (type == kCGEventTapDisabledByTimeout)
1187 CGEventTapEnable(cursorClippingEventTap, TRUE);
1191 if (!clippingCursor)
1194 eventTime = CGEventGetTimestamp(event);
1195 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1197 eventLocation = CGEventGetLocation(event);
1199 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1201 if ([self isMouseMoveEventType:type])
1203 double deltaX, deltaY;
1204 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1207 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1208 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1210 for (i = 0; i < warpsFinished; i++)
1212 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1213 deltaX -= warpRecord.to.x - warpRecord.from.x;
1214 deltaY -= warpRecord.to.y - warpRecord.from.y;
1215 [warpRecords removeObjectAtIndex:0];
1220 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1221 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1224 synthesizedLocation.x += deltaX;
1225 synthesizedLocation.y += deltaY;
1228 // If the event is destined for another process, don't clip it. This may
1229 // happen if the user activates Exposé or Mission Control. In that case,
1230 // our app does not resign active status, so clipping is still in effect,
1231 // but the cursor should not actually be clipped.
1233 // In addition, the fact that mouse moves may have been delivered to a
1234 // different process means we have to treat the next one we receive as
1235 // absolute rather than relative.
1236 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1237 [self clipCursorLocation:&synthesizedLocation];
1239 lastSetCursorPositionTime = lastEventTapEventTime;
1241 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1242 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1243 CGEventSetLocation(event, synthesizedLocation);
1248 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1249 CGEventRef event, void *refcon)
1251 WineApplicationController* controller = refcon;
1252 return [controller eventTapWithProxy:proxy type:type event:event];
1255 - (BOOL) installEventTap
1257 ProcessSerialNumber psn;
1259 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1260 CGEventMaskBit(kCGEventLeftMouseUp) |
1261 CGEventMaskBit(kCGEventRightMouseDown) |
1262 CGEventMaskBit(kCGEventRightMouseUp) |
1263 CGEventMaskBit(kCGEventMouseMoved) |
1264 CGEventMaskBit(kCGEventLeftMouseDragged) |
1265 CGEventMaskBit(kCGEventRightMouseDragged) |
1266 CGEventMaskBit(kCGEventOtherMouseDown) |
1267 CGEventMaskBit(kCGEventOtherMouseUp) |
1268 CGEventMaskBit(kCGEventOtherMouseDragged) |
1269 CGEventMaskBit(kCGEventScrollWheel);
1270 CFRunLoopSourceRef source;
1272 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1274 if (cursorClippingEventTap)
1277 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1278 // framework with dlsym() because the Win32 function of the same name
1280 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1284 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1285 if (!pGetCurrentProcess)
1287 dlclose(appServices);
1291 err = pGetCurrentProcess(&psn);
1292 dlclose(appServices);
1296 // We create an annotated session event tap rather than a process-specific
1297 // event tap because we need to programmatically move the cursor even when
1298 // mouse moves are directed to other processes. We disable our tap when
1299 // other processes are active, but things like Exposé are handled by other
1300 // processes even when we remain active.
1301 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1302 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1303 if (!cursorClippingEventTap)
1306 CGEventTapEnable(cursorClippingEventTap, FALSE);
1308 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1311 CFRelease(cursorClippingEventTap);
1312 cursorClippingEventTap = NULL;
1316 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1321 - (BOOL) setCursorPosition:(CGPoint)pos
1325 if ([windowsBeingDragged count])
1327 else if (clippingCursor)
1329 [self clipCursorLocation:&pos];
1331 ret = [self warpCursorTo:&pos from:NULL];
1332 synthesizedLocation = pos;
1335 // We want to discard mouse-move events that have already been
1336 // through the event tap, because it's too late to account for
1337 // the setting of the cursor position with them. However, the
1338 // events that may be queued with times after that but before
1339 // the above warp can still be used. So, use the last event
1340 // tap event time so that -sendEvent: doesn't discard them.
1341 lastSetCursorPositionTime = lastEventTapEventTime;
1346 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1347 // the mouse from the cursor position for 0.25 seconds. This means
1348 // that mouse movement during that interval doesn't move the cursor
1349 // and events carry a constant location (the warped-to position)
1350 // even though they have delta values. For apps which warp the
1351 // cursor frequently (like after every mouse move), this makes
1352 // cursor movement horribly laggy and jerky, as only a fraction of
1353 // mouse move events have any effect.
1355 // On some versions of OS X, it's sufficient to forcibly reassociate
1356 // the mouse and cursor position. On others, it's necessary to set
1357 // the local events suppression interval to 0 for the warp. That's
1358 // deprecated, but I'm not aware of any other way. For good
1359 // measure, we do both.
1360 CGSetLocalEventsSuppressionInterval(0);
1361 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1362 CGSetLocalEventsSuppressionInterval(0.25);
1365 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1367 CGAssociateMouseAndMouseCursorPosition(true);
1373 WineEventQueue* queue;
1375 // Discard all pending mouse move events.
1376 [eventQueuesLock lock];
1377 for (queue in eventQueues)
1379 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1380 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1382 [queue resetMouseEventPositions:pos];
1384 [eventQueuesLock unlock];
1390 - (void) activateCursorClipping
1392 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1394 CGEventTapEnable(cursorClippingEventTap, TRUE);
1395 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1399 - (void) deactivateCursorClipping
1401 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1403 CGEventTapEnable(cursorClippingEventTap, FALSE);
1404 [warpRecords removeAllObjects];
1405 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1409 - (void) updateCursorClippingState
1411 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1412 [self activateCursorClipping];
1414 [self deactivateCursorClipping];
1417 - (void) updateWindowsForCursorClipping
1420 for (window in [NSApp windows])
1422 if ([window isKindOfClass:[WineWindow class]])
1423 [window updateForCursorClipping];
1427 - (BOOL) startClippingCursor:(CGRect)rect
1431 if (!cursorClippingEventTap && ![self installEventTap])
1434 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1435 CGEventTapIsEnabled(cursorClippingEventTap))
1438 err = CGAssociateMouseAndMouseCursorPosition(false);
1439 if (err != kCGErrorSuccess)
1442 clippingCursor = TRUE;
1443 cursorClipRect = rect;
1444 [self updateCursorClippingState];
1445 [self updateWindowsForCursorClipping];
1450 - (BOOL) stopClippingCursor
1452 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1453 if (err != kCGErrorSuccess)
1456 clippingCursor = FALSE;
1457 [self updateCursorClippingState];
1458 [self updateWindowsForCursorClipping];
1463 - (BOOL) isKeyPressed:(uint16_t)keyCode
1465 int bits = sizeof(pressedKeyCodes[0]) * 8;
1466 int index = keyCode / bits;
1467 uint32_t mask = 1 << (keyCode % bits);
1468 return (pressedKeyCodes[index] & mask) != 0;
1471 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1473 int bits = sizeof(pressedKeyCodes[0]) * 8;
1474 int index = keyCode / bits;
1475 uint32_t mask = 1 << (keyCode % bits);
1477 pressedKeyCodes[index] |= mask;
1479 pressedKeyCodes[index] &= ~mask;
1482 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1485 [windowsBeingDragged addObject:window];
1487 [windowsBeingDragged removeObject:window];
1488 [self updateCursorClippingState];
1491 - (void) handleMouseMove:(NSEvent*)anEvent
1493 WineWindow* targetWindow;
1494 BOOL drag = [anEvent type] != NSMouseMoved;
1496 if ([windowsBeingDragged count])
1498 else if (mouseCaptureWindow)
1499 targetWindow = mouseCaptureWindow;
1501 targetWindow = (WineWindow*)[anEvent window];
1504 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1505 event indicates its window is the main window, even if the cursor is
1506 over a different window. Find the actual WineWindow that is under the
1507 cursor and post the event as being for that window. */
1508 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1509 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1510 NSInteger windowUnderNumber;
1512 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1513 belowWindowWithWindowNumber:0];
1514 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1515 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1519 if ([targetWindow isKindOfClass:[WineWindow class]])
1521 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1522 macdrv_event* event;
1525 // If we recently warped the cursor (other than in our cursor-clipping
1526 // event tap), discard mouse move events until we see an event which is
1527 // later than that time.
1528 if (lastSetCursorPositionTime)
1530 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1533 lastSetCursorPositionTime = 0;
1534 forceNextMouseMoveAbsolute = TRUE;
1537 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1540 forceNextMouseMoveAbsolute = FALSE;
1544 // Send absolute move events if the cursor is in the interior of
1545 // its range. Only send relative moves if the cursor is pinned to
1546 // the boundaries of where it can go. We compute the position
1547 // that's one additional point in the direction of movement. If
1548 // that is outside of the clipping rect or desktop region (the
1549 // union of the screen frames), then we figure the cursor would
1550 // have moved outside if it could but it was pinned.
1551 CGPoint computedPoint = point;
1552 CGFloat deltaX = [anEvent deltaX];
1553 CGFloat deltaY = [anEvent deltaY];
1557 else if (deltaX < -0.001)
1562 else if (deltaY < -0.001)
1565 // Assume cursor is pinned for now
1567 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1569 const CGRect* rects;
1570 NSUInteger count, i;
1572 // Caches screenFrameCGRects if necessary
1573 [self primaryScreenHeight];
1575 rects = [screenFrameCGRects bytes];
1576 count = [screenFrameCGRects length] / sizeof(rects[0]);
1578 for (i = 0; i < count; i++)
1580 if (CGRectContainsPoint(rects[i], computedPoint))
1592 [self clipCursorLocation:&point];
1593 point = cgpoint_win_from_mac(point);
1595 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1596 event->mouse_moved.x = floor(point.x);
1597 event->mouse_moved.y = floor(point.y);
1599 mouseMoveDeltaX = 0;
1600 mouseMoveDeltaY = 0;
1604 double scale = retina_on ? 2 : 1;
1606 /* Add event delta to accumulated delta error */
1607 /* deltaY is already flipped */
1608 mouseMoveDeltaX += [anEvent deltaX];
1609 mouseMoveDeltaY += [anEvent deltaY];
1611 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1612 event->mouse_moved.x = mouseMoveDeltaX * scale;
1613 event->mouse_moved.y = mouseMoveDeltaY * scale;
1615 /* Keep the remainder after integer truncation. */
1616 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1617 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1620 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1622 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1623 event->mouse_moved.drag = drag;
1625 [targetWindow.queue postEvent:event];
1628 macdrv_release_event(event);
1630 lastTargetWindow = targetWindow;
1633 lastTargetWindow = nil;
1635 [self updateCursor:FALSE];
1638 - (void) handleMouseButton:(NSEvent*)theEvent
1640 WineWindow* window = (WineWindow*)[theEvent window];
1641 NSEventType type = [theEvent type];
1642 WineWindow* windowBroughtForward = nil;
1643 BOOL process = FALSE;
1645 if ([window isKindOfClass:[WineWindow class]] &&
1646 type == NSLeftMouseDown &&
1647 (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1649 NSWindowButton windowButton;
1651 windowBroughtForward = window;
1653 /* Any left-click on our window anyplace other than the close or
1654 minimize buttons will bring it forward. */
1655 for (windowButton = NSWindowCloseButton;
1656 windowButton <= NSWindowMiniaturizeButton;
1659 NSButton* button = [window standardWindowButton:windowButton];
1662 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1663 if ([button mouse:point inRect:[button bounds]])
1665 windowBroughtForward = nil;
1672 if ([windowsBeingDragged count])
1674 else if (mouseCaptureWindow)
1675 window = mouseCaptureWindow;
1677 if ([window isKindOfClass:[WineWindow class]])
1679 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1680 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1683 [self clipCursorLocation:&pt];
1687 if (mouseCaptureWindow)
1691 // Test if the click was in the window's content area.
1692 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1693 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1694 process = NSMouseInRect(nspoint, contentRect, NO);
1695 if (process && [window styleMask] & NSResizableWindowMask)
1697 // Ignore clicks in the grow box (resize widget).
1698 HIPoint origin = { 0, 0 };
1699 HIThemeGrowBoxDrawInfo info = { 0 };
1703 info.kind = kHIThemeGrowBoxKindNormal;
1704 info.direction = kThemeGrowRight | kThemeGrowDown;
1705 if ([window styleMask] & NSUtilityWindowMask)
1706 info.size = kHIThemeGrowBoxSizeSmall;
1708 info.size = kHIThemeGrowBoxSizeNormal;
1710 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1711 if (status == noErr)
1713 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1714 NSMinY(contentRect),
1716 bounds.size.height);
1717 process = !NSMouseInRect(nspoint, growBox, NO);
1722 unmatchedMouseDowns |= NSEventMaskFromType(type);
1726 NSEventType downType = type - 1;
1727 NSUInteger downMask = NSEventMaskFromType(downType);
1728 process = (unmatchedMouseDowns & downMask) != 0;
1729 unmatchedMouseDowns &= ~downMask;
1734 macdrv_event* event;
1736 pt = cgpoint_win_from_mac(pt);
1738 event = macdrv_create_event(MOUSE_BUTTON, window);
1739 event->mouse_button.button = [theEvent buttonNumber];
1740 event->mouse_button.pressed = pressed;
1741 event->mouse_button.x = floor(pt.x);
1742 event->mouse_button.y = floor(pt.y);
1743 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1745 [window.queue postEvent:event];
1747 macdrv_release_event(event);
1751 if (windowBroughtForward)
1753 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1754 NSInteger ancestorNumber = [ancestor windowNumber];
1755 NSInteger ancestorLevel = [ancestor level];
1757 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1759 NSInteger windowNumber = [windowNumberObject integerValue];
1760 if (windowNumber == ancestorNumber)
1762 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1763 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1764 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1766 [ancestor postBroughtForwardEvent];
1770 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1771 [self windowGotFocus:windowBroughtForward];
1774 // Since mouse button events deliver absolute cursor position, the
1775 // accumulating delta from move events is invalidated. Make sure
1776 // next mouse move event starts over from an absolute baseline.
1777 // Also, it's at least possible that the title bar widgets (e.g. close
1778 // button, etc.) could enter an internal event loop on a mouse down that
1779 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1780 // dragged events and, after that, any notion of the cursor position
1781 // computed from accumulating deltas would be wrong.
1782 forceNextMouseMoveAbsolute = TRUE;
1785 - (void) handleScrollWheel:(NSEvent*)theEvent
1789 if (mouseCaptureWindow)
1790 window = mouseCaptureWindow;
1792 window = (WineWindow*)[theEvent window];
1794 if ([window isKindOfClass:[WineWindow class]])
1796 CGEventRef cgevent = [theEvent CGEvent];
1797 CGPoint pt = CGEventGetLocation(cgevent);
1801 [self clipCursorLocation:&pt];
1803 if (mouseCaptureWindow)
1807 // Only process the event if it was in the window's content area.
1808 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1809 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1810 process = NSMouseInRect(nspoint, contentRect, NO);
1815 macdrv_event* event;
1817 BOOL continuous = FALSE;
1819 pt = cgpoint_win_from_mac(pt);
1821 event = macdrv_create_event(MOUSE_SCROLL, window);
1822 event->mouse_scroll.x = floor(pt.x);
1823 event->mouse_scroll.y = floor(pt.y);
1824 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1826 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1830 /* Continuous scroll wheel events come from high-precision scrolling
1831 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1832 For these, we can get more precise data from the CGEvent API. */
1833 /* Axis 1 is vertical, axis 2 is horizontal. */
1834 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1835 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1839 double pixelsPerLine = 10;
1840 CGEventSourceRef source;
1842 /* The non-continuous values are in units of "lines", not pixels. */
1843 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1845 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1849 x = pixelsPerLine * [theEvent deltaX];
1850 y = pixelsPerLine * [theEvent deltaY];
1853 /* Mac: negative is right or down, positive is left or up.
1854 Win32: negative is left or down, positive is right or up.
1855 So, negate the X scroll value to translate. */
1858 /* The x,y values so far are in pixels. Win32 expects to receive some
1859 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1860 6 times the pixel value. */
1864 if (use_precise_scrolling)
1866 event->mouse_scroll.x_scroll = x;
1867 event->mouse_scroll.y_scroll = y;
1871 /* For non-continuous "clicky" wheels, if there was any motion, make
1872 sure there was at least WHEEL_DELTA motion. This is so, at slow
1873 speeds where the system's acceleration curve is actually reducing the
1874 scroll distance, the user is sure to get some action out of each click.
1875 For example, this is important for rotating though weapons in a
1876 first-person shooter. */
1877 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1878 event->mouse_scroll.x_scroll = 120;
1879 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1880 event->mouse_scroll.x_scroll = -120;
1882 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1883 event->mouse_scroll.y_scroll = 120;
1884 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1885 event->mouse_scroll.y_scroll = -120;
1890 /* If it's been a while since the last scroll event or if the scrolling has
1891 reversed direction, reset the accumulated scroll value. */
1892 if ([theEvent timestamp] - lastScrollTime > 1)
1893 accumScrollX = accumScrollY = 0;
1896 /* The accumulated scroll value is in the opposite direction/sign of the last
1897 scroll. That's because it's the "debt" resulting from over-scrolling in
1898 that direction. We accumulate by adding in the scroll amount and then, if
1899 it has the same sign as the scroll value, we subtract any whole or partial
1900 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1901 scroll direction if the accumulated debt and the new scroll value have the
1903 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1905 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1908 lastScrollTime = [theEvent timestamp];
1913 if (accumScrollX > 0 && x > 0)
1914 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1915 if (accumScrollX < 0 && x < 0)
1916 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1917 if (accumScrollY > 0 && y > 0)
1918 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1919 if (accumScrollY < 0 && y < 0)
1920 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1922 accumScrollX -= event->mouse_scroll.x_scroll;
1923 accumScrollY -= event->mouse_scroll.y_scroll;
1926 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1927 [window.queue postEvent:event];
1929 macdrv_release_event(event);
1931 // Since scroll wheel events deliver absolute cursor position, the
1932 // accumulating delta from move events is invalidated. Make sure next
1933 // mouse move event starts over from an absolute baseline.
1934 forceNextMouseMoveAbsolute = TRUE;
1939 // Returns TRUE if the event was handled and caller should do nothing more
1940 // with it. Returns FALSE if the caller should process it as normal and
1941 // then call -didSendEvent:.
1942 - (BOOL) handleEvent:(NSEvent*)anEvent
1945 NSEventType type = [anEvent type];
1947 if (type == NSFlagsChanged)
1948 self.lastFlagsChanged = anEvent;
1949 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1950 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1952 [self handleMouseMove:anEvent];
1953 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1955 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1956 type == NSRightMouseDown || type == NSRightMouseUp ||
1957 type == NSOtherMouseDown || type == NSOtherMouseUp)
1959 [self handleMouseButton:anEvent];
1960 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1962 else if (type == NSScrollWheel)
1964 [self handleScrollWheel:anEvent];
1965 ret = mouseCaptureWindow != nil;
1967 else if (type == NSKeyUp)
1969 uint16_t keyCode = [anEvent keyCode];
1970 if ([self isKeyPressed:keyCode])
1972 WineWindow* window = (WineWindow*)[anEvent window];
1973 [self noteKey:keyCode pressed:FALSE];
1974 if ([window isKindOfClass:[WineWindow class]])
1975 [window postKeyEvent:anEvent];
1978 else if (type == NSAppKitDefined)
1980 short subtype = [anEvent subtype];
1982 // These subtypes are not documented but they appear to mean
1983 // "a window is being dragged" and "a window is no longer being
1984 // dragged", respectively.
1985 if (subtype == 20 || subtype == 21)
1987 WineWindow* window = (WineWindow*)[anEvent window];
1988 if ([window isKindOfClass:[WineWindow class]])
1990 macdrv_event* event;
1995 [windowsBeingDragged addObject:window];
1996 eventType = WINDOW_DRAG_BEGIN;
2000 [windowsBeingDragged removeObject:window];
2001 eventType = WINDOW_DRAG_END;
2003 [self updateCursorClippingState];
2005 event = macdrv_create_event(eventType, window);
2006 [window.queue postEvent:event];
2007 macdrv_release_event(event);
2015 - (void) didSendEvent:(NSEvent*)anEvent
2017 NSEventType type = [anEvent type];
2019 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2021 NSUInteger modifiers = [anEvent modifierFlags];
2022 if ((modifiers & NSCommandKeyMask) &&
2023 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2025 // Command-Tab and Command-Shift-Tab would normally be intercepted
2026 // by the system to switch applications. If we're seeing it, it's
2027 // presumably because we've captured the displays, preventing
2028 // normal application switching. Do it manually.
2029 [self handleCommandTab];
2034 - (void) setupObservations
2036 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2037 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2038 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2040 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2043 usingBlock:^(NSNotification *note){
2044 NSWindow* window = [note object];
2045 [keyWindows removeObjectIdenticalTo:window];
2046 [keyWindows insertObject:window atIndex:0];
2049 [nc addObserverForName:NSWindowWillCloseNotification
2051 queue:[NSOperationQueue mainQueue]
2052 usingBlock:^(NSNotification *note){
2053 NSWindow* window = [note object];
2054 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2056 [keyWindows removeObjectIdenticalTo:window];
2057 if (window == lastTargetWindow)
2058 lastTargetWindow = nil;
2059 if (window == self.mouseCaptureWindow)
2060 self.mouseCaptureWindow = nil;
2061 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2063 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2064 [self updateFullscreenWindows];
2067 [windowsBeingDragged removeObject:window];
2068 [self updateCursorClippingState];
2071 [nc addObserver:self
2072 selector:@selector(keyboardSelectionDidChange)
2073 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2076 /* The above notification isn't sent unless the NSTextInputContext
2077 class has initialized itself. Poke it. */
2078 [NSTextInputContext self];
2080 [wsnc addObserver:self
2081 selector:@selector(activeSpaceDidChange)
2082 name:NSWorkspaceActiveSpaceDidChangeNotification
2085 [nc addObserver:self
2086 selector:@selector(releaseMouseCapture)
2087 name:NSMenuDidBeginTrackingNotification
2090 [dnc addObserver:self
2091 selector:@selector(releaseMouseCapture)
2092 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2094 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2096 [dnc addObserver:self
2097 selector:@selector(enabledKeyboardInputSourcesChanged)
2098 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2102 - (BOOL) inputSourceIsInputMethod
2104 if (!inputSourceIsInputMethodValid)
2106 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2109 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2110 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2111 CFRelease(inputSource);
2114 inputSourceIsInputMethod = FALSE;
2115 inputSourceIsInputMethodValid = TRUE;
2118 return inputSourceIsInputMethod;
2121 - (void) releaseMouseCapture
2123 // This might be invoked on a background thread by the distributed
2124 // notification center. Shunt it to the main thread.
2125 if (![NSThread isMainThread])
2127 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2131 if (mouseCaptureWindow)
2133 macdrv_event* event;
2135 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2136 [mouseCaptureWindow.queue postEvent:event];
2137 macdrv_release_event(event);
2141 - (void) unminimizeWindowIfNoneVisible
2143 if (![self frontWineWindow])
2145 for (WineWindow* window in [NSApp windows])
2147 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2149 [window deminiaturize:self];
2156 - (void) setRetinaMode:(int)mode
2162 double scale = mode ? 0.5 : 2.0;
2163 cursorClipRect.origin.x *= scale;
2164 cursorClipRect.origin.y *= scale;
2165 cursorClipRect.size.width *= scale;
2166 cursorClipRect.size.height *= scale;
2169 for (WineWindow* window in [NSApp windows])
2171 if ([window isKindOfClass:[WineWindow class]])
2172 [window setRetinaMode:mode];
2178 * ---------- NSApplicationDelegate methods ----------
2180 - (void)applicationDidBecomeActive:(NSNotification *)notification
2182 NSNumber* displayID;
2183 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2185 latentDisplayModes = [[NSMutableDictionary alloc] init];
2186 for (displayID in modesToRealize)
2188 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2189 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2192 [self updateCursorClippingState];
2194 [self updateFullscreenWindows];
2195 [self adjustWindowLevels:YES];
2198 [self unminimizeWindowIfNoneVisible];
2201 // If a Wine process terminates abruptly while it has the display captured
2202 // and switched to a different resolution, Mac OS X will uncapture the
2203 // displays and switch their resolutions back. However, the other Wine
2204 // processes won't have their notion of the desktop rect changed back.
2205 // This can lead them to refuse to draw or acknowledge clicks in certain
2206 // portions of their windows.
2208 // To solve this, we synthesize a displays-changed event whenever we're
2209 // activated. This will provoke a re-synchronization of Wine's notion of
2210 // the desktop rect with the actual state.
2211 [self sendDisplaysChanged:TRUE];
2213 // The cursor probably moved while we were inactive. Accumulated mouse
2214 // movement deltas are invalidated. Make sure the next mouse move event
2215 // starts over from an absolute baseline.
2216 forceNextMouseMoveAbsolute = TRUE;
2219 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2221 primaryScreenHeightValid = FALSE;
2222 [self sendDisplaysChanged:FALSE];
2223 [self adjustWindowLevels];
2225 // When the display configuration changes, the cursor position may jump.
2226 // Accumulated mouse movement deltas are invalidated. Make sure the next
2227 // mouse move event starts over from an absolute baseline.
2228 forceNextMouseMoveAbsolute = TRUE;
2231 - (void)applicationDidResignActive:(NSNotification *)notification
2233 macdrv_event* event;
2234 WineEventQueue* queue;
2236 [self updateCursorClippingState];
2238 [self invalidateGotFocusEvents];
2240 event = macdrv_create_event(APP_DEACTIVATED, nil);
2242 [eventQueuesLock lock];
2243 for (queue in eventQueues)
2244 [queue postEvent:event];
2245 [eventQueuesLock unlock];
2247 macdrv_release_event(event);
2249 [self releaseMouseCapture];
2252 - (void) applicationDidUnhide:(NSNotification*)aNotification
2254 [self adjustWindowLevels];
2257 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2259 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2260 // don't count as "visible windows" for this purpose.
2261 [self unminimizeWindowIfNoneVisible];
2265 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2267 NSApplicationTerminateReply ret = NSTerminateNow;
2268 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2269 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2270 macdrv_event* event;
2271 WineEventQueue* queue;
2273 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2275 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2278 case kAEReallyLogOut:
2279 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2281 case kAEShowRestartDialog:
2282 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2284 case kAEShowShutdownDialog:
2285 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2288 event->app_quit_requested.reason = QUIT_REASON_NONE;
2292 [eventQueuesLock lock];
2294 if ([eventQueues count])
2296 for (queue in eventQueues)
2297 [queue postEvent:event];
2298 ret = NSTerminateLater;
2301 [eventQueuesLock unlock];
2303 macdrv_release_event(event);
2308 - (void)applicationWillResignActive:(NSNotification *)notification
2310 [self adjustWindowLevels:NO];
2313 /***********************************************************************
2316 * Run-loop-source perform callback. Pull request blocks from the
2317 * array of queued requests and invoke them.
2319 static void PerformRequest(void *info)
2321 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2322 WineApplicationController* controller = [WineApplicationController sharedController];
2326 __block dispatch_block_t block;
2328 dispatch_sync(controller->requestsManipQueue, ^{
2329 if ([controller->requests count])
2331 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2332 [controller->requests removeObjectAtIndex:0];
2345 pool = [[NSAutoreleasePool alloc] init];
2351 /***********************************************************************
2354 * Run a block on the main thread asynchronously.
2356 void OnMainThreadAsync(dispatch_block_t block)
2358 WineApplicationController* controller = [WineApplicationController sharedController];
2360 block = [block copy];
2361 dispatch_sync(controller->requestsManipQueue, ^{
2362 [controller->requests addObject:block];
2365 CFRunLoopSourceSignal(controller->requestSource);
2366 CFRunLoopWakeUp(CFRunLoopGetMain());
2371 /***********************************************************************
2374 void LogError(const char* func, NSString* format, ...)
2377 va_start(args, format);
2378 LogErrorv(func, format, args);
2382 /***********************************************************************
2385 void LogErrorv(const char* func, NSString* format, va_list args)
2387 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2389 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2390 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2396 /***********************************************************************
2397 * macdrv_window_rejected_focus
2399 * Pass focus to the next window that hasn't already rejected this same
2400 * WINDOW_GOT_FOCUS event.
2402 void macdrv_window_rejected_focus(const macdrv_event *event)
2405 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2409 /***********************************************************************
2410 * macdrv_get_input_source_info
2412 * Returns the keyboard layout uchr data, keyboard type and input source.
2414 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2417 TISInputSourceRef inputSourceLayout;
2419 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2420 if (inputSourceLayout)
2422 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2423 kTISPropertyUnicodeKeyLayoutData);
2424 *uchr = CFDataCreateCopy(NULL, data);
2425 CFRelease(inputSourceLayout);
2427 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2428 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2429 *input_source = TISCopyCurrentKeyboardInputSource();
2434 /***********************************************************************
2437 * Play the beep sound configured by the user in System Preferences.
2439 void macdrv_beep(void)
2441 OnMainThreadAsync(^{
2446 /***********************************************************************
2447 * macdrv_set_display_mode
2449 int macdrv_set_display_mode(const struct macdrv_display* display,
2450 CGDisplayModeRef display_mode)
2455 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2461 /***********************************************************************
2466 * If name is non-NULL, it is a selector for a class method on NSCursor
2467 * identifying the cursor to set. In that case, frames is ignored. If
2468 * name is NULL, then frames is used.
2470 * frames is an array of dictionaries. Each dictionary is a frame of
2471 * an animated cursor. Under the key "image" is a CGImage for the
2472 * frame. Under the key "duration" is a CFNumber time interval, in
2473 * seconds, for how long that frame is presented before proceeding to
2474 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2475 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2476 * This is the hot spot, measured in pixels down and to the right of the
2477 * top-left corner of the image.
2479 * If the array has exactly 1 element, the cursor is static, not
2480 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2482 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2486 sel = NSSelectorFromString((NSString*)name);
2489 OnMainThreadAsync(^{
2490 WineApplicationController* controller = [WineApplicationController sharedController];
2491 [controller setCursorWithFrames:nil];
2492 controller.cursor = [NSCursor performSelector:sel];
2493 [controller unhideCursor];
2498 NSArray* nsframes = (NSArray*)frames;
2499 if ([nsframes count])
2501 OnMainThreadAsync(^{
2502 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2507 OnMainThreadAsync(^{
2508 WineApplicationController* controller = [WineApplicationController sharedController];
2509 [controller setCursorWithFrames:nil];
2510 [controller hideCursor];
2516 /***********************************************************************
2517 * macdrv_get_cursor_position
2519 * Obtains the current cursor position. Returns zero on failure,
2520 * non-zero on success.
2522 int macdrv_get_cursor_position(CGPoint *pos)
2525 NSPoint location = [NSEvent mouseLocation];
2526 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2527 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2533 /***********************************************************************
2534 * macdrv_set_cursor_position
2536 * Sets the cursor position without generating events. Returns zero on
2537 * failure, non-zero on success.
2539 int macdrv_set_cursor_position(CGPoint pos)
2544 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2550 /***********************************************************************
2551 * macdrv_clip_cursor
2553 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2554 * to or larger than the whole desktop region, the cursor is unclipped.
2555 * Returns zero on failure, non-zero on success.
2557 int macdrv_clip_cursor(CGRect r)
2562 WineApplicationController* controller = [WineApplicationController sharedController];
2563 BOOL clipping = FALSE;
2566 if (!CGRectIsInfinite(rect))
2567 rect = cgrect_mac_from_win(rect);
2569 if (!CGRectIsInfinite(rect))
2571 NSRect nsrect = NSRectFromCGRect(rect);
2574 /* Convert the rectangle from top-down coords to bottom-up. */
2575 [controller flipRect:&nsrect];
2578 for (screen in [NSScreen screens])
2580 if (!NSContainsRect(nsrect, [screen frame]))
2589 ret = [controller startClippingCursor:rect];
2591 ret = [controller stopClippingCursor];
2597 /***********************************************************************
2598 * macdrv_set_application_icon
2600 * Set the application icon. The images array contains CGImages. If
2601 * there are more than one, then they represent different sizes or
2602 * color depths from the icon resource. If images is NULL or empty,
2603 * restores the default application image.
2605 void macdrv_set_application_icon(CFArrayRef images)
2607 NSArray* imageArray = (NSArray*)images;
2609 OnMainThreadAsync(^{
2610 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2614 /***********************************************************************
2617 void macdrv_quit_reply(int reply)
2620 [NSApp replyToApplicationShouldTerminate:reply];
2624 /***********************************************************************
2625 * macdrv_using_input_method
2627 int macdrv_using_input_method(void)
2632 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2638 /***********************************************************************
2639 * macdrv_set_mouse_capture_window
2641 void macdrv_set_mouse_capture_window(macdrv_window window)
2643 WineWindow* w = (WineWindow*)window;
2645 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2648 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2652 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2653 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2654 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2656 /***********************************************************************
2657 * macdrv_create_input_source_list
2659 CFArrayRef macdrv_create_input_source_list(void)
2661 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2664 CFArrayRef input_list;
2665 CFDictionaryRef filter_dict;
2666 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2667 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2670 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2671 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2672 input_list = TISCreateInputSourceList(filter_dict, false);
2674 for (i = 0; i < CFArrayGetCount(input_list); i++)
2676 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2677 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2678 CFDictionaryRef entry;
2679 const void *input_keys[3] = { macdrv_input_source_input_key,
2680 macdrv_input_source_type_key,
2681 macdrv_input_source_lang_key };
2682 const void *input_values[3];
2684 input_values[0] = input;
2685 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2686 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2688 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2689 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2691 CFArrayAppendValue(ret, entry);
2694 CFRelease(input_list);
2695 CFRelease(filter_dict);
2701 int macdrv_select_input_source(TISInputSourceRef input_source)
2703 __block int ret = FALSE;
2706 ret = (TISSelectInputSource(input_source) == noErr);
2712 void macdrv_set_cocoa_retina_mode(int new_mode)
2715 [[WineApplicationController sharedController] setRetinaMode:new_mode];