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 NSInteger minFloatingLevel = NSFloatingWindowLevel;
524 __block WineWindow* prev = nil;
527 if ([NSApp isHidden]) return;
529 windowNumbers = [NSWindow windowNumbersWithOptions:0];
530 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
532 // For the most part, we rely on the window server's ordering of the windows
533 // to be authoritative. The one exception is if the "floating" property of
534 // one of the windows has been changed, it may be in the wrong level and thus
535 // in the order. This method is what's supposed to fix that up. So build
536 // a list of Wine windows sorted first by floating-ness and then by order
537 // as indicated by the window server.
538 for (windowNumber in windowNumbers)
540 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
541 if ([window isKindOfClass:[WineWindow class]])
544 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
546 [wineWindows addObject:window];
550 NSDisableScreenUpdates();
552 // Go from back to front so that all windows in front of one which is
553 // elevated for full-screen are also elevated.
554 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
555 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
556 WineWindow* window = (WineWindow*)obj;
557 NSInteger origLevel = [window level];
558 NSInteger newLevel = [window minimumLevelForActive:active];
562 if (minFloatingLevel <= maxNonfloatingLevel)
563 minFloatingLevel = maxNonfloatingLevel + 1;
564 if (newLevel < minFloatingLevel)
565 newLevel = minFloatingLevel;
568 if (newLevel < maxLevel)
573 if (!window.floating && maxNonfloatingLevel < newLevel)
574 maxNonfloatingLevel = newLevel;
576 if (newLevel != origLevel)
578 [window setLevel:newLevel];
580 // -setLevel: puts the window at the front of its new level. If
581 // we decreased the level, that's good (it was in front of that
582 // level before, so it should still be now). But if we increased
583 // the level, the window should be toward the back (but still
584 // ahead of the previous windows we did this to).
585 if (origLevel < newLevel)
588 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
590 [window orderBack:nil];
597 NSEnableScreenUpdates();
599 [wineWindows release];
601 // The above took care of the visible windows on the current space. That
602 // leaves windows on other spaces, minimized windows, and windows which
603 // are not ordered in. We want to leave windows on other spaces alone
604 // so the space remains just as they left it (when viewed in Exposé or
605 // Mission Control, for example). We'll adjust the window levels again
606 // after we switch to another space, anyway. Windows which aren't
607 // ordered in will be handled when we order them in. Minimized windows
608 // on the current space should be set to the level they would have gotten
609 // if they were at the front of the windows with the same floating-ness,
610 // because that's where they'll go if/when they are unminimized. Again,
611 // for good measure we'll adjust window levels again when a window is
613 for (window in [NSApp windows])
615 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
616 [window isOnActiveSpace])
618 NSInteger origLevel = [window level];
619 NSInteger newLevel = [window minimumLevelForActive:YES];
620 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
622 if (newLevel < maxLevelForType)
623 newLevel = maxLevelForType;
625 if (newLevel != origLevel)
626 [window setLevel:newLevel];
631 - (void) adjustWindowLevels
633 [self adjustWindowLevels:[NSApp isActive]];
636 - (void) updateFullscreenWindows
638 if (capture_displays_for_fullscreen && [NSApp isActive])
640 BOOL anyFullscreen = FALSE;
641 NSNumber* windowNumber;
642 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
644 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
645 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
647 anyFullscreen = TRUE;
654 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
655 displaysCapturedForFullscreen = TRUE;
657 else if (displaysCapturedForFullscreen)
659 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
660 displaysCapturedForFullscreen = FALSE;
665 - (void) activeSpaceDidChange
667 [self updateFullscreenWindows];
668 [self adjustWindowLevels];
671 - (void) sendDisplaysChanged:(BOOL)activating
674 WineEventQueue* queue;
676 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
677 event->displays_changed.activating = activating;
679 [eventQueuesLock lock];
681 // If we're activating, then we just need one of our threads to get the
682 // event, so it can send it directly to the desktop window. Otherwise,
683 // we need all of the threads to get it because we don't know which owns
684 // the desktop window and only that one will do anything with it.
685 if (activating) event->deliver = 1;
687 for (queue in eventQueues)
688 [queue postEvent:event];
689 [eventQueuesLock unlock];
691 macdrv_release_event(event);
694 // We can compare two modes directly using CFEqual, but that may require that
695 // they are identical to a level that we don't need. In particular, when the
696 // OS switches between the integrated and discrete GPUs, the set of display
697 // modes can change in subtle ways. We're interested in whether two modes
698 // match in their most salient features, even if they aren't identical.
699 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
701 NSString *encoding1, *encoding2;
702 uint32_t ioflags1, ioflags2, different;
703 double refresh1, refresh2;
705 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
706 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
708 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
709 if (CGDisplayModeGetPixelWidth != NULL &&
710 CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
711 if (CGDisplayModeGetPixelHeight != NULL &&
712 CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
715 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
716 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
717 if (![encoding1 isEqualToString:encoding2]) return FALSE;
719 ioflags1 = CGDisplayModeGetIOFlags(mode1);
720 ioflags2 = CGDisplayModeGetIOFlags(mode2);
721 different = ioflags1 ^ ioflags2;
722 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
723 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
726 refresh1 = CGDisplayModeGetRefreshRate(mode1);
727 if (refresh1 == 0) refresh1 = 60;
728 refresh2 = CGDisplayModeGetRefreshRate(mode2);
729 if (refresh2 == 0) refresh2 = 60;
730 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
735 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
737 NSMutableArray* ret = [NSMutableArray array];
738 NSDictionary* options = nil;
740 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
741 if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
742 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
743 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
746 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
747 for (id candidateModeObject in modes)
749 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
750 if ([self mode:candidateMode matchesMode:mode])
751 [ret addObject:candidateModeObject];
756 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
759 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
760 CGDisplayModeRef originalMode;
762 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
764 if (originalMode && [self mode:mode matchesMode:originalMode])
766 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
768 CGRestorePermanentDisplayConfiguration();
769 if (!displaysCapturedForFullscreen)
770 CGReleaseAllDisplays();
771 [originalDisplayModes removeAllObjects];
774 else // ... otherwise, try to restore just the one display
776 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
778 mode = (CGDisplayModeRef)modeObject;
779 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
781 [originalDisplayModes removeObjectForKey:displayIDKey];
790 BOOL active = [NSApp isActive];
791 CGDisplayModeRef currentMode;
794 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
796 currentMode = CGDisplayCopyDisplayMode(displayID);
797 if (!currentMode) // Invalid display ID
800 if ([self mode:mode matchesMode:currentMode]) // Already there!
802 CGDisplayModeRelease(currentMode);
806 CGDisplayModeRelease(currentMode);
809 modes = [self modesMatchingMode:mode forDisplay:displayID];
813 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
814 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
818 // If we get here, we have the displays captured. If we don't
819 // know the original mode of the display, the current mode must
820 // be the original. We should re-query the current mode since
821 // another process could have changed it between when we last
822 // checked and when we captured the displays.
824 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
828 for (id modeObject in modes)
830 mode = (CGDisplayModeRef)modeObject;
831 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
838 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
839 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
840 else if (![originalDisplayModes count])
842 CGRestorePermanentDisplayConfiguration();
843 if (!displaysCapturedForFullscreen)
844 CGReleaseAllDisplays();
848 CGDisplayModeRelease(currentMode);
852 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
859 [self adjustWindowLevels];
864 - (BOOL) areDisplaysCaptured
866 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
869 - (void) updateCursor:(BOOL)force
871 if (force || lastTargetWindow)
873 if (clientWantsCursorHidden && !cursorHidden)
879 if (!cursorIsCurrent)
882 cursorIsCurrent = TRUE;
885 if (!clientWantsCursorHidden && cursorHidden)
888 cursorHidden = FALSE;
895 [[NSCursor arrowCursor] set];
896 cursorIsCurrent = FALSE;
901 cursorHidden = FALSE;
908 if (!clientWantsCursorHidden)
910 clientWantsCursorHidden = TRUE;
911 [self updateCursor:TRUE];
915 - (void) unhideCursor
917 if (clientWantsCursorHidden)
919 clientWantsCursorHidden = FALSE;
920 [self updateCursor:FALSE];
924 - (void) setCursor:(NSCursor*)newCursor
926 if (newCursor != cursor)
929 cursor = [newCursor retain];
930 cursorIsCurrent = FALSE;
931 [self updateCursor:FALSE];
937 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
938 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
939 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
940 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
943 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
944 hotSpot = CGPointZero;
945 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
950 - (void) nextCursorFrame:(NSTimer*)theTimer
953 NSTimeInterval duration;
957 if (cursorFrame >= [cursorFrames count])
961 frame = [cursorFrames objectAtIndex:cursorFrame];
962 duration = [[frame objectForKey:@"duration"] doubleValue];
963 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
964 [cursorTimer setFireDate:date];
967 - (void) setCursorWithFrames:(NSArray*)frames
969 if (self.cursorFrames == frames)
972 self.cursorFrames = frames;
974 [cursorTimer invalidate];
975 self.cursorTimer = nil;
979 if ([frames count] > 1)
981 NSDictionary* frame = [frames objectAtIndex:0];
982 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
983 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
984 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
987 selector:@selector(nextCursorFrame:)
989 repeats:YES] autorelease];
990 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
997 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
999 NSImage* nsimage = nil;
1003 NSSize bestSize = NSZeroSize;
1006 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1008 for (image in images)
1010 CGImageRef cgimage = (CGImageRef)image;
1011 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1014 NSSize size = [imageRep size];
1016 [nsimage addRepresentation:imageRep];
1019 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1024 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1025 [nsimage setSize:bestSize];
1030 self.applicationIcon = nsimage;
1033 - (void) handleCommandTab
1035 if ([NSApp isActive])
1037 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1038 NSRunningApplication* app;
1039 NSRunningApplication* otherValidApp = nil;
1041 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1043 NSNumber* displayID;
1044 for (displayID in originalDisplayModes)
1046 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1047 [latentDisplayModes setObject:(id)mode forKey:displayID];
1048 CGDisplayModeRelease(mode);
1051 CGRestorePermanentDisplayConfiguration();
1052 CGReleaseAllDisplays();
1053 [originalDisplayModes removeAllObjects];
1054 displaysCapturedForFullscreen = FALSE;
1057 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1059 if (![app isEqual:thisApp] && !app.terminated &&
1060 app.activationPolicy == NSApplicationActivationPolicyRegular)
1064 // There's another visible app. Just hide ourselves and let
1065 // the system activate the other app.
1071 otherValidApp = app;
1075 // Didn't find a visible GUI app. Try the Finder or, if that's not
1076 // running, the first hidden GUI app. If even that doesn't work, we
1077 // just fail to switch and remain the active app.
1078 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1079 if (!app) app = otherValidApp;
1081 [app activateWithOptions:0];
1086 * ---------- Cursor clipping methods ----------
1088 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1089 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1090 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1091 * general case, we leverage that. We disassociate mouse movements from
1092 * the cursor position and then move the cursor manually, keeping it within
1093 * the clipping rectangle.
1095 * Moving the cursor manually isn't enough. We need to modify the event
1096 * stream so that the events have the new location, too. We need to do
1097 * this at a point before the events enter Cocoa, so that Cocoa will assign
1098 * the correct window to the event. So, we install a Quartz event tap to
1101 * Also, there's a complication when we move the cursor. We use
1102 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1103 * events, but the change of cursor position is incorporated into the
1104 * deltas of the next mouse move event. When the mouse is disassociated
1105 * from the cursor position, we need the deltas to only reflect actual
1106 * device movement, not programmatic changes. So, the event tap cancels
1107 * out the change caused by our calls to CGWarpMouseCursorPosition().
1109 - (void) clipCursorLocation:(CGPoint*)location
1111 if (location->x < CGRectGetMinX(cursorClipRect))
1112 location->x = CGRectGetMinX(cursorClipRect);
1113 if (location->y < CGRectGetMinY(cursorClipRect))
1114 location->y = CGRectGetMinY(cursorClipRect);
1115 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1116 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1117 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1118 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1121 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1123 CGPoint oldLocation;
1125 if (currentLocation)
1126 oldLocation = *currentLocation;
1128 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1130 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1132 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1135 warpRecord.from = oldLocation;
1136 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1138 /* Actually move the cursor. */
1139 err = CGWarpMouseCursorPosition(*newLocation);
1140 if (err != kCGErrorSuccess)
1143 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1144 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1146 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1148 warpRecord.to = *newLocation;
1149 [warpRecords addObject:warpRecord];
1156 - (BOOL) isMouseMoveEventType:(CGEventType)type
1160 case kCGEventMouseMoved:
1161 case kCGEventLeftMouseDragged:
1162 case kCGEventRightMouseDragged:
1163 case kCGEventOtherMouseDragged:
1170 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1172 int warpsFinished = 0;
1173 for (WarpRecord* warpRecord in warpRecords)
1175 if (warpRecord.timeAfter < eventTime ||
1176 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1182 return warpsFinished;
1185 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1186 type:(CGEventType)type
1187 event:(CGEventRef)event
1189 CGEventTimestamp eventTime;
1190 CGPoint eventLocation, cursorLocation;
1192 if (type == kCGEventTapDisabledByUserInput)
1194 if (type == kCGEventTapDisabledByTimeout)
1196 CGEventTapEnable(cursorClippingEventTap, TRUE);
1200 if (!clippingCursor)
1203 eventTime = CGEventGetTimestamp(event);
1204 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1206 eventLocation = CGEventGetLocation(event);
1208 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1210 if ([self isMouseMoveEventType:type])
1212 double deltaX, deltaY;
1213 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1216 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1217 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1219 for (i = 0; i < warpsFinished; i++)
1221 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1222 deltaX -= warpRecord.to.x - warpRecord.from.x;
1223 deltaY -= warpRecord.to.y - warpRecord.from.y;
1224 [warpRecords removeObjectAtIndex:0];
1229 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1230 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1233 synthesizedLocation.x += deltaX;
1234 synthesizedLocation.y += deltaY;
1237 // If the event is destined for another process, don't clip it. This may
1238 // happen if the user activates Exposé or Mission Control. In that case,
1239 // our app does not resign active status, so clipping is still in effect,
1240 // but the cursor should not actually be clipped.
1242 // In addition, the fact that mouse moves may have been delivered to a
1243 // different process means we have to treat the next one we receive as
1244 // absolute rather than relative.
1245 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1246 [self clipCursorLocation:&synthesizedLocation];
1248 lastSetCursorPositionTime = lastEventTapEventTime;
1250 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1251 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1252 CGEventSetLocation(event, synthesizedLocation);
1257 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1258 CGEventRef event, void *refcon)
1260 WineApplicationController* controller = refcon;
1261 return [controller eventTapWithProxy:proxy type:type event:event];
1264 - (BOOL) installEventTap
1266 ProcessSerialNumber psn;
1268 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1269 CGEventMaskBit(kCGEventLeftMouseUp) |
1270 CGEventMaskBit(kCGEventRightMouseDown) |
1271 CGEventMaskBit(kCGEventRightMouseUp) |
1272 CGEventMaskBit(kCGEventMouseMoved) |
1273 CGEventMaskBit(kCGEventLeftMouseDragged) |
1274 CGEventMaskBit(kCGEventRightMouseDragged) |
1275 CGEventMaskBit(kCGEventOtherMouseDown) |
1276 CGEventMaskBit(kCGEventOtherMouseUp) |
1277 CGEventMaskBit(kCGEventOtherMouseDragged) |
1278 CGEventMaskBit(kCGEventScrollWheel);
1279 CFRunLoopSourceRef source;
1281 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1283 if (cursorClippingEventTap)
1286 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1287 // framework with dlsym() because the Win32 function of the same name
1289 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1293 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1294 if (!pGetCurrentProcess)
1296 dlclose(appServices);
1300 err = pGetCurrentProcess(&psn);
1301 dlclose(appServices);
1305 // We create an annotated session event tap rather than a process-specific
1306 // event tap because we need to programmatically move the cursor even when
1307 // mouse moves are directed to other processes. We disable our tap when
1308 // other processes are active, but things like Exposé are handled by other
1309 // processes even when we remain active.
1310 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1311 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1312 if (!cursorClippingEventTap)
1315 CGEventTapEnable(cursorClippingEventTap, FALSE);
1317 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1320 CFRelease(cursorClippingEventTap);
1321 cursorClippingEventTap = NULL;
1325 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1330 - (BOOL) setCursorPosition:(CGPoint)pos
1334 if ([windowsBeingDragged count])
1336 else if (clippingCursor)
1338 [self clipCursorLocation:&pos];
1340 ret = [self warpCursorTo:&pos from:NULL];
1341 synthesizedLocation = pos;
1344 // We want to discard mouse-move events that have already been
1345 // through the event tap, because it's too late to account for
1346 // the setting of the cursor position with them. However, the
1347 // events that may be queued with times after that but before
1348 // the above warp can still be used. So, use the last event
1349 // tap event time so that -sendEvent: doesn't discard them.
1350 lastSetCursorPositionTime = lastEventTapEventTime;
1355 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1356 // the mouse from the cursor position for 0.25 seconds. This means
1357 // that mouse movement during that interval doesn't move the cursor
1358 // and events carry a constant location (the warped-to position)
1359 // even though they have delta values. For apps which warp the
1360 // cursor frequently (like after every mouse move), this makes
1361 // cursor movement horribly laggy and jerky, as only a fraction of
1362 // mouse move events have any effect.
1364 // On some versions of OS X, it's sufficient to forcibly reassociate
1365 // the mouse and cursor position. On others, it's necessary to set
1366 // the local events suppression interval to 0 for the warp. That's
1367 // deprecated, but I'm not aware of any other way. For good
1368 // measure, we do both.
1369 CGSetLocalEventsSuppressionInterval(0);
1370 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1371 CGSetLocalEventsSuppressionInterval(0.25);
1374 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1376 CGAssociateMouseAndMouseCursorPosition(true);
1382 WineEventQueue* queue;
1384 // Discard all pending mouse move events.
1385 [eventQueuesLock lock];
1386 for (queue in eventQueues)
1388 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1389 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1391 [queue resetMouseEventPositions:pos];
1393 [eventQueuesLock unlock];
1399 - (void) activateCursorClipping
1401 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1403 CGEventTapEnable(cursorClippingEventTap, TRUE);
1404 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1408 - (void) deactivateCursorClipping
1410 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1412 CGEventTapEnable(cursorClippingEventTap, FALSE);
1413 [warpRecords removeAllObjects];
1414 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1418 - (void) updateCursorClippingState
1420 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1421 [self activateCursorClipping];
1423 [self deactivateCursorClipping];
1426 - (void) updateWindowsForCursorClipping
1429 for (window in [NSApp windows])
1431 if ([window isKindOfClass:[WineWindow class]])
1432 [window updateForCursorClipping];
1436 - (BOOL) startClippingCursor:(CGRect)rect
1440 if (!cursorClippingEventTap && ![self installEventTap])
1443 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1444 CGEventTapIsEnabled(cursorClippingEventTap))
1447 err = CGAssociateMouseAndMouseCursorPosition(false);
1448 if (err != kCGErrorSuccess)
1451 clippingCursor = TRUE;
1452 cursorClipRect = rect;
1453 [self updateCursorClippingState];
1454 [self updateWindowsForCursorClipping];
1459 - (BOOL) stopClippingCursor
1461 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1462 if (err != kCGErrorSuccess)
1465 clippingCursor = FALSE;
1466 [self updateCursorClippingState];
1467 [self updateWindowsForCursorClipping];
1472 - (BOOL) isKeyPressed:(uint16_t)keyCode
1474 int bits = sizeof(pressedKeyCodes[0]) * 8;
1475 int index = keyCode / bits;
1476 uint32_t mask = 1 << (keyCode % bits);
1477 return (pressedKeyCodes[index] & mask) != 0;
1480 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1482 int bits = sizeof(pressedKeyCodes[0]) * 8;
1483 int index = keyCode / bits;
1484 uint32_t mask = 1 << (keyCode % bits);
1486 pressedKeyCodes[index] |= mask;
1488 pressedKeyCodes[index] &= ~mask;
1491 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1494 [windowsBeingDragged addObject:window];
1496 [windowsBeingDragged removeObject:window];
1497 [self updateCursorClippingState];
1500 - (void) handleMouseMove:(NSEvent*)anEvent
1502 WineWindow* targetWindow;
1503 BOOL drag = [anEvent type] != NSMouseMoved;
1505 if ([windowsBeingDragged count])
1507 else if (mouseCaptureWindow)
1508 targetWindow = mouseCaptureWindow;
1510 targetWindow = (WineWindow*)[anEvent window];
1513 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1514 event indicates its window is the main window, even if the cursor is
1515 over a different window. Find the actual WineWindow that is under the
1516 cursor and post the event as being for that window. */
1517 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1518 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1519 NSInteger windowUnderNumber;
1521 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1522 belowWindowWithWindowNumber:0];
1523 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1524 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1528 if ([targetWindow isKindOfClass:[WineWindow class]])
1530 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1531 macdrv_event* event;
1534 // If we recently warped the cursor (other than in our cursor-clipping
1535 // event tap), discard mouse move events until we see an event which is
1536 // later than that time.
1537 if (lastSetCursorPositionTime)
1539 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1542 lastSetCursorPositionTime = 0;
1543 forceNextMouseMoveAbsolute = TRUE;
1546 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1549 forceNextMouseMoveAbsolute = FALSE;
1553 // Send absolute move events if the cursor is in the interior of
1554 // its range. Only send relative moves if the cursor is pinned to
1555 // the boundaries of where it can go. We compute the position
1556 // that's one additional point in the direction of movement. If
1557 // that is outside of the clipping rect or desktop region (the
1558 // union of the screen frames), then we figure the cursor would
1559 // have moved outside if it could but it was pinned.
1560 CGPoint computedPoint = point;
1561 CGFloat deltaX = [anEvent deltaX];
1562 CGFloat deltaY = [anEvent deltaY];
1566 else if (deltaX < -0.001)
1571 else if (deltaY < -0.001)
1574 // Assume cursor is pinned for now
1576 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1578 const CGRect* rects;
1579 NSUInteger count, i;
1581 // Caches screenFrameCGRects if necessary
1582 [self primaryScreenHeight];
1584 rects = [screenFrameCGRects bytes];
1585 count = [screenFrameCGRects length] / sizeof(rects[0]);
1587 for (i = 0; i < count; i++)
1589 if (CGRectContainsPoint(rects[i], computedPoint))
1601 [self clipCursorLocation:&point];
1602 point = cgpoint_win_from_mac(point);
1604 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1605 event->mouse_moved.x = floor(point.x);
1606 event->mouse_moved.y = floor(point.y);
1608 mouseMoveDeltaX = 0;
1609 mouseMoveDeltaY = 0;
1613 double scale = retina_on ? 2 : 1;
1615 /* Add event delta to accumulated delta error */
1616 /* deltaY is already flipped */
1617 mouseMoveDeltaX += [anEvent deltaX];
1618 mouseMoveDeltaY += [anEvent deltaY];
1620 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1621 event->mouse_moved.x = mouseMoveDeltaX * scale;
1622 event->mouse_moved.y = mouseMoveDeltaY * scale;
1624 /* Keep the remainder after integer truncation. */
1625 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1626 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1629 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1631 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1632 event->mouse_moved.drag = drag;
1634 [targetWindow.queue postEvent:event];
1637 macdrv_release_event(event);
1639 lastTargetWindow = targetWindow;
1642 lastTargetWindow = nil;
1644 [self updateCursor:FALSE];
1647 - (void) handleMouseButton:(NSEvent*)theEvent
1649 WineWindow* window = (WineWindow*)[theEvent window];
1650 NSEventType type = [theEvent type];
1651 WineWindow* windowBroughtForward = nil;
1652 BOOL process = FALSE;
1654 if ([window isKindOfClass:[WineWindow class]] &&
1655 type == NSLeftMouseDown &&
1656 (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1658 NSWindowButton windowButton;
1660 windowBroughtForward = window;
1662 /* Any left-click on our window anyplace other than the close or
1663 minimize buttons will bring it forward. */
1664 for (windowButton = NSWindowCloseButton;
1665 windowButton <= NSWindowMiniaturizeButton;
1668 NSButton* button = [window standardWindowButton:windowButton];
1671 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1672 if ([button mouse:point inRect:[button bounds]])
1674 windowBroughtForward = nil;
1681 if ([windowsBeingDragged count])
1683 else if (mouseCaptureWindow)
1684 window = mouseCaptureWindow;
1686 if ([window isKindOfClass:[WineWindow class]])
1688 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1689 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1692 [self clipCursorLocation:&pt];
1696 if (mouseCaptureWindow)
1700 // Test if the click was in the window's content area.
1701 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1702 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1703 process = NSMouseInRect(nspoint, contentRect, NO);
1704 if (process && [window styleMask] & NSResizableWindowMask)
1706 // Ignore clicks in the grow box (resize widget).
1707 HIPoint origin = { 0, 0 };
1708 HIThemeGrowBoxDrawInfo info = { 0 };
1712 info.kind = kHIThemeGrowBoxKindNormal;
1713 info.direction = kThemeGrowRight | kThemeGrowDown;
1714 if ([window styleMask] & NSUtilityWindowMask)
1715 info.size = kHIThemeGrowBoxSizeSmall;
1717 info.size = kHIThemeGrowBoxSizeNormal;
1719 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1720 if (status == noErr)
1722 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1723 NSMinY(contentRect),
1725 bounds.size.height);
1726 process = !NSMouseInRect(nspoint, growBox, NO);
1731 unmatchedMouseDowns |= NSEventMaskFromType(type);
1735 NSEventType downType = type - 1;
1736 NSUInteger downMask = NSEventMaskFromType(downType);
1737 process = (unmatchedMouseDowns & downMask) != 0;
1738 unmatchedMouseDowns &= ~downMask;
1743 macdrv_event* event;
1745 pt = cgpoint_win_from_mac(pt);
1747 event = macdrv_create_event(MOUSE_BUTTON, window);
1748 event->mouse_button.button = [theEvent buttonNumber];
1749 event->mouse_button.pressed = pressed;
1750 event->mouse_button.x = floor(pt.x);
1751 event->mouse_button.y = floor(pt.y);
1752 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1754 [window.queue postEvent:event];
1756 macdrv_release_event(event);
1760 if (windowBroughtForward)
1762 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1763 NSInteger ancestorNumber = [ancestor windowNumber];
1764 NSInteger ancestorLevel = [ancestor level];
1766 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1768 NSInteger windowNumber = [windowNumberObject integerValue];
1769 if (windowNumber == ancestorNumber)
1771 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1772 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1773 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1775 [ancestor postBroughtForwardEvent];
1779 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1780 [self windowGotFocus:windowBroughtForward];
1783 // Since mouse button events deliver absolute cursor position, the
1784 // accumulating delta from move events is invalidated. Make sure
1785 // next mouse move event starts over from an absolute baseline.
1786 // Also, it's at least possible that the title bar widgets (e.g. close
1787 // button, etc.) could enter an internal event loop on a mouse down that
1788 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1789 // dragged events and, after that, any notion of the cursor position
1790 // computed from accumulating deltas would be wrong.
1791 forceNextMouseMoveAbsolute = TRUE;
1794 - (void) handleScrollWheel:(NSEvent*)theEvent
1798 if (mouseCaptureWindow)
1799 window = mouseCaptureWindow;
1801 window = (WineWindow*)[theEvent window];
1803 if ([window isKindOfClass:[WineWindow class]])
1805 CGEventRef cgevent = [theEvent CGEvent];
1806 CGPoint pt = CGEventGetLocation(cgevent);
1810 [self clipCursorLocation:&pt];
1812 if (mouseCaptureWindow)
1816 // Only process the event if it was in the window's content area.
1817 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1818 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1819 process = NSMouseInRect(nspoint, contentRect, NO);
1824 macdrv_event* event;
1826 BOOL continuous = FALSE;
1828 pt = cgpoint_win_from_mac(pt);
1830 event = macdrv_create_event(MOUSE_SCROLL, window);
1831 event->mouse_scroll.x = floor(pt.x);
1832 event->mouse_scroll.y = floor(pt.y);
1833 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1835 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1839 /* Continuous scroll wheel events come from high-precision scrolling
1840 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1841 For these, we can get more precise data from the CGEvent API. */
1842 /* Axis 1 is vertical, axis 2 is horizontal. */
1843 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1844 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1848 double pixelsPerLine = 10;
1849 CGEventSourceRef source;
1851 /* The non-continuous values are in units of "lines", not pixels. */
1852 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1854 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1858 x = pixelsPerLine * [theEvent deltaX];
1859 y = pixelsPerLine * [theEvent deltaY];
1862 /* Mac: negative is right or down, positive is left or up.
1863 Win32: negative is left or down, positive is right or up.
1864 So, negate the X scroll value to translate. */
1867 /* The x,y values so far are in pixels. Win32 expects to receive some
1868 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1869 6 times the pixel value. */
1873 if (use_precise_scrolling)
1875 event->mouse_scroll.x_scroll = x;
1876 event->mouse_scroll.y_scroll = y;
1880 /* For non-continuous "clicky" wheels, if there was any motion, make
1881 sure there was at least WHEEL_DELTA motion. This is so, at slow
1882 speeds where the system's acceleration curve is actually reducing the
1883 scroll distance, the user is sure to get some action out of each click.
1884 For example, this is important for rotating though weapons in a
1885 first-person shooter. */
1886 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1887 event->mouse_scroll.x_scroll = 120;
1888 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1889 event->mouse_scroll.x_scroll = -120;
1891 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1892 event->mouse_scroll.y_scroll = 120;
1893 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1894 event->mouse_scroll.y_scroll = -120;
1899 /* If it's been a while since the last scroll event or if the scrolling has
1900 reversed direction, reset the accumulated scroll value. */
1901 if ([theEvent timestamp] - lastScrollTime > 1)
1902 accumScrollX = accumScrollY = 0;
1905 /* The accumulated scroll value is in the opposite direction/sign of the last
1906 scroll. That's because it's the "debt" resulting from over-scrolling in
1907 that direction. We accumulate by adding in the scroll amount and then, if
1908 it has the same sign as the scroll value, we subtract any whole or partial
1909 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1910 scroll direction if the accumulated debt and the new scroll value have the
1912 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1914 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1917 lastScrollTime = [theEvent timestamp];
1922 if (accumScrollX > 0 && x > 0)
1923 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1924 if (accumScrollX < 0 && x < 0)
1925 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1926 if (accumScrollY > 0 && y > 0)
1927 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1928 if (accumScrollY < 0 && y < 0)
1929 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1931 accumScrollX -= event->mouse_scroll.x_scroll;
1932 accumScrollY -= event->mouse_scroll.y_scroll;
1935 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1936 [window.queue postEvent:event];
1938 macdrv_release_event(event);
1940 // Since scroll wheel events deliver absolute cursor position, the
1941 // accumulating delta from move events is invalidated. Make sure next
1942 // mouse move event starts over from an absolute baseline.
1943 forceNextMouseMoveAbsolute = TRUE;
1948 // Returns TRUE if the event was handled and caller should do nothing more
1949 // with it. Returns FALSE if the caller should process it as normal and
1950 // then call -didSendEvent:.
1951 - (BOOL) handleEvent:(NSEvent*)anEvent
1954 NSEventType type = [anEvent type];
1956 if (type == NSFlagsChanged)
1957 self.lastFlagsChanged = anEvent;
1958 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1959 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1961 [self handleMouseMove:anEvent];
1962 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1964 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1965 type == NSRightMouseDown || type == NSRightMouseUp ||
1966 type == NSOtherMouseDown || type == NSOtherMouseUp)
1968 [self handleMouseButton:anEvent];
1969 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1971 else if (type == NSScrollWheel)
1973 [self handleScrollWheel:anEvent];
1974 ret = mouseCaptureWindow != nil;
1976 else if (type == NSKeyUp)
1978 uint16_t keyCode = [anEvent keyCode];
1979 if ([self isKeyPressed:keyCode])
1981 WineWindow* window = (WineWindow*)[anEvent window];
1982 [self noteKey:keyCode pressed:FALSE];
1983 if ([window isKindOfClass:[WineWindow class]])
1984 [window postKeyEvent:anEvent];
1987 else if (type == NSAppKitDefined)
1989 short subtype = [anEvent subtype];
1991 // These subtypes are not documented but they appear to mean
1992 // "a window is being dragged" and "a window is no longer being
1993 // dragged", respectively.
1994 if (subtype == 20 || subtype == 21)
1996 WineWindow* window = (WineWindow*)[anEvent window];
1997 if ([window isKindOfClass:[WineWindow class]])
1999 macdrv_event* event;
2004 [windowsBeingDragged addObject:window];
2005 eventType = WINDOW_DRAG_BEGIN;
2009 [windowsBeingDragged removeObject:window];
2010 eventType = WINDOW_DRAG_END;
2012 [self updateCursorClippingState];
2014 event = macdrv_create_event(eventType, window);
2015 [window.queue postEvent:event];
2016 macdrv_release_event(event);
2024 - (void) didSendEvent:(NSEvent*)anEvent
2026 NSEventType type = [anEvent type];
2028 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2030 NSUInteger modifiers = [anEvent modifierFlags];
2031 if ((modifiers & NSCommandKeyMask) &&
2032 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2034 // Command-Tab and Command-Shift-Tab would normally be intercepted
2035 // by the system to switch applications. If we're seeing it, it's
2036 // presumably because we've captured the displays, preventing
2037 // normal application switching. Do it manually.
2038 [self handleCommandTab];
2043 - (void) setupObservations
2045 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2046 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2047 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2049 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2052 usingBlock:^(NSNotification *note){
2053 NSWindow* window = [note object];
2054 [keyWindows removeObjectIdenticalTo:window];
2055 [keyWindows insertObject:window atIndex:0];
2058 [nc addObserverForName:NSWindowWillCloseNotification
2060 queue:[NSOperationQueue mainQueue]
2061 usingBlock:^(NSNotification *note){
2062 NSWindow* window = [note object];
2063 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2065 [keyWindows removeObjectIdenticalTo:window];
2066 if (window == lastTargetWindow)
2067 lastTargetWindow = nil;
2068 if (window == self.mouseCaptureWindow)
2069 self.mouseCaptureWindow = nil;
2070 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2072 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2073 [self updateFullscreenWindows];
2076 [windowsBeingDragged removeObject:window];
2077 [self updateCursorClippingState];
2080 [nc addObserver:self
2081 selector:@selector(keyboardSelectionDidChange)
2082 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2085 /* The above notification isn't sent unless the NSTextInputContext
2086 class has initialized itself. Poke it. */
2087 [NSTextInputContext self];
2089 [wsnc addObserver:self
2090 selector:@selector(activeSpaceDidChange)
2091 name:NSWorkspaceActiveSpaceDidChangeNotification
2094 [nc addObserver:self
2095 selector:@selector(releaseMouseCapture)
2096 name:NSMenuDidBeginTrackingNotification
2099 [dnc addObserver:self
2100 selector:@selector(releaseMouseCapture)
2101 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2103 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2105 [dnc addObserver:self
2106 selector:@selector(enabledKeyboardInputSourcesChanged)
2107 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2111 - (BOOL) inputSourceIsInputMethod
2113 if (!inputSourceIsInputMethodValid)
2115 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2118 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2119 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2120 CFRelease(inputSource);
2123 inputSourceIsInputMethod = FALSE;
2124 inputSourceIsInputMethodValid = TRUE;
2127 return inputSourceIsInputMethod;
2130 - (void) releaseMouseCapture
2132 // This might be invoked on a background thread by the distributed
2133 // notification center. Shunt it to the main thread.
2134 if (![NSThread isMainThread])
2136 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2140 if (mouseCaptureWindow)
2142 macdrv_event* event;
2144 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2145 [mouseCaptureWindow.queue postEvent:event];
2146 macdrv_release_event(event);
2150 - (void) unminimizeWindowIfNoneVisible
2152 if (![self frontWineWindow])
2154 for (WineWindow* window in [NSApp windows])
2156 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2158 [window deminiaturize:self];
2165 - (void) setRetinaMode:(int)mode
2171 double scale = mode ? 0.5 : 2.0;
2172 cursorClipRect.origin.x *= scale;
2173 cursorClipRect.origin.y *= scale;
2174 cursorClipRect.size.width *= scale;
2175 cursorClipRect.size.height *= scale;
2178 for (WineWindow* window in [NSApp windows])
2180 if ([window isKindOfClass:[WineWindow class]])
2181 [window setRetinaMode:mode];
2187 * ---------- NSApplicationDelegate methods ----------
2189 - (void)applicationDidBecomeActive:(NSNotification *)notification
2191 NSNumber* displayID;
2192 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2194 latentDisplayModes = [[NSMutableDictionary alloc] init];
2195 for (displayID in modesToRealize)
2197 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2198 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2201 [self updateCursorClippingState];
2203 [self updateFullscreenWindows];
2204 [self adjustWindowLevels:YES];
2207 [self unminimizeWindowIfNoneVisible];
2210 // If a Wine process terminates abruptly while it has the display captured
2211 // and switched to a different resolution, Mac OS X will uncapture the
2212 // displays and switch their resolutions back. However, the other Wine
2213 // processes won't have their notion of the desktop rect changed back.
2214 // This can lead them to refuse to draw or acknowledge clicks in certain
2215 // portions of their windows.
2217 // To solve this, we synthesize a displays-changed event whenever we're
2218 // activated. This will provoke a re-synchronization of Wine's notion of
2219 // the desktop rect with the actual state.
2220 [self sendDisplaysChanged:TRUE];
2222 // The cursor probably moved while we were inactive. Accumulated mouse
2223 // movement deltas are invalidated. Make sure the next mouse move event
2224 // starts over from an absolute baseline.
2225 forceNextMouseMoveAbsolute = TRUE;
2228 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2230 primaryScreenHeightValid = FALSE;
2231 [self sendDisplaysChanged:FALSE];
2232 [self adjustWindowLevels];
2234 // When the display configuration changes, the cursor position may jump.
2235 // Accumulated mouse movement deltas are invalidated. Make sure the next
2236 // mouse move event starts over from an absolute baseline.
2237 forceNextMouseMoveAbsolute = TRUE;
2240 - (void)applicationDidResignActive:(NSNotification *)notification
2242 macdrv_event* event;
2243 WineEventQueue* queue;
2245 [self updateCursorClippingState];
2247 [self invalidateGotFocusEvents];
2249 event = macdrv_create_event(APP_DEACTIVATED, nil);
2251 [eventQueuesLock lock];
2252 for (queue in eventQueues)
2253 [queue postEvent:event];
2254 [eventQueuesLock unlock];
2256 macdrv_release_event(event);
2258 [self releaseMouseCapture];
2261 - (void) applicationDidUnhide:(NSNotification*)aNotification
2263 [self adjustWindowLevels];
2266 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2268 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2269 // don't count as "visible windows" for this purpose.
2270 [self unminimizeWindowIfNoneVisible];
2274 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2276 NSApplicationTerminateReply ret = NSTerminateNow;
2277 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2278 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2279 macdrv_event* event;
2280 WineEventQueue* queue;
2282 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2284 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2287 case kAEReallyLogOut:
2288 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2290 case kAEShowRestartDialog:
2291 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2293 case kAEShowShutdownDialog:
2294 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2297 event->app_quit_requested.reason = QUIT_REASON_NONE;
2301 [eventQueuesLock lock];
2303 if ([eventQueues count])
2305 for (queue in eventQueues)
2306 [queue postEvent:event];
2307 ret = NSTerminateLater;
2310 [eventQueuesLock unlock];
2312 macdrv_release_event(event);
2317 - (void)applicationWillBecomeActive:(NSNotification *)notification
2319 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2322 [eventQueuesLock lock];
2323 for (WineEventQueue* queue in eventQueues)
2324 [queue postEvent:event];
2325 [eventQueuesLock unlock];
2327 macdrv_release_event(event);
2330 - (void)applicationWillResignActive:(NSNotification *)notification
2332 [self adjustWindowLevels:NO];
2335 /***********************************************************************
2338 * Run-loop-source perform callback. Pull request blocks from the
2339 * array of queued requests and invoke them.
2341 static void PerformRequest(void *info)
2343 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2344 WineApplicationController* controller = [WineApplicationController sharedController];
2348 __block dispatch_block_t block;
2350 dispatch_sync(controller->requestsManipQueue, ^{
2351 if ([controller->requests count])
2353 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2354 [controller->requests removeObjectAtIndex:0];
2367 pool = [[NSAutoreleasePool alloc] init];
2373 /***********************************************************************
2376 * Run a block on the main thread asynchronously.
2378 void OnMainThreadAsync(dispatch_block_t block)
2380 WineApplicationController* controller = [WineApplicationController sharedController];
2382 block = [block copy];
2383 dispatch_sync(controller->requestsManipQueue, ^{
2384 [controller->requests addObject:block];
2387 CFRunLoopSourceSignal(controller->requestSource);
2388 CFRunLoopWakeUp(CFRunLoopGetMain());
2393 /***********************************************************************
2396 void LogError(const char* func, NSString* format, ...)
2399 va_start(args, format);
2400 LogErrorv(func, format, args);
2404 /***********************************************************************
2407 void LogErrorv(const char* func, NSString* format, va_list args)
2409 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2411 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2412 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2418 /***********************************************************************
2419 * macdrv_window_rejected_focus
2421 * Pass focus to the next window that hasn't already rejected this same
2422 * WINDOW_GOT_FOCUS event.
2424 void macdrv_window_rejected_focus(const macdrv_event *event)
2427 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2431 /***********************************************************************
2432 * macdrv_get_input_source_info
2434 * Returns the keyboard layout uchr data, keyboard type and input source.
2436 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2439 TISInputSourceRef inputSourceLayout;
2441 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2442 if (inputSourceLayout)
2444 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2445 kTISPropertyUnicodeKeyLayoutData);
2446 *uchr = CFDataCreateCopy(NULL, data);
2447 CFRelease(inputSourceLayout);
2449 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2450 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2452 *input_source = TISCopyCurrentKeyboardInputSource();
2457 /***********************************************************************
2460 * Play the beep sound configured by the user in System Preferences.
2462 void macdrv_beep(void)
2464 OnMainThreadAsync(^{
2469 /***********************************************************************
2470 * macdrv_set_display_mode
2472 int macdrv_set_display_mode(const struct macdrv_display* display,
2473 CGDisplayModeRef display_mode)
2478 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2484 /***********************************************************************
2489 * If name is non-NULL, it is a selector for a class method on NSCursor
2490 * identifying the cursor to set. In that case, frames is ignored. If
2491 * name is NULL, then frames is used.
2493 * frames is an array of dictionaries. Each dictionary is a frame of
2494 * an animated cursor. Under the key "image" is a CGImage for the
2495 * frame. Under the key "duration" is a CFNumber time interval, in
2496 * seconds, for how long that frame is presented before proceeding to
2497 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2498 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2499 * This is the hot spot, measured in pixels down and to the right of the
2500 * top-left corner of the image.
2502 * If the array has exactly 1 element, the cursor is static, not
2503 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2505 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2509 sel = NSSelectorFromString((NSString*)name);
2512 OnMainThreadAsync(^{
2513 WineApplicationController* controller = [WineApplicationController sharedController];
2514 [controller setCursorWithFrames:nil];
2515 controller.cursor = [NSCursor performSelector:sel];
2516 [controller unhideCursor];
2521 NSArray* nsframes = (NSArray*)frames;
2522 if ([nsframes count])
2524 OnMainThreadAsync(^{
2525 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2530 OnMainThreadAsync(^{
2531 WineApplicationController* controller = [WineApplicationController sharedController];
2532 [controller setCursorWithFrames:nil];
2533 [controller hideCursor];
2539 /***********************************************************************
2540 * macdrv_get_cursor_position
2542 * Obtains the current cursor position. Returns zero on failure,
2543 * non-zero on success.
2545 int macdrv_get_cursor_position(CGPoint *pos)
2548 NSPoint location = [NSEvent mouseLocation];
2549 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2550 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2556 /***********************************************************************
2557 * macdrv_set_cursor_position
2559 * Sets the cursor position without generating events. Returns zero on
2560 * failure, non-zero on success.
2562 int macdrv_set_cursor_position(CGPoint pos)
2567 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2573 /***********************************************************************
2574 * macdrv_clip_cursor
2576 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2577 * to or larger than the whole desktop region, the cursor is unclipped.
2578 * Returns zero on failure, non-zero on success.
2580 int macdrv_clip_cursor(CGRect r)
2585 WineApplicationController* controller = [WineApplicationController sharedController];
2586 BOOL clipping = FALSE;
2589 if (!CGRectIsInfinite(rect))
2590 rect = cgrect_mac_from_win(rect);
2592 if (!CGRectIsInfinite(rect))
2594 NSRect nsrect = NSRectFromCGRect(rect);
2597 /* Convert the rectangle from top-down coords to bottom-up. */
2598 [controller flipRect:&nsrect];
2601 for (screen in [NSScreen screens])
2603 if (!NSContainsRect(nsrect, [screen frame]))
2612 ret = [controller startClippingCursor:rect];
2614 ret = [controller stopClippingCursor];
2620 /***********************************************************************
2621 * macdrv_set_application_icon
2623 * Set the application icon. The images array contains CGImages. If
2624 * there are more than one, then they represent different sizes or
2625 * color depths from the icon resource. If images is NULL or empty,
2626 * restores the default application image.
2628 void macdrv_set_application_icon(CFArrayRef images)
2630 NSArray* imageArray = (NSArray*)images;
2632 OnMainThreadAsync(^{
2633 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2637 /***********************************************************************
2640 void macdrv_quit_reply(int reply)
2643 [NSApp replyToApplicationShouldTerminate:reply];
2647 /***********************************************************************
2648 * macdrv_using_input_method
2650 int macdrv_using_input_method(void)
2655 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2661 /***********************************************************************
2662 * macdrv_set_mouse_capture_window
2664 void macdrv_set_mouse_capture_window(macdrv_window window)
2666 WineWindow* w = (WineWindow*)window;
2668 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2671 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2675 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2676 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2677 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2679 /***********************************************************************
2680 * macdrv_create_input_source_list
2682 CFArrayRef macdrv_create_input_source_list(void)
2684 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2687 CFArrayRef input_list;
2688 CFDictionaryRef filter_dict;
2689 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2690 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2693 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2694 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2695 input_list = TISCreateInputSourceList(filter_dict, false);
2697 for (i = 0; i < CFArrayGetCount(input_list); i++)
2699 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2700 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2701 CFDictionaryRef entry;
2702 const void *input_keys[3] = { macdrv_input_source_input_key,
2703 macdrv_input_source_type_key,
2704 macdrv_input_source_lang_key };
2705 const void *input_values[3];
2707 input_values[0] = input;
2708 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2709 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2711 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2712 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2714 CFArrayAppendValue(ret, entry);
2717 CFRelease(input_list);
2718 CFRelease(filter_dict);
2724 int macdrv_select_input_source(TISInputSourceRef input_source)
2726 __block int ret = FALSE;
2729 ret = (TISSelectInputSource(input_source) == noErr);
2735 void macdrv_set_cocoa_retina_mode(int new_mode)
2738 [[WineApplicationController sharedController] setRetinaMode:new_mode];