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>
24 #import "cocoa_cursorclipping.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
31 // Private notifications that are reliably dispatched when a window is moved by dragging its titlebar.
32 // The object of the notification is the window being dragged.
33 // Available in macOS 10.12+
34 static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification";
35 static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
41 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
42 @interface NSWindow (WineAutoTabbingExtensions)
44 + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
50 /***********************************************************************
53 * Look up a localized string by its ID in the dictionary.
55 static NSString* WineLocalizedString(unsigned int stringID)
57 NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
58 return [(NSDictionary*)localized_strings objectForKey:key];
62 @implementation WineApplication
64 @synthesize wineController;
66 - (void) sendEvent:(NSEvent*)anEvent
68 if (![wineController handleEvent:anEvent])
70 [super sendEvent:anEvent];
71 [wineController didSendEvent:anEvent];
75 - (void) setWineController:(WineApplicationController*)newController
77 wineController = newController;
78 [self setDelegate:wineController];
84 @interface WineApplicationController ()
86 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
87 @property (copy, nonatomic) NSArray* cursorFrames;
88 @property (retain, nonatomic) NSTimer* cursorTimer;
89 @property (retain, nonatomic) NSCursor* cursor;
90 @property (retain, nonatomic) NSImage* applicationIcon;
91 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
92 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
94 - (void) setupObservations;
95 - (void) applicationDidBecomeActive:(NSNotification *)notification;
97 static void PerformRequest(void *info);
102 @implementation WineApplicationController
104 @synthesize keyboardType, lastFlagsChanged;
105 @synthesize applicationIcon;
106 @synthesize cursorFrames, cursorTimer, cursor;
107 @synthesize mouseCaptureWindow;
108 @synthesize lastSetCursorPositionTime;
112 if (self == [WineApplicationController class])
114 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
115 @"", @"NSQuotedKeystrokeBinding",
116 @"", @"NSRepeatCountBinding",
117 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
119 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
121 if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
122 [NSWindow setAllowsAutomaticWindowTabbing:NO];
126 + (WineApplicationController*) sharedController
128 static WineApplicationController* sharedController;
129 static dispatch_once_t once;
131 dispatch_once(&once, ^{
132 sharedController = [[self alloc] init];
135 return sharedController;
143 CFRunLoopSourceContext context = { 0 };
144 context.perform = PerformRequest;
145 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
151 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
152 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
154 requests = [[NSMutableArray alloc] init];
155 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
157 eventQueues = [[NSMutableArray alloc] init];
158 eventQueuesLock = [[NSLock alloc] init];
160 keyWindows = [[NSMutableArray alloc] init];
162 originalDisplayModes = [[NSMutableDictionary alloc] init];
163 latentDisplayModes = [[NSMutableDictionary alloc] init];
165 windowsBeingDragged = [[NSMutableSet alloc] init];
167 // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
168 if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
170 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
171 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
174 useDragNotifications = NO;
176 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
177 !keyWindows || !originalDisplayModes || !latentDisplayModes)
183 [self setupObservations];
185 keyboardType = LMGetKbdType();
187 if ([NSApp isActive])
188 [self applicationDidBecomeActive:nil];
195 [windowsBeingDragged release];
197 [screenFrameCGRects release];
198 [applicationIcon release];
199 [clipCursorHandler release];
200 [cursorTimer release];
201 [cursorFrames release];
202 [latentDisplayModes release];
203 [originalDisplayModes release];
204 [keyWindows release];
205 [eventQueues release];
206 [eventQueuesLock release];
207 if (requestsManipQueue) dispatch_release(requestsManipQueue);
211 CFRunLoopSourceInvalidate(requestSource);
212 CFRelease(requestSource);
217 - (void) transformProcessToForeground:(BOOL)activateIfTransformed
219 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
223 NSString* bundleName;
227 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
229 if (activateIfTransformed)
230 [NSApp activateIgnoringOtherApps:YES];
232 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
233 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
235 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
236 reason:@"Running Windows program"] retain]; // intentional leak
240 mainMenu = [[[NSMenu alloc] init] autorelease];
243 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
244 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
246 if ([bundleName length])
247 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
249 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
250 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
252 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
253 action:@selector(hideOtherApplications:)
255 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
257 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
258 action:@selector(unhideAllApplications:)
261 [submenu addItem:[NSMenuItem separatorItem]];
263 if ([bundleName length])
264 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
266 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
267 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
268 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
269 item = [[[NSMenuItem alloc] init] autorelease];
270 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
271 [item setSubmenu:submenu];
272 [mainMenu addItem:item];
275 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
276 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
277 action:@selector(performMiniaturize:)
279 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
280 action:@selector(performZoom:)
282 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
283 action:@selector(toggleFullScreen:)
285 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
286 NSEventModifierFlagOption |
287 NSEventModifierFlagControl];
288 [submenu addItem:[NSMenuItem separatorItem]];
289 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
290 action:@selector(arrangeInFront:)
292 item = [[[NSMenuItem alloc] init] autorelease];
293 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
294 [item setSubmenu:submenu];
295 [mainMenu addItem:item];
297 [NSApp setMainMenu:mainMenu];
298 [NSApp setWindowsMenu:submenu];
300 [NSApp setApplicationIconImage:self.applicationIcon];
304 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
306 PerformRequest(NULL);
312 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
313 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
315 inMode:NSDefaultRunLoopMode
318 [NSApp sendEvent:event];
322 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
323 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
328 - (BOOL) registerEventQueue:(WineEventQueue*)queue
330 [eventQueuesLock lock];
331 [eventQueues addObject:queue];
332 [eventQueuesLock unlock];
336 - (void) unregisterEventQueue:(WineEventQueue*)queue
338 [eventQueuesLock lock];
339 [eventQueues removeObjectIdenticalTo:queue];
340 [eventQueuesLock unlock];
343 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
345 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
348 - (double) ticksForEventTime:(NSTimeInterval)eventTime
350 return (eventTime + eventTimeAdjustment) * 1000;
353 /* Invalidate old focus offers across all queues. */
354 - (void) invalidateGotFocusEvents
356 WineEventQueue* queue;
360 [eventQueuesLock lock];
361 for (queue in eventQueues)
363 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
366 [eventQueuesLock unlock];
369 - (void) windowGotFocus:(WineWindow*)window
373 [self invalidateGotFocusEvents];
375 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
376 event->window_got_focus.serial = windowFocusSerial;
378 event->window_got_focus.tried_windows = [triedWindows retain];
380 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
381 [window.queue postEvent:event];
382 macdrv_release_event(event);
385 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
387 if (event->window_got_focus.serial == windowFocusSerial)
389 NSMutableArray* windows = [keyWindows mutableCopy];
390 NSNumber* windowNumber;
393 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
395 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
396 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
397 ![windows containsObject:window])
398 [windows addObject:window];
401 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
402 [triedWindows addObject:(WineWindow*)event->window];
403 for (window in windows)
405 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
407 [window makeKeyWindow];
416 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
418 if (!source1 && !source2)
420 if (!source1 || !source2)
422 return CFEqual(source1, source2);
425 - (void) keyboardSelectionDidChange:(BOOL)force
427 TISInputSourceRef inputSource, inputSourceLayout;
431 NSTextInputContext* context = [NSTextInputContext currentInputContext];
432 if (!context || ![context client])
436 inputSource = TISCopyCurrentKeyboardInputSource();
437 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
438 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
439 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
441 if (inputSource) CFRelease(inputSource);
442 if (inputSourceLayout) CFRelease(inputSourceLayout);
446 if (lastKeyboardInputSource)
447 CFRelease(lastKeyboardInputSource);
448 lastKeyboardInputSource = inputSource;
449 if (lastKeyboardLayoutInputSource)
450 CFRelease(lastKeyboardLayoutInputSource);
451 lastKeyboardLayoutInputSource = inputSourceLayout;
453 inputSourceIsInputMethodValid = FALSE;
455 if (inputSourceLayout)
458 uchr = TISGetInputSourceProperty(inputSourceLayout,
459 kTISPropertyUnicodeKeyLayoutData);
463 WineEventQueue* queue;
465 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
466 event->keyboard_changed.keyboard_type = self.keyboardType;
467 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
468 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
469 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
471 if (event->keyboard_changed.uchr)
473 [eventQueuesLock lock];
475 for (queue in eventQueues)
476 [queue postEvent:event];
478 [eventQueuesLock unlock];
481 macdrv_release_event(event);
486 - (void) keyboardSelectionDidChange
488 [self keyboardSelectionDidChange:NO];
491 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
493 if (newType != keyboardType)
495 keyboardType = newType;
496 [self keyboardSelectionDidChange:YES];
500 - (void) enabledKeyboardInputSourcesChanged
502 macdrv_layout_list_needs_update = TRUE;
505 - (CGFloat) primaryScreenHeight
507 if (!primaryScreenHeightValid)
509 NSArray* screens = [NSScreen screens];
510 NSUInteger count = [screens count];
517 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
518 primaryScreenHeightValid = TRUE;
520 size = count * sizeof(CGRect);
521 if (!screenFrameCGRects)
522 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
524 [screenFrameCGRects setLength:size];
526 rect = [screenFrameCGRects mutableBytes];
527 for (screen in screens)
529 CGRect temp = NSRectToCGRect([screen frame]);
530 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
535 return 1280; /* arbitrary value */
538 return primaryScreenHeight;
541 - (NSPoint) flippedMouseLocation:(NSPoint)point
543 /* This relies on the fact that Cocoa's mouse location points are
544 actually off by one (precisely because they were flipped from
545 Quartz screen coordinates using this same technique). */
546 point.y = [self primaryScreenHeight] - point.y;
550 - (void) flipRect:(NSRect*)rect
552 // We don't use -primaryScreenHeight here so there's no chance of having
553 // out-of-date cached info. This method is called infrequently enough
554 // that getting the screen height each time is not prohibitively expensive.
555 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
558 - (WineWindow*) frontWineWindow
560 NSNumber* windowNumber;
561 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
563 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
564 if ([window isKindOfClass:[WineWindow class]] && [window screen])
565 return (WineWindow*)window;
571 - (void) adjustWindowLevels:(BOOL)active
573 NSArray* windowNumbers;
574 NSMutableArray* wineWindows;
575 NSNumber* windowNumber;
576 NSUInteger nextFloatingIndex = 0;
577 __block NSInteger maxLevel = NSIntegerMin;
578 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
579 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
580 __block WineWindow* prev = nil;
583 if ([NSApp isHidden]) return;
585 windowNumbers = [NSWindow windowNumbersWithOptions:0];
586 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
588 // For the most part, we rely on the window server's ordering of the windows
589 // to be authoritative. The one exception is if the "floating" property of
590 // one of the windows has been changed, it may be in the wrong level and thus
591 // in the order. This method is what's supposed to fix that up. So build
592 // a list of Wine windows sorted first by floating-ness and then by order
593 // as indicated by the window server.
594 for (windowNumber in windowNumbers)
596 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
597 if ([window isKindOfClass:[WineWindow class]])
600 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
602 [wineWindows addObject:window];
606 NSDisableScreenUpdates();
608 // Go from back to front so that all windows in front of one which is
609 // elevated for full-screen are also elevated.
610 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
611 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
612 WineWindow* window = (WineWindow*)obj;
613 NSInteger origLevel = [window level];
614 NSInteger newLevel = [window minimumLevelForActive:active];
618 if (minFloatingLevel <= maxNonfloatingLevel)
619 minFloatingLevel = maxNonfloatingLevel + 1;
620 if (newLevel < minFloatingLevel)
621 newLevel = minFloatingLevel;
624 if (newLevel < maxLevel)
629 if (!window.floating && maxNonfloatingLevel < newLevel)
630 maxNonfloatingLevel = newLevel;
632 if (newLevel != origLevel)
634 [window setLevel:newLevel];
636 if (origLevel < newLevel)
638 // If we increased the level, the window should be toward the
639 // back of its new level (but still ahead of the previous
640 // windows we did this to).
642 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
644 [window orderBack:nil];
648 // If we decreased the level, we want the window at the top
649 // of its new level. -setLevel: is documented to do that on
650 // its own, but that's buggy on Ventura. Since we're looping
651 // back-to-front here, -orderFront: will do the right thing.
652 [window orderFront:nil];
659 NSEnableScreenUpdates();
661 [wineWindows release];
663 // The above took care of the visible windows on the current space. That
664 // leaves windows on other spaces, minimized windows, and windows which
665 // are not ordered in. We want to leave windows on other spaces alone
666 // so the space remains just as they left it (when viewed in Exposé or
667 // Mission Control, for example). We'll adjust the window levels again
668 // after we switch to another space, anyway. Windows which aren't
669 // ordered in will be handled when we order them in. Minimized windows
670 // on the current space should be set to the level they would have gotten
671 // if they were at the front of the windows with the same floating-ness,
672 // because that's where they'll go if/when they are unminimized. Again,
673 // for good measure we'll adjust window levels again when a window is
675 for (window in [NSApp windows])
677 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
678 [window isOnActiveSpace])
680 NSInteger origLevel = [window level];
681 NSInteger newLevel = [window minimumLevelForActive:YES];
682 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
684 if (newLevel < maxLevelForType)
685 newLevel = maxLevelForType;
687 if (newLevel != origLevel)
688 [window setLevel:newLevel];
693 - (void) adjustWindowLevels
695 [self adjustWindowLevels:[NSApp isActive]];
698 - (void) updateFullscreenWindows
700 if (capture_displays_for_fullscreen && [NSApp isActive])
702 BOOL anyFullscreen = FALSE;
703 NSNumber* windowNumber;
704 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
706 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
707 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
709 anyFullscreen = TRUE;
716 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
717 displaysCapturedForFullscreen = TRUE;
719 else if (displaysCapturedForFullscreen)
721 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
722 displaysCapturedForFullscreen = FALSE;
727 - (void) activeSpaceDidChange
729 [self updateFullscreenWindows];
730 [self adjustWindowLevels];
733 - (void) sendDisplaysChanged:(BOOL)activating
736 WineEventQueue* queue;
738 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
739 event->displays_changed.activating = activating;
741 [eventQueuesLock lock];
743 // If we're activating, then we just need one of our threads to get the
744 // event, so it can send it directly to the desktop window. Otherwise,
745 // we need all of the threads to get it because we don't know which owns
746 // the desktop window and only that one will do anything with it.
747 if (activating) event->deliver = 1;
749 for (queue in eventQueues)
750 [queue postEvent:event];
751 [eventQueuesLock unlock];
753 macdrv_release_event(event);
756 // We can compare two modes directly using CFEqual, but that may require that
757 // they are identical to a level that we don't need. In particular, when the
758 // OS switches between the integrated and discrete GPUs, the set of display
759 // modes can change in subtle ways. We're interested in whether two modes
760 // match in their most salient features, even if they aren't identical.
761 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
763 NSString *encoding1, *encoding2;
764 uint32_t ioflags1, ioflags2, different;
765 double refresh1, refresh2;
767 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
768 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
769 if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
770 if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
772 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
773 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
774 if (![encoding1 isEqualToString:encoding2]) return FALSE;
776 ioflags1 = CGDisplayModeGetIOFlags(mode1);
777 ioflags2 = CGDisplayModeGetIOFlags(mode2);
778 different = ioflags1 ^ ioflags2;
779 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
780 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
783 refresh1 = CGDisplayModeGetRefreshRate(mode1);
784 if (refresh1 == 0) refresh1 = 60;
785 refresh2 = CGDisplayModeGetRefreshRate(mode2);
786 if (refresh2 == 0) refresh2 = 60;
787 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
792 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
794 NSMutableArray* ret = [NSMutableArray array];
795 NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
797 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
798 for (id candidateModeObject in modes)
800 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
801 if ([self mode:candidateMode matchesMode:mode])
802 [ret addObject:candidateModeObject];
807 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
810 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
811 CGDisplayModeRef originalMode;
813 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
815 if (originalMode && [self mode:mode matchesMode:originalMode])
817 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
819 CGRestorePermanentDisplayConfiguration();
820 if (!displaysCapturedForFullscreen)
821 CGReleaseAllDisplays();
822 [originalDisplayModes removeAllObjects];
825 else // ... otherwise, try to restore just the one display
827 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
829 mode = (CGDisplayModeRef)modeObject;
830 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
832 [originalDisplayModes removeObjectForKey:displayIDKey];
841 CGDisplayModeRef currentMode;
844 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
846 currentMode = CGDisplayCopyDisplayMode(displayID);
847 if (!currentMode) // Invalid display ID
850 if ([self mode:mode matchesMode:currentMode]) // Already there!
852 CGDisplayModeRelease(currentMode);
856 CGDisplayModeRelease(currentMode);
859 modes = [self modesMatchingMode:mode forDisplay:displayID];
863 [self transformProcessToForeground:YES];
865 BOOL active = [NSApp isActive];
867 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
868 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
872 // If we get here, we have the displays captured. If we don't
873 // know the original mode of the display, the current mode must
874 // be the original. We should re-query the current mode since
875 // another process could have changed it between when we last
876 // checked and when we captured the displays.
878 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
882 for (id modeObject in modes)
884 mode = (CGDisplayModeRef)modeObject;
885 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
892 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
893 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
894 else if (![originalDisplayModes count])
896 CGRestorePermanentDisplayConfiguration();
897 if (!displaysCapturedForFullscreen)
898 CGReleaseAllDisplays();
902 CGDisplayModeRelease(currentMode);
906 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
913 [self adjustWindowLevels];
918 - (BOOL) areDisplaysCaptured
920 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
923 - (void) updateCursor:(BOOL)force
925 if (force || lastTargetWindow)
927 if (clientWantsCursorHidden && !cursorHidden)
933 if (!cursorIsCurrent)
936 cursorIsCurrent = TRUE;
939 if (!clientWantsCursorHidden && cursorHidden)
942 cursorHidden = FALSE;
949 [[NSCursor arrowCursor] set];
950 cursorIsCurrent = FALSE;
955 cursorHidden = FALSE;
962 if (!clientWantsCursorHidden)
964 clientWantsCursorHidden = TRUE;
965 [self updateCursor:TRUE];
969 - (void) unhideCursor
971 if (clientWantsCursorHidden)
973 clientWantsCursorHidden = FALSE;
974 [self updateCursor:FALSE];
978 - (void) setCursor:(NSCursor*)newCursor
980 if (newCursor != cursor)
983 cursor = [newCursor retain];
984 cursorIsCurrent = FALSE;
985 [self updateCursor:FALSE];
991 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
992 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
993 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
994 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
995 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
998 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
999 hotSpot = CGPointZero;
1000 hotSpot = cgpoint_mac_from_win(hotSpot);
1001 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1003 [self unhideCursor];
1006 - (void) nextCursorFrame:(NSTimer*)theTimer
1008 NSDictionary* frame;
1009 NSTimeInterval duration;
1013 if (cursorFrame >= [cursorFrames count])
1017 frame = [cursorFrames objectAtIndex:cursorFrame];
1018 duration = [[frame objectForKey:@"duration"] doubleValue];
1019 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1020 [cursorTimer setFireDate:date];
1023 - (void) setCursorWithFrames:(NSArray*)frames
1025 if (self.cursorFrames == frames)
1028 self.cursorFrames = frames;
1030 [cursorTimer invalidate];
1031 self.cursorTimer = nil;
1035 if ([frames count] > 1)
1037 NSDictionary* frame = [frames objectAtIndex:0];
1038 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1039 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1040 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1043 selector:@selector(nextCursorFrame:)
1045 repeats:YES] autorelease];
1046 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1053 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1055 NSImage* nsimage = nil;
1059 NSSize bestSize = NSZeroSize;
1062 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1064 for (image in images)
1066 CGImageRef cgimage = (CGImageRef)image;
1067 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1070 NSSize size = [imageRep size];
1072 [nsimage addRepresentation:imageRep];
1075 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1080 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1081 [nsimage setSize:bestSize];
1086 self.applicationIcon = nsimage;
1089 - (void) handleCommandTab
1091 if ([NSApp isActive])
1093 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1094 NSRunningApplication* app;
1095 NSRunningApplication* otherValidApp = nil;
1097 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1099 NSNumber* displayID;
1100 for (displayID in originalDisplayModes)
1102 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1103 [latentDisplayModes setObject:(id)mode forKey:displayID];
1104 CGDisplayModeRelease(mode);
1107 CGRestorePermanentDisplayConfiguration();
1108 CGReleaseAllDisplays();
1109 [originalDisplayModes removeAllObjects];
1110 displaysCapturedForFullscreen = FALSE;
1113 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1115 if (![app isEqual:thisApp] && !app.terminated &&
1116 app.activationPolicy == NSApplicationActivationPolicyRegular)
1120 // There's another visible app. Just hide ourselves and let
1121 // the system activate the other app.
1127 otherValidApp = app;
1131 // Didn't find a visible GUI app. Try the Finder or, if that's not
1132 // running, the first hidden GUI app. If even that doesn't work, we
1133 // just fail to switch and remain the active app.
1134 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1135 if (!app) app = otherValidApp;
1137 [app activateWithOptions:0];
1141 - (BOOL) setCursorPosition:(CGPoint)pos
1145 if ([windowsBeingDragged count])
1147 else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1148 ret = [clipCursorHandler setCursorPosition:pos];
1151 if (self.clippingCursor)
1152 [clipCursorHandler clipCursorLocation:&pos];
1154 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1155 // the mouse from the cursor position for 0.25 seconds. This means
1156 // that mouse movement during that interval doesn't move the cursor
1157 // and events carry a constant location (the warped-to position)
1158 // even though they have delta values. For apps which warp the
1159 // cursor frequently (like after every mouse move), this makes
1160 // cursor movement horribly laggy and jerky, as only a fraction of
1161 // mouse move events have any effect.
1163 // On some versions of OS X, it's sufficient to forcibly reassociate
1164 // the mouse and cursor position. On others, it's necessary to set
1165 // the local events suppression interval to 0 for the warp. That's
1166 // deprecated, but I'm not aware of any other way. For good
1167 // measure, we do both.
1168 CGSetLocalEventsSuppressionInterval(0);
1169 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1170 CGSetLocalEventsSuppressionInterval(0.25);
1173 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1175 CGAssociateMouseAndMouseCursorPosition(true);
1181 WineEventQueue* queue;
1183 // Discard all pending mouse move events.
1184 [eventQueuesLock lock];
1185 for (queue in eventQueues)
1187 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1188 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1190 [queue resetMouseEventPositions:pos];
1192 [eventQueuesLock unlock];
1198 - (void) updateWindowsForCursorClipping
1201 for (window in [NSApp windows])
1203 if ([window isKindOfClass:[WineWindow class]])
1204 [window updateForCursorClipping];
1208 - (BOOL) startClippingCursor:(CGRect)rect
1210 if (!clipCursorHandler) {
1211 if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1212 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1214 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1217 if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1220 if (![clipCursorHandler startClippingCursor:rect])
1223 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1225 [self updateWindowsForCursorClipping];
1230 - (BOOL) stopClippingCursor
1232 if (!self.clippingCursor)
1235 if (![clipCursorHandler stopClippingCursor])
1238 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1240 [self updateWindowsForCursorClipping];
1245 - (BOOL) clippingCursor
1247 return clipCursorHandler.clippingCursor;
1250 - (BOOL) isKeyPressed:(uint16_t)keyCode
1252 int bits = sizeof(pressedKeyCodes[0]) * 8;
1253 int index = keyCode / bits;
1254 uint32_t mask = 1 << (keyCode % bits);
1255 return (pressedKeyCodes[index] & mask) != 0;
1258 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1260 int bits = sizeof(pressedKeyCodes[0]) * 8;
1261 int index = keyCode / bits;
1262 uint32_t mask = 1 << (keyCode % bits);
1264 pressedKeyCodes[index] |= mask;
1266 pressedKeyCodes[index] &= ~mask;
1269 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1272 [windowsBeingDragged addObject:window];
1274 [windowsBeingDragged removeObject:window];
1277 - (void) windowWillOrderOut:(WineWindow*)window
1279 if ([windowsBeingDragged containsObject:window])
1281 [self window:window isBeingDragged:NO];
1283 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1284 [window.queue postEvent:event];
1285 macdrv_release_event(event);
1289 - (BOOL) isAnyWineWindowVisible
1291 for (WineWindow* w in [NSApp windows])
1293 if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1300 - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1302 macdrv_event* event;
1307 [windowsBeingDragged addObject:window];
1308 eventType = WINDOW_DRAG_BEGIN;
1312 [windowsBeingDragged removeObject:window];
1313 eventType = WINDOW_DRAG_END;
1316 event = macdrv_create_event(eventType, window);
1317 if (eventType == WINDOW_DRAG_BEGIN)
1318 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1319 [window.queue postEvent:event];
1320 macdrv_release_event(event);
1323 - (void) handleMouseMove:(NSEvent*)anEvent
1325 WineWindow* targetWindow;
1326 BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1328 if ([windowsBeingDragged count])
1330 else if (mouseCaptureWindow)
1331 targetWindow = mouseCaptureWindow;
1333 targetWindow = (WineWindow*)[anEvent window];
1336 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1337 event indicates its window is the main window, even if the cursor is
1338 over a different window. Find the actual WineWindow that is under the
1339 cursor and post the event as being for that window. */
1340 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1341 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1342 NSInteger windowUnderNumber;
1344 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1345 belowWindowWithWindowNumber:0];
1346 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1347 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1351 if ([targetWindow isKindOfClass:[WineWindow class]])
1353 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1354 macdrv_event* event;
1357 // If we recently warped the cursor (other than in our cursor-clipping
1358 // event tap), discard mouse move events until we see an event which is
1359 // later than that time.
1360 if (lastSetCursorPositionTime)
1362 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1365 lastSetCursorPositionTime = 0;
1366 forceNextMouseMoveAbsolute = TRUE;
1369 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1372 forceNextMouseMoveAbsolute = FALSE;
1376 // Send absolute move events if the cursor is in the interior of
1377 // its range. Only send relative moves if the cursor is pinned to
1378 // the boundaries of where it can go. We compute the position
1379 // that's one additional point in the direction of movement. If
1380 // that is outside of the clipping rect or desktop region (the
1381 // union of the screen frames), then we figure the cursor would
1382 // have moved outside if it could but it was pinned.
1383 CGPoint computedPoint = point;
1384 CGFloat deltaX = [anEvent deltaX];
1385 CGFloat deltaY = [anEvent deltaY];
1389 else if (deltaX < -0.001)
1394 else if (deltaY < -0.001)
1397 // Assume cursor is pinned for now
1399 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1401 const CGRect* rects;
1402 NSUInteger count, i;
1404 // Caches screenFrameCGRects if necessary
1405 [self primaryScreenHeight];
1407 rects = [screenFrameCGRects bytes];
1408 count = [screenFrameCGRects length] / sizeof(rects[0]);
1410 for (i = 0; i < count; i++)
1412 if (CGRectContainsPoint(rects[i], computedPoint))
1423 if (self.clippingCursor)
1424 [clipCursorHandler clipCursorLocation:&point];
1425 point = cgpoint_win_from_mac(point);
1427 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1428 event->mouse_moved.x = floor(point.x);
1429 event->mouse_moved.y = floor(point.y);
1431 mouseMoveDeltaX = 0;
1432 mouseMoveDeltaY = 0;
1436 double scale = retina_on ? 2 : 1;
1438 /* Add event delta to accumulated delta error */
1439 /* deltaY is already flipped */
1440 mouseMoveDeltaX += [anEvent deltaX];
1441 mouseMoveDeltaY += [anEvent deltaY];
1443 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1444 event->mouse_moved.x = mouseMoveDeltaX * scale;
1445 event->mouse_moved.y = mouseMoveDeltaY * scale;
1447 /* Keep the remainder after integer truncation. */
1448 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1449 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1452 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1454 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1455 event->mouse_moved.drag = drag;
1457 [targetWindow.queue postEvent:event];
1460 macdrv_release_event(event);
1462 lastTargetWindow = targetWindow;
1465 lastTargetWindow = nil;
1467 [self updateCursor:FALSE];
1470 - (void) handleMouseButton:(NSEvent*)theEvent
1472 WineWindow* window = (WineWindow*)[theEvent window];
1473 NSEventType type = [theEvent type];
1474 WineWindow* windowBroughtForward = nil;
1475 BOOL process = FALSE;
1477 if ([window isKindOfClass:[WineWindow class]] &&
1478 type == NSEventTypeLeftMouseDown &&
1479 ![theEvent wine_commandKeyDown])
1481 NSWindowButton windowButton;
1483 windowBroughtForward = window;
1485 /* Any left-click on our window anyplace other than the close or
1486 minimize buttons will bring it forward. */
1487 for (windowButton = NSWindowCloseButton;
1488 windowButton <= NSWindowMiniaturizeButton;
1491 NSButton* button = [window standardWindowButton:windowButton];
1494 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1495 if ([button mouse:point inRect:[button bounds]])
1497 windowBroughtForward = nil;
1504 if ([windowsBeingDragged count])
1506 else if (mouseCaptureWindow)
1507 window = mouseCaptureWindow;
1509 if ([window isKindOfClass:[WineWindow class]])
1511 BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1512 type == NSEventTypeRightMouseDown ||
1513 type == NSEventTypeOtherMouseDown);
1514 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1516 if (self.clippingCursor)
1517 [clipCursorHandler clipCursorLocation:&pt];
1521 if (mouseCaptureWindow)
1525 // Test if the click was in the window's content area.
1526 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1527 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1528 process = NSMouseInRect(nspoint, contentRect, NO);
1529 if (process && [window styleMask] & NSWindowStyleMaskResizable)
1531 // Ignore clicks in the grow box (resize widget).
1532 HIPoint origin = { 0, 0 };
1533 HIThemeGrowBoxDrawInfo info = { 0 };
1537 info.kind = kHIThemeGrowBoxKindNormal;
1538 info.direction = kThemeGrowRight | kThemeGrowDown;
1539 if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1540 info.size = kHIThemeGrowBoxSizeSmall;
1542 info.size = kHIThemeGrowBoxSizeNormal;
1544 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1545 if (status == noErr)
1547 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1548 NSMinY(contentRect),
1550 bounds.size.height);
1551 process = !NSMouseInRect(nspoint, growBox, NO);
1556 unmatchedMouseDowns |= NSEventMaskFromType(type);
1560 NSEventType downType = type - 1;
1561 NSUInteger downMask = NSEventMaskFromType(downType);
1562 process = (unmatchedMouseDowns & downMask) != 0;
1563 unmatchedMouseDowns &= ~downMask;
1568 macdrv_event* event;
1570 pt = cgpoint_win_from_mac(pt);
1572 event = macdrv_create_event(MOUSE_BUTTON, window);
1573 event->mouse_button.button = [theEvent buttonNumber];
1574 event->mouse_button.pressed = pressed;
1575 event->mouse_button.x = floor(pt.x);
1576 event->mouse_button.y = floor(pt.y);
1577 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1579 [window.queue postEvent:event];
1581 macdrv_release_event(event);
1585 if (windowBroughtForward)
1587 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1588 NSInteger ancestorNumber = [ancestor windowNumber];
1589 NSInteger ancestorLevel = [ancestor level];
1591 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1593 NSInteger windowNumber = [windowNumberObject integerValue];
1594 if (windowNumber == ancestorNumber)
1596 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1597 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1598 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1600 [ancestor postBroughtForwardEvent];
1604 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1605 [self windowGotFocus:windowBroughtForward];
1608 // Since mouse button events deliver absolute cursor position, the
1609 // accumulating delta from move events is invalidated. Make sure
1610 // next mouse move event starts over from an absolute baseline.
1611 // Also, it's at least possible that the title bar widgets (e.g. close
1612 // button, etc.) could enter an internal event loop on a mouse down that
1613 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1614 // dragged events and, after that, any notion of the cursor position
1615 // computed from accumulating deltas would be wrong.
1616 forceNextMouseMoveAbsolute = TRUE;
1619 - (void) handleScrollWheel:(NSEvent*)theEvent
1623 if (mouseCaptureWindow)
1624 window = mouseCaptureWindow;
1626 window = (WineWindow*)[theEvent window];
1628 if ([window isKindOfClass:[WineWindow class]])
1630 CGEventRef cgevent = [theEvent CGEvent];
1631 CGPoint pt = CGEventGetLocation(cgevent);
1634 if (self.clippingCursor)
1635 [clipCursorHandler clipCursorLocation:&pt];
1637 if (mouseCaptureWindow)
1641 // Only process the event if it was in the window's content area.
1642 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1643 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1644 process = NSMouseInRect(nspoint, contentRect, NO);
1649 macdrv_event* event;
1651 BOOL continuous = FALSE;
1653 pt = cgpoint_win_from_mac(pt);
1655 event = macdrv_create_event(MOUSE_SCROLL, window);
1656 event->mouse_scroll.x = floor(pt.x);
1657 event->mouse_scroll.y = floor(pt.y);
1658 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1660 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1664 /* Continuous scroll wheel events come from high-precision scrolling
1665 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1666 For these, we can get more precise data from the CGEvent API. */
1667 /* Axis 1 is vertical, axis 2 is horizontal. */
1668 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1669 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1673 double pixelsPerLine = 10;
1674 CGEventSourceRef source;
1676 /* The non-continuous values are in units of "lines", not pixels. */
1677 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1679 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1683 x = pixelsPerLine * [theEvent deltaX];
1684 y = pixelsPerLine * [theEvent deltaY];
1687 /* Mac: negative is right or down, positive is left or up.
1688 Win32: negative is left or down, positive is right or up.
1689 So, negate the X scroll value to translate. */
1692 /* The x,y values so far are in pixels. Win32 expects to receive some
1693 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1694 6 times the pixel value. */
1698 if (use_precise_scrolling)
1700 event->mouse_scroll.x_scroll = x;
1701 event->mouse_scroll.y_scroll = y;
1705 /* For non-continuous "clicky" wheels, if there was any motion, make
1706 sure there was at least WHEEL_DELTA motion. This is so, at slow
1707 speeds where the system's acceleration curve is actually reducing the
1708 scroll distance, the user is sure to get some action out of each click.
1709 For example, this is important for rotating though weapons in a
1710 first-person shooter. */
1711 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1712 event->mouse_scroll.x_scroll = 120;
1713 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1714 event->mouse_scroll.x_scroll = -120;
1716 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1717 event->mouse_scroll.y_scroll = 120;
1718 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1719 event->mouse_scroll.y_scroll = -120;
1724 /* If it's been a while since the last scroll event or if the scrolling has
1725 reversed direction, reset the accumulated scroll value. */
1726 if ([theEvent timestamp] - lastScrollTime > 1)
1727 accumScrollX = accumScrollY = 0;
1730 /* The accumulated scroll value is in the opposite direction/sign of the last
1731 scroll. That's because it's the "debt" resulting from over-scrolling in
1732 that direction. We accumulate by adding in the scroll amount and then, if
1733 it has the same sign as the scroll value, we subtract any whole or partial
1734 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1735 scroll direction if the accumulated debt and the new scroll value have the
1737 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1739 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1742 lastScrollTime = [theEvent timestamp];
1747 if (accumScrollX > 0 && x > 0)
1748 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1749 if (accumScrollX < 0 && x < 0)
1750 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1751 if (accumScrollY > 0 && y > 0)
1752 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1753 if (accumScrollY < 0 && y < 0)
1754 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1756 accumScrollX -= event->mouse_scroll.x_scroll;
1757 accumScrollY -= event->mouse_scroll.y_scroll;
1760 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1761 [window.queue postEvent:event];
1763 macdrv_release_event(event);
1765 // Since scroll wheel events deliver absolute cursor position, the
1766 // accumulating delta from move events is invalidated. Make sure next
1767 // mouse move event starts over from an absolute baseline.
1768 forceNextMouseMoveAbsolute = TRUE;
1773 // Returns TRUE if the event was handled and caller should do nothing more
1774 // with it. Returns FALSE if the caller should process it as normal and
1775 // then call -didSendEvent:.
1776 - (BOOL) handleEvent:(NSEvent*)anEvent
1779 NSEventType type = [anEvent type];
1781 if (type == NSEventTypeFlagsChanged)
1782 self.lastFlagsChanged = anEvent;
1783 else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1784 type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1786 [self handleMouseMove:anEvent];
1787 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1789 else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1790 type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1791 type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1793 [self handleMouseButton:anEvent];
1794 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1796 else if (type == NSEventTypeScrollWheel)
1798 [self handleScrollWheel:anEvent];
1799 ret = mouseCaptureWindow != nil;
1801 else if (type == NSEventTypeKeyDown)
1803 // -[NSApplication sendEvent:] seems to consume presses of the Help
1804 // key (Insert key on PC keyboards), so we have to bypass it and
1805 // send the event directly to the window.
1806 if (anEvent.keyCode == kVK_Help)
1808 [anEvent.window sendEvent:anEvent];
1812 else if (type == NSEventTypeKeyUp)
1814 uint16_t keyCode = [anEvent keyCode];
1815 if ([self isKeyPressed:keyCode])
1817 WineWindow* window = (WineWindow*)[anEvent window];
1818 [self noteKey:keyCode pressed:FALSE];
1819 if ([window isKindOfClass:[WineWindow class]])
1820 [window postKeyEvent:anEvent];
1823 else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1825 WineWindow *window = (WineWindow *)[anEvent window];
1826 short subtype = [anEvent subtype];
1828 // These subtypes are not documented but they appear to mean
1829 // "a window is being dragged" and "a window is no longer being
1830 // dragged", respectively.
1831 if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1832 [self handleWindowDrag:window begin:(subtype == 20)];
1838 - (void) didSendEvent:(NSEvent*)anEvent
1840 NSEventType type = [anEvent type];
1842 if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1844 NSUInteger modifiers = [anEvent modifierFlags];
1845 if ((modifiers & NSEventModifierFlagCommand) &&
1846 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1848 // Command-Tab and Command-Shift-Tab would normally be intercepted
1849 // by the system to switch applications. If we're seeing it, it's
1850 // presumably because we've captured the displays, preventing
1851 // normal application switching. Do it manually.
1852 [self handleCommandTab];
1857 - (void) setupObservations
1859 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1860 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1861 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1863 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1866 usingBlock:^(NSNotification *note){
1867 NSWindow* window = [note object];
1868 [keyWindows removeObjectIdenticalTo:window];
1869 [keyWindows insertObject:window atIndex:0];
1872 [nc addObserverForName:NSWindowWillCloseNotification
1874 queue:[NSOperationQueue mainQueue]
1875 usingBlock:^(NSNotification *note){
1876 NSWindow* window = [note object];
1877 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1879 [keyWindows removeObjectIdenticalTo:window];
1880 if (window == lastTargetWindow)
1881 lastTargetWindow = nil;
1882 if (window == self.mouseCaptureWindow)
1883 self.mouseCaptureWindow = nil;
1884 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1886 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1887 [self updateFullscreenWindows];
1890 [windowsBeingDragged removeObject:window];
1893 if (useDragNotifications) {
1894 [nc addObserverForName:NSWindowWillStartDraggingNotification
1896 queue:[NSOperationQueue mainQueue]
1897 usingBlock:^(NSNotification *note){
1898 NSWindow* window = [note object];
1899 if ([window isKindOfClass:[WineWindow class]])
1900 [self handleWindowDrag:(WineWindow *)window begin:YES];
1903 [nc addObserverForName:NSWindowDidEndDraggingNotification
1905 queue:[NSOperationQueue mainQueue]
1906 usingBlock:^(NSNotification *note){
1907 NSWindow* window = [note object];
1908 if ([window isKindOfClass:[WineWindow class]])
1909 [self handleWindowDrag:(WineWindow *)window begin:NO];
1913 [nc addObserver:self
1914 selector:@selector(keyboardSelectionDidChange)
1915 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1918 /* The above notification isn't sent unless the NSTextInputContext
1919 class has initialized itself. Poke it. */
1920 [NSTextInputContext self];
1922 [wsnc addObserver:self
1923 selector:@selector(activeSpaceDidChange)
1924 name:NSWorkspaceActiveSpaceDidChangeNotification
1927 [nc addObserver:self
1928 selector:@selector(releaseMouseCapture)
1929 name:NSMenuDidBeginTrackingNotification
1932 [dnc addObserver:self
1933 selector:@selector(releaseMouseCapture)
1934 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1936 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1938 [dnc addObserver:self
1939 selector:@selector(enabledKeyboardInputSourcesChanged)
1940 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1944 - (BOOL) inputSourceIsInputMethod
1946 if (!inputSourceIsInputMethodValid)
1948 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1951 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1952 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1953 CFRelease(inputSource);
1956 inputSourceIsInputMethod = FALSE;
1957 inputSourceIsInputMethodValid = TRUE;
1960 return inputSourceIsInputMethod;
1963 - (void) releaseMouseCapture
1965 // This might be invoked on a background thread by the distributed
1966 // notification center. Shunt it to the main thread.
1967 if (![NSThread isMainThread])
1969 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
1973 if (mouseCaptureWindow)
1975 macdrv_event* event;
1977 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
1978 [mouseCaptureWindow.queue postEvent:event];
1979 macdrv_release_event(event);
1983 - (void) unminimizeWindowIfNoneVisible
1985 if (![self frontWineWindow])
1987 for (WineWindow* window in [NSApp windows])
1989 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1991 [window deminiaturize:self];
1998 - (void) setRetinaMode:(int)mode
2002 [clipCursorHandler setRetinaMode:mode];
2004 for (WineWindow* window in [NSApp windows])
2006 if ([window isKindOfClass:[WineWindow class]])
2007 [window setRetinaMode:mode];
2013 * ---------- NSApplicationDelegate methods ----------
2015 - (void)applicationDidBecomeActive:(NSNotification *)notification
2017 NSNumber* displayID;
2018 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2020 latentDisplayModes = [[NSMutableDictionary alloc] init];
2021 for (displayID in modesToRealize)
2023 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2024 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2027 [self updateFullscreenWindows];
2028 [self adjustWindowLevels:YES];
2031 [self unminimizeWindowIfNoneVisible];
2034 // If a Wine process terminates abruptly while it has the display captured
2035 // and switched to a different resolution, Mac OS X will uncapture the
2036 // displays and switch their resolutions back. However, the other Wine
2037 // processes won't have their notion of the desktop rect changed back.
2038 // This can lead them to refuse to draw or acknowledge clicks in certain
2039 // portions of their windows.
2041 // To solve this, we synthesize a displays-changed event whenever we're
2042 // activated. This will provoke a re-synchronization of Wine's notion of
2043 // the desktop rect with the actual state.
2044 [self sendDisplaysChanged:TRUE];
2046 // The cursor probably moved while we were inactive. Accumulated mouse
2047 // movement deltas are invalidated. Make sure the next mouse move event
2048 // starts over from an absolute baseline.
2049 forceNextMouseMoveAbsolute = TRUE;
2052 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2054 primaryScreenHeightValid = FALSE;
2055 [self sendDisplaysChanged:FALSE];
2056 [self adjustWindowLevels];
2058 // When the display configuration changes, the cursor position may jump.
2059 // Accumulated mouse movement deltas are invalidated. Make sure the next
2060 // mouse move event starts over from an absolute baseline.
2061 forceNextMouseMoveAbsolute = TRUE;
2064 - (void)applicationDidResignActive:(NSNotification *)notification
2066 macdrv_event* event;
2067 WineEventQueue* queue;
2069 [self invalidateGotFocusEvents];
2071 event = macdrv_create_event(APP_DEACTIVATED, nil);
2073 [eventQueuesLock lock];
2074 for (queue in eventQueues)
2075 [queue postEvent:event];
2076 [eventQueuesLock unlock];
2078 macdrv_release_event(event);
2080 [self releaseMouseCapture];
2083 - (void) applicationDidUnhide:(NSNotification*)aNotification
2085 [self adjustWindowLevels];
2088 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2090 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2091 // don't count as "visible windows" for this purpose.
2092 [self unminimizeWindowIfNoneVisible];
2096 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2098 NSApplicationTerminateReply ret = NSTerminateNow;
2099 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2100 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2101 macdrv_event* event;
2102 WineEventQueue* queue;
2104 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2106 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2109 case kAEReallyLogOut:
2110 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2112 case kAEShowRestartDialog:
2113 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2115 case kAEShowShutdownDialog:
2116 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2119 event->app_quit_requested.reason = QUIT_REASON_NONE;
2123 [eventQueuesLock lock];
2125 if ([eventQueues count])
2127 for (queue in eventQueues)
2128 [queue postEvent:event];
2129 ret = NSTerminateLater;
2132 [eventQueuesLock unlock];
2134 macdrv_release_event(event);
2139 - (void)applicationWillBecomeActive:(NSNotification *)notification
2141 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2144 [eventQueuesLock lock];
2145 for (WineEventQueue* queue in eventQueues)
2146 [queue postEvent:event];
2147 [eventQueuesLock unlock];
2149 macdrv_release_event(event);
2152 - (void)applicationWillResignActive:(NSNotification *)notification
2154 [self adjustWindowLevels:NO];
2157 /***********************************************************************
2160 * Run-loop-source perform callback. Pull request blocks from the
2161 * array of queued requests and invoke them.
2163 static void PerformRequest(void *info)
2165 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2166 WineApplicationController* controller = [WineApplicationController sharedController];
2170 __block dispatch_block_t block;
2172 dispatch_sync(controller->requestsManipQueue, ^{
2173 if ([controller->requests count])
2175 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2176 [controller->requests removeObjectAtIndex:0];
2189 pool = [[NSAutoreleasePool alloc] init];
2195 /***********************************************************************
2198 * Run a block on the main thread asynchronously.
2200 void OnMainThreadAsync(dispatch_block_t block)
2202 WineApplicationController* controller = [WineApplicationController sharedController];
2204 block = [block copy];
2205 dispatch_sync(controller->requestsManipQueue, ^{
2206 [controller->requests addObject:block];
2209 CFRunLoopSourceSignal(controller->requestSource);
2210 CFRunLoopWakeUp(CFRunLoopGetMain());
2215 /***********************************************************************
2218 void LogError(const char* func, NSString* format, ...)
2221 va_start(args, format);
2222 LogErrorv(func, format, args);
2226 /***********************************************************************
2229 void LogErrorv(const char* func, NSString* format, va_list args)
2231 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2233 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2234 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2240 /***********************************************************************
2241 * macdrv_window_rejected_focus
2243 * Pass focus to the next window that hasn't already rejected this same
2244 * WINDOW_GOT_FOCUS event.
2246 void macdrv_window_rejected_focus(const macdrv_event *event)
2249 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2253 /***********************************************************************
2254 * macdrv_get_input_source_info
2256 * Returns the keyboard layout uchr data, keyboard type and input source.
2258 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2261 TISInputSourceRef inputSourceLayout;
2263 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2264 if (inputSourceLayout)
2266 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2267 kTISPropertyUnicodeKeyLayoutData);
2268 *uchr = CFDataCreateCopy(NULL, data);
2269 CFRelease(inputSourceLayout);
2271 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2272 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2274 *input_source = TISCopyCurrentKeyboardInputSource();
2279 /***********************************************************************
2282 * Play the beep sound configured by the user in System Preferences.
2284 void macdrv_beep(void)
2286 OnMainThreadAsync(^{
2291 /***********************************************************************
2292 * macdrv_set_display_mode
2294 int macdrv_set_display_mode(const struct macdrv_display* display,
2295 CGDisplayModeRef display_mode)
2300 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2306 /***********************************************************************
2311 * If name is non-NULL, it is a selector for a class method on NSCursor
2312 * identifying the cursor to set. In that case, frames is ignored. If
2313 * name is NULL, then frames is used.
2315 * frames is an array of dictionaries. Each dictionary is a frame of
2316 * an animated cursor. Under the key "image" is a CGImage for the
2317 * frame. Under the key "duration" is a CFNumber time interval, in
2318 * seconds, for how long that frame is presented before proceeding to
2319 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2320 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2321 * This is the hot spot, measured in pixels down and to the right of the
2322 * top-left corner of the image.
2324 * If the array has exactly 1 element, the cursor is static, not
2325 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2327 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2331 sel = NSSelectorFromString((NSString*)name);
2334 OnMainThreadAsync(^{
2335 WineApplicationController* controller = [WineApplicationController sharedController];
2336 [controller setCursorWithFrames:nil];
2337 controller.cursor = [NSCursor performSelector:sel];
2338 [controller unhideCursor];
2343 NSArray* nsframes = (NSArray*)frames;
2344 if ([nsframes count])
2346 OnMainThreadAsync(^{
2347 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2352 OnMainThreadAsync(^{
2353 WineApplicationController* controller = [WineApplicationController sharedController];
2354 [controller setCursorWithFrames:nil];
2355 [controller hideCursor];
2361 /***********************************************************************
2362 * macdrv_get_cursor_position
2364 * Obtains the current cursor position. Returns zero on failure,
2365 * non-zero on success.
2367 int macdrv_get_cursor_position(CGPoint *pos)
2370 NSPoint location = [NSEvent mouseLocation];
2371 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2372 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2378 /***********************************************************************
2379 * macdrv_set_cursor_position
2381 * Sets the cursor position without generating events. Returns zero on
2382 * failure, non-zero on success.
2384 int macdrv_set_cursor_position(CGPoint pos)
2389 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2395 /***********************************************************************
2396 * macdrv_clip_cursor
2398 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2399 * to or larger than the whole desktop region, the cursor is unclipped.
2400 * Returns zero on failure, non-zero on success.
2402 int macdrv_clip_cursor(CGRect r)
2407 WineApplicationController* controller = [WineApplicationController sharedController];
2408 BOOL clipping = FALSE;
2411 if (!CGRectIsInfinite(rect))
2412 rect = cgrect_mac_from_win(rect);
2414 if (!CGRectIsInfinite(rect))
2416 NSRect nsrect = NSRectFromCGRect(rect);
2419 /* Convert the rectangle from top-down coords to bottom-up. */
2420 [controller flipRect:&nsrect];
2423 for (screen in [NSScreen screens])
2425 if (!NSContainsRect(nsrect, [screen frame]))
2434 ret = [controller startClippingCursor:rect];
2436 ret = [controller stopClippingCursor];
2442 /***********************************************************************
2443 * macdrv_set_application_icon
2445 * Set the application icon. The images array contains CGImages. If
2446 * there are more than one, then they represent different sizes or
2447 * color depths from the icon resource. If images is NULL or empty,
2448 * restores the default application image.
2450 void macdrv_set_application_icon(CFArrayRef images)
2452 NSArray* imageArray = (NSArray*)images;
2454 OnMainThreadAsync(^{
2455 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2459 /***********************************************************************
2462 void macdrv_quit_reply(int reply)
2465 [NSApp replyToApplicationShouldTerminate:reply];
2469 /***********************************************************************
2470 * macdrv_using_input_method
2472 int macdrv_using_input_method(void)
2477 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2483 /***********************************************************************
2484 * macdrv_set_mouse_capture_window
2486 void macdrv_set_mouse_capture_window(macdrv_window window)
2488 WineWindow* w = (WineWindow*)window;
2490 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2493 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2497 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2498 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2499 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2501 /***********************************************************************
2502 * macdrv_create_input_source_list
2504 CFArrayRef macdrv_create_input_source_list(void)
2506 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2509 CFArrayRef input_list;
2510 CFDictionaryRef filter_dict;
2511 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2512 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2515 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2516 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2517 input_list = TISCreateInputSourceList(filter_dict, false);
2519 for (i = 0; i < CFArrayGetCount(input_list); i++)
2521 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2522 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2523 CFDictionaryRef entry;
2524 const void *input_keys[3] = { macdrv_input_source_input_key,
2525 macdrv_input_source_type_key,
2526 macdrv_input_source_lang_key };
2527 const void *input_values[3];
2529 input_values[0] = input;
2530 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2531 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2533 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2534 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2536 CFArrayAppendValue(ret, entry);
2539 CFRelease(input_list);
2540 CFRelease(filter_dict);
2546 int macdrv_select_input_source(TISInputSourceRef input_source)
2548 __block int ret = FALSE;
2551 ret = (TISSelectInputSource(input_source) == noErr);
2557 void macdrv_set_cocoa_retina_mode(int new_mode)
2560 [[WineApplicationController sharedController] setRetinaMode:new_mode];
2564 int macdrv_is_any_wine_window_visible(void)
2566 __block int ret = FALSE;
2569 ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];