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];
1594 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1595 event->mouse_moved.x = point.x;
1596 event->mouse_moved.y = point.y;
1598 mouseMoveDeltaX = 0;
1599 mouseMoveDeltaY = 0;
1603 /* Add event delta to accumulated delta error */
1604 /* deltaY is already flipped */
1605 mouseMoveDeltaX += [anEvent deltaX];
1606 mouseMoveDeltaY += [anEvent deltaY];
1608 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1609 event->mouse_moved.x = mouseMoveDeltaX;
1610 event->mouse_moved.y = mouseMoveDeltaY;
1612 /* Keep the remainder after integer truncation. */
1613 mouseMoveDeltaX -= event->mouse_moved.x;
1614 mouseMoveDeltaY -= event->mouse_moved.y;
1617 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1619 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1620 event->mouse_moved.drag = drag;
1622 [targetWindow.queue postEvent:event];
1625 macdrv_release_event(event);
1627 lastTargetWindow = targetWindow;
1630 lastTargetWindow = nil;
1632 [self updateCursor:FALSE];
1635 - (void) handleMouseButton:(NSEvent*)theEvent
1637 WineWindow* window = (WineWindow*)[theEvent window];
1638 NSEventType type = [theEvent type];
1639 WineWindow* windowBroughtForward = nil;
1640 BOOL process = FALSE;
1642 if ([window isKindOfClass:[WineWindow class]] &&
1643 type == NSLeftMouseDown &&
1644 (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1646 NSWindowButton windowButton;
1648 windowBroughtForward = window;
1650 /* Any left-click on our window anyplace other than the close or
1651 minimize buttons will bring it forward. */
1652 for (windowButton = NSWindowCloseButton;
1653 windowButton <= NSWindowMiniaturizeButton;
1656 NSButton* button = [window standardWindowButton:windowButton];
1659 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1660 if ([button mouse:point inRect:[button bounds]])
1662 windowBroughtForward = nil;
1669 if ([windowsBeingDragged count])
1671 else if (mouseCaptureWindow)
1672 window = mouseCaptureWindow;
1674 if ([window isKindOfClass:[WineWindow class]])
1676 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1677 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1680 [self clipCursorLocation:&pt];
1684 if (mouseCaptureWindow)
1688 // Test if the click was in the window's content area.
1689 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1690 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1691 process = NSMouseInRect(nspoint, contentRect, NO);
1692 if (process && [window styleMask] & NSResizableWindowMask)
1694 // Ignore clicks in the grow box (resize widget).
1695 HIPoint origin = { 0, 0 };
1696 HIThemeGrowBoxDrawInfo info = { 0 };
1700 info.kind = kHIThemeGrowBoxKindNormal;
1701 info.direction = kThemeGrowRight | kThemeGrowDown;
1702 if ([window styleMask] & NSUtilityWindowMask)
1703 info.size = kHIThemeGrowBoxSizeSmall;
1705 info.size = kHIThemeGrowBoxSizeNormal;
1707 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1708 if (status == noErr)
1710 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1711 NSMinY(contentRect),
1713 bounds.size.height);
1714 process = !NSMouseInRect(nspoint, growBox, NO);
1719 unmatchedMouseDowns |= NSEventMaskFromType(type);
1723 NSEventType downType = type - 1;
1724 NSUInteger downMask = NSEventMaskFromType(downType);
1725 process = (unmatchedMouseDowns & downMask) != 0;
1726 unmatchedMouseDowns &= ~downMask;
1731 macdrv_event* event;
1733 event = macdrv_create_event(MOUSE_BUTTON, window);
1734 event->mouse_button.button = [theEvent buttonNumber];
1735 event->mouse_button.pressed = pressed;
1736 event->mouse_button.x = pt.x;
1737 event->mouse_button.y = pt.y;
1738 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1740 [window.queue postEvent:event];
1742 macdrv_release_event(event);
1746 if (windowBroughtForward)
1748 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1749 NSInteger ancestorNumber = [ancestor windowNumber];
1750 NSInteger ancestorLevel = [ancestor level];
1752 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1754 NSInteger windowNumber = [windowNumberObject integerValue];
1755 if (windowNumber == ancestorNumber)
1757 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1758 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1759 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1761 [ancestor postBroughtForwardEvent];
1765 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1766 [self windowGotFocus:windowBroughtForward];
1769 // Since mouse button events deliver absolute cursor position, the
1770 // accumulating delta from move events is invalidated. Make sure
1771 // next mouse move event starts over from an absolute baseline.
1772 // Also, it's at least possible that the title bar widgets (e.g. close
1773 // button, etc.) could enter an internal event loop on a mouse down that
1774 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1775 // dragged events and, after that, any notion of the cursor position
1776 // computed from accumulating deltas would be wrong.
1777 forceNextMouseMoveAbsolute = TRUE;
1780 - (void) handleScrollWheel:(NSEvent*)theEvent
1784 if (mouseCaptureWindow)
1785 window = mouseCaptureWindow;
1787 window = (WineWindow*)[theEvent window];
1789 if ([window isKindOfClass:[WineWindow class]])
1791 CGEventRef cgevent = [theEvent CGEvent];
1792 CGPoint pt = CGEventGetLocation(cgevent);
1796 [self clipCursorLocation:&pt];
1798 if (mouseCaptureWindow)
1802 // Only process the event if it was in the window's content area.
1803 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1804 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1805 process = NSMouseInRect(nspoint, contentRect, NO);
1810 macdrv_event* event;
1812 BOOL continuous = FALSE;
1814 event = macdrv_create_event(MOUSE_SCROLL, window);
1815 event->mouse_scroll.x = pt.x;
1816 event->mouse_scroll.y = pt.y;
1817 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1819 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1823 /* Continuous scroll wheel events come from high-precision scrolling
1824 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1825 For these, we can get more precise data from the CGEvent API. */
1826 /* Axis 1 is vertical, axis 2 is horizontal. */
1827 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1828 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1832 double pixelsPerLine = 10;
1833 CGEventSourceRef source;
1835 /* The non-continuous values are in units of "lines", not pixels. */
1836 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1838 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1842 x = pixelsPerLine * [theEvent deltaX];
1843 y = pixelsPerLine * [theEvent deltaY];
1846 /* Mac: negative is right or down, positive is left or up.
1847 Win32: negative is left or down, positive is right or up.
1848 So, negate the X scroll value to translate. */
1851 /* The x,y values so far are in pixels. Win32 expects to receive some
1852 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1853 6 times the pixel value. */
1857 if (use_precise_scrolling)
1859 event->mouse_scroll.x_scroll = x;
1860 event->mouse_scroll.y_scroll = y;
1864 /* For non-continuous "clicky" wheels, if there was any motion, make
1865 sure there was at least WHEEL_DELTA motion. This is so, at slow
1866 speeds where the system's acceleration curve is actually reducing the
1867 scroll distance, the user is sure to get some action out of each click.
1868 For example, this is important for rotating though weapons in a
1869 first-person shooter. */
1870 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1871 event->mouse_scroll.x_scroll = 120;
1872 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1873 event->mouse_scroll.x_scroll = -120;
1875 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1876 event->mouse_scroll.y_scroll = 120;
1877 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1878 event->mouse_scroll.y_scroll = -120;
1883 /* If it's been a while since the last scroll event or if the scrolling has
1884 reversed direction, reset the accumulated scroll value. */
1885 if ([theEvent timestamp] - lastScrollTime > 1)
1886 accumScrollX = accumScrollY = 0;
1889 /* The accumulated scroll value is in the opposite direction/sign of the last
1890 scroll. That's because it's the "debt" resulting from over-scrolling in
1891 that direction. We accumulate by adding in the scroll amount and then, if
1892 it has the same sign as the scroll value, we subtract any whole or partial
1893 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1894 scroll direction if the accumulated debt and the new scroll value have the
1896 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1898 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1901 lastScrollTime = [theEvent timestamp];
1906 if (accumScrollX > 0 && x > 0)
1907 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1908 if (accumScrollX < 0 && x < 0)
1909 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1910 if (accumScrollY > 0 && y > 0)
1911 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1912 if (accumScrollY < 0 && y < 0)
1913 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1915 accumScrollX -= event->mouse_scroll.x_scroll;
1916 accumScrollY -= event->mouse_scroll.y_scroll;
1919 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1920 [window.queue postEvent:event];
1922 macdrv_release_event(event);
1924 // Since scroll wheel events deliver absolute cursor position, the
1925 // accumulating delta from move events is invalidated. Make sure next
1926 // mouse move event starts over from an absolute baseline.
1927 forceNextMouseMoveAbsolute = TRUE;
1932 // Returns TRUE if the event was handled and caller should do nothing more
1933 // with it. Returns FALSE if the caller should process it as normal and
1934 // then call -didSendEvent:.
1935 - (BOOL) handleEvent:(NSEvent*)anEvent
1938 NSEventType type = [anEvent type];
1940 if (type == NSFlagsChanged)
1941 self.lastFlagsChanged = anEvent;
1942 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1943 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1945 [self handleMouseMove:anEvent];
1946 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1948 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1949 type == NSRightMouseDown || type == NSRightMouseUp ||
1950 type == NSOtherMouseDown || type == NSOtherMouseUp)
1952 [self handleMouseButton:anEvent];
1953 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1955 else if (type == NSScrollWheel)
1957 [self handleScrollWheel:anEvent];
1958 ret = mouseCaptureWindow != nil;
1960 else if (type == NSKeyUp)
1962 uint16_t keyCode = [anEvent keyCode];
1963 if ([self isKeyPressed:keyCode])
1965 WineWindow* window = (WineWindow*)[anEvent window];
1966 [self noteKey:keyCode pressed:FALSE];
1967 if ([window isKindOfClass:[WineWindow class]])
1968 [window postKeyEvent:anEvent];
1971 else if (type == NSAppKitDefined)
1973 short subtype = [anEvent subtype];
1975 // These subtypes are not documented but they appear to mean
1976 // "a window is being dragged" and "a window is no longer being
1977 // dragged", respectively.
1978 if (subtype == 20 || subtype == 21)
1980 WineWindow* window = (WineWindow*)[anEvent window];
1981 if ([window isKindOfClass:[WineWindow class]])
1983 macdrv_event* event;
1988 [windowsBeingDragged addObject:window];
1989 eventType = WINDOW_DRAG_BEGIN;
1993 [windowsBeingDragged removeObject:window];
1994 eventType = WINDOW_DRAG_END;
1996 [self updateCursorClippingState];
1998 event = macdrv_create_event(eventType, window);
1999 [window.queue postEvent:event];
2000 macdrv_release_event(event);
2008 - (void) didSendEvent:(NSEvent*)anEvent
2010 NSEventType type = [anEvent type];
2012 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2014 NSUInteger modifiers = [anEvent modifierFlags];
2015 if ((modifiers & NSCommandKeyMask) &&
2016 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2018 // Command-Tab and Command-Shift-Tab would normally be intercepted
2019 // by the system to switch applications. If we're seeing it, it's
2020 // presumably because we've captured the displays, preventing
2021 // normal application switching. Do it manually.
2022 [self handleCommandTab];
2027 - (void) setupObservations
2029 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2030 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2031 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2033 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2036 usingBlock:^(NSNotification *note){
2037 NSWindow* window = [note object];
2038 [keyWindows removeObjectIdenticalTo:window];
2039 [keyWindows insertObject:window atIndex:0];
2042 [nc addObserverForName:NSWindowWillCloseNotification
2044 queue:[NSOperationQueue mainQueue]
2045 usingBlock:^(NSNotification *note){
2046 NSWindow* window = [note object];
2047 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2049 [keyWindows removeObjectIdenticalTo:window];
2050 if (window == lastTargetWindow)
2051 lastTargetWindow = nil;
2052 if (window == self.mouseCaptureWindow)
2053 self.mouseCaptureWindow = nil;
2054 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2056 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2057 [self updateFullscreenWindows];
2060 [windowsBeingDragged removeObject:window];
2061 [self updateCursorClippingState];
2064 [nc addObserver:self
2065 selector:@selector(keyboardSelectionDidChange)
2066 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2069 /* The above notification isn't sent unless the NSTextInputContext
2070 class has initialized itself. Poke it. */
2071 [NSTextInputContext self];
2073 [wsnc addObserver:self
2074 selector:@selector(activeSpaceDidChange)
2075 name:NSWorkspaceActiveSpaceDidChangeNotification
2078 [nc addObserver:self
2079 selector:@selector(releaseMouseCapture)
2080 name:NSMenuDidBeginTrackingNotification
2083 [dnc addObserver:self
2084 selector:@selector(releaseMouseCapture)
2085 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2087 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2089 [dnc addObserver:self
2090 selector:@selector(enabledKeyboardInputSourcesChanged)
2091 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2095 - (BOOL) inputSourceIsInputMethod
2097 if (!inputSourceIsInputMethodValid)
2099 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2102 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2103 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2104 CFRelease(inputSource);
2107 inputSourceIsInputMethod = FALSE;
2108 inputSourceIsInputMethodValid = TRUE;
2111 return inputSourceIsInputMethod;
2114 - (void) releaseMouseCapture
2116 // This might be invoked on a background thread by the distributed
2117 // notification center. Shunt it to the main thread.
2118 if (![NSThread isMainThread])
2120 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2124 if (mouseCaptureWindow)
2126 macdrv_event* event;
2128 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2129 [mouseCaptureWindow.queue postEvent:event];
2130 macdrv_release_event(event);
2134 - (void) unminimizeWindowIfNoneVisible
2136 if (![self frontWineWindow])
2138 for (WineWindow* window in [NSApp windows])
2140 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2142 [window deminiaturize:self];
2151 * ---------- NSApplicationDelegate methods ----------
2153 - (void)applicationDidBecomeActive:(NSNotification *)notification
2155 NSNumber* displayID;
2156 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2158 latentDisplayModes = [[NSMutableDictionary alloc] init];
2159 for (displayID in modesToRealize)
2161 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2162 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2165 [self updateCursorClippingState];
2167 [self updateFullscreenWindows];
2168 [self adjustWindowLevels:YES];
2171 [self unminimizeWindowIfNoneVisible];
2174 // If a Wine process terminates abruptly while it has the display captured
2175 // and switched to a different resolution, Mac OS X will uncapture the
2176 // displays and switch their resolutions back. However, the other Wine
2177 // processes won't have their notion of the desktop rect changed back.
2178 // This can lead them to refuse to draw or acknowledge clicks in certain
2179 // portions of their windows.
2181 // To solve this, we synthesize a displays-changed event whenever we're
2182 // activated. This will provoke a re-synchronization of Wine's notion of
2183 // the desktop rect with the actual state.
2184 [self sendDisplaysChanged:TRUE];
2186 // The cursor probably moved while we were inactive. Accumulated mouse
2187 // movement deltas are invalidated. Make sure the next mouse move event
2188 // starts over from an absolute baseline.
2189 forceNextMouseMoveAbsolute = TRUE;
2192 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2194 primaryScreenHeightValid = FALSE;
2195 [self sendDisplaysChanged:FALSE];
2196 [self adjustWindowLevels];
2198 // When the display configuration changes, the cursor position may jump.
2199 // Accumulated mouse movement deltas are invalidated. Make sure the next
2200 // mouse move event starts over from an absolute baseline.
2201 forceNextMouseMoveAbsolute = TRUE;
2204 - (void)applicationDidResignActive:(NSNotification *)notification
2206 macdrv_event* event;
2207 WineEventQueue* queue;
2209 [self updateCursorClippingState];
2211 [self invalidateGotFocusEvents];
2213 event = macdrv_create_event(APP_DEACTIVATED, nil);
2215 [eventQueuesLock lock];
2216 for (queue in eventQueues)
2217 [queue postEvent:event];
2218 [eventQueuesLock unlock];
2220 macdrv_release_event(event);
2222 [self releaseMouseCapture];
2225 - (void) applicationDidUnhide:(NSNotification*)aNotification
2227 [self adjustWindowLevels];
2230 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2232 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2233 // don't count as "visible windows" for this purpose.
2234 [self unminimizeWindowIfNoneVisible];
2238 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2240 NSApplicationTerminateReply ret = NSTerminateNow;
2241 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2242 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2243 macdrv_event* event;
2244 WineEventQueue* queue;
2246 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2248 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2251 case kAEReallyLogOut:
2252 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2254 case kAEShowRestartDialog:
2255 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2257 case kAEShowShutdownDialog:
2258 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2261 event->app_quit_requested.reason = QUIT_REASON_NONE;
2265 [eventQueuesLock lock];
2267 if ([eventQueues count])
2269 for (queue in eventQueues)
2270 [queue postEvent:event];
2271 ret = NSTerminateLater;
2274 [eventQueuesLock unlock];
2276 macdrv_release_event(event);
2281 - (void)applicationWillResignActive:(NSNotification *)notification
2283 [self adjustWindowLevels:NO];
2286 /***********************************************************************
2289 * Run-loop-source perform callback. Pull request blocks from the
2290 * array of queued requests and invoke them.
2292 static void PerformRequest(void *info)
2294 WineApplicationController* controller = [WineApplicationController sharedController];
2298 __block dispatch_block_t block;
2300 dispatch_sync(controller->requestsManipQueue, ^{
2301 if ([controller->requests count])
2303 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2304 [controller->requests removeObjectAtIndex:0];
2318 /***********************************************************************
2321 * Run a block on the main thread asynchronously.
2323 void OnMainThreadAsync(dispatch_block_t block)
2325 WineApplicationController* controller = [WineApplicationController sharedController];
2327 block = [block copy];
2328 dispatch_sync(controller->requestsManipQueue, ^{
2329 [controller->requests addObject:block];
2332 CFRunLoopSourceSignal(controller->requestSource);
2333 CFRunLoopWakeUp(CFRunLoopGetMain());
2338 /***********************************************************************
2341 void LogError(const char* func, NSString* format, ...)
2344 va_start(args, format);
2345 LogErrorv(func, format, args);
2349 /***********************************************************************
2352 void LogErrorv(const char* func, NSString* format, va_list args)
2354 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2356 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2357 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2363 /***********************************************************************
2364 * macdrv_window_rejected_focus
2366 * Pass focus to the next window that hasn't already rejected this same
2367 * WINDOW_GOT_FOCUS event.
2369 void macdrv_window_rejected_focus(const macdrv_event *event)
2372 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2376 /***********************************************************************
2377 * macdrv_get_input_source_info
2379 * Returns the keyboard layout uchr data, keyboard type and input source.
2381 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2384 TISInputSourceRef inputSourceLayout;
2386 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2387 if (inputSourceLayout)
2389 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2390 kTISPropertyUnicodeKeyLayoutData);
2391 *uchr = CFDataCreateCopy(NULL, data);
2392 CFRelease(inputSourceLayout);
2394 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2395 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2396 *input_source = TISCopyCurrentKeyboardInputSource();
2401 /***********************************************************************
2404 * Play the beep sound configured by the user in System Preferences.
2406 void macdrv_beep(void)
2408 OnMainThreadAsync(^{
2413 /***********************************************************************
2414 * macdrv_set_display_mode
2416 int macdrv_set_display_mode(const struct macdrv_display* display,
2417 CGDisplayModeRef display_mode)
2422 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2428 /***********************************************************************
2433 * If name is non-NULL, it is a selector for a class method on NSCursor
2434 * identifying the cursor to set. In that case, frames is ignored. If
2435 * name is NULL, then frames is used.
2437 * frames is an array of dictionaries. Each dictionary is a frame of
2438 * an animated cursor. Under the key "image" is a CGImage for the
2439 * frame. Under the key "duration" is a CFNumber time interval, in
2440 * seconds, for how long that frame is presented before proceeding to
2441 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2442 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2443 * This is the hot spot, measured in pixels down and to the right of the
2444 * top-left corner of the image.
2446 * If the array has exactly 1 element, the cursor is static, not
2447 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2449 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2453 sel = NSSelectorFromString((NSString*)name);
2456 OnMainThreadAsync(^{
2457 WineApplicationController* controller = [WineApplicationController sharedController];
2458 [controller setCursorWithFrames:nil];
2459 controller.cursor = [NSCursor performSelector:sel];
2460 [controller unhideCursor];
2465 NSArray* nsframes = (NSArray*)frames;
2466 if ([nsframes count])
2468 OnMainThreadAsync(^{
2469 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2474 OnMainThreadAsync(^{
2475 WineApplicationController* controller = [WineApplicationController sharedController];
2476 [controller setCursorWithFrames:nil];
2477 [controller hideCursor];
2483 /***********************************************************************
2484 * macdrv_get_cursor_position
2486 * Obtains the current cursor position. Returns zero on failure,
2487 * non-zero on success.
2489 int macdrv_get_cursor_position(CGPoint *pos)
2492 NSPoint location = [NSEvent mouseLocation];
2493 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2494 *pos = NSPointToCGPoint(location);
2500 /***********************************************************************
2501 * macdrv_set_cursor_position
2503 * Sets the cursor position without generating events. Returns zero on
2504 * failure, non-zero on success.
2506 int macdrv_set_cursor_position(CGPoint pos)
2511 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2517 /***********************************************************************
2518 * macdrv_clip_cursor
2520 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2521 * to or larger than the whole desktop region, the cursor is unclipped.
2522 * Returns zero on failure, non-zero on success.
2524 int macdrv_clip_cursor(CGRect rect)
2529 WineApplicationController* controller = [WineApplicationController sharedController];
2530 BOOL clipping = FALSE;
2532 if (!CGRectIsInfinite(rect))
2534 NSRect nsrect = NSRectFromCGRect(rect);
2537 /* Convert the rectangle from top-down coords to bottom-up. */
2538 [controller flipRect:&nsrect];
2541 for (screen in [NSScreen screens])
2543 if (!NSContainsRect(nsrect, [screen frame]))
2552 ret = [controller startClippingCursor:rect];
2554 ret = [controller stopClippingCursor];
2560 /***********************************************************************
2561 * macdrv_set_application_icon
2563 * Set the application icon. The images array contains CGImages. If
2564 * there are more than one, then they represent different sizes or
2565 * color depths from the icon resource. If images is NULL or empty,
2566 * restores the default application image.
2568 void macdrv_set_application_icon(CFArrayRef images)
2570 NSArray* imageArray = (NSArray*)images;
2572 OnMainThreadAsync(^{
2573 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2577 /***********************************************************************
2580 void macdrv_quit_reply(int reply)
2583 [NSApp replyToApplicationShouldTerminate:reply];
2587 /***********************************************************************
2588 * macdrv_using_input_method
2590 int macdrv_using_input_method(void)
2595 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2601 /***********************************************************************
2602 * macdrv_set_mouse_capture_window
2604 void macdrv_set_mouse_capture_window(macdrv_window window)
2606 WineWindow* w = (WineWindow*)window;
2608 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2611 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2615 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2616 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2617 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2619 /***********************************************************************
2620 * macdrv_create_input_source_list
2622 CFArrayRef macdrv_create_input_source_list(void)
2624 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2627 CFArrayRef input_list;
2628 CFDictionaryRef filter_dict;
2629 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2630 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2633 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2634 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2635 input_list = TISCreateInputSourceList(filter_dict, false);
2637 for (i = 0; i < CFArrayGetCount(input_list); i++)
2639 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2640 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2641 CFDictionaryRef entry;
2642 const void *input_keys[3] = { macdrv_input_source_input_key,
2643 macdrv_input_source_type_key,
2644 macdrv_input_source_lang_key };
2645 const void *input_values[3];
2647 input_values[0] = input;
2648 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2649 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2651 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2652 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2654 CFArrayAppendValue(ret, entry);
2657 CFRelease(input_list);
2658 CFRelease(filter_dict);
2664 int macdrv_select_input_source(TISInputSourceRef input_source)
2666 __block int ret = FALSE;
2669 ret = (TISSelectInputSource(input_source) == noErr);