| /* |
| * MACDRV Cocoa application class |
| * |
| * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #import <Carbon/Carbon.h> |
| #include <dlfcn.h> |
| |
| #import "cocoa_app.h" |
| #import "cocoa_event.h" |
| #import "cocoa_window.h" |
| |
| |
| static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode"; |
| |
| |
| int macdrv_err_on; |
| |
| |
| /*********************************************************************** |
| * WineLocalizedString |
| * |
| * Look up a localized string by its ID in the dictionary. |
| */ |
| static NSString* WineLocalizedString(unsigned int stringID) |
| { |
| NSNumber* key = [NSNumber numberWithUnsignedInt:stringID]; |
| return [(NSDictionary*)localized_strings objectForKey:key]; |
| } |
| |
| |
| @implementation WineApplication |
| |
| @synthesize wineController; |
| |
| - (void) sendEvent:(NSEvent*)anEvent |
| { |
| if (![wineController handleEvent:anEvent]) |
| { |
| [super sendEvent:anEvent]; |
| [wineController didSendEvent:anEvent]; |
| } |
| } |
| |
| - (void) setWineController:(WineApplicationController*)newController |
| { |
| wineController = newController; |
| [self setDelegate:wineController]; |
| } |
| |
| @end |
| |
| |
| @interface WarpRecord : NSObject |
| { |
| CGEventTimestamp timeBefore, timeAfter; |
| CGPoint from, to; |
| } |
| |
| @property (nonatomic) CGEventTimestamp timeBefore; |
| @property (nonatomic) CGEventTimestamp timeAfter; |
| @property (nonatomic) CGPoint from; |
| @property (nonatomic) CGPoint to; |
| |
| @end |
| |
| |
| @implementation WarpRecord |
| |
| @synthesize timeBefore, timeAfter, from, to; |
| |
| @end; |
| |
| |
| @interface WineApplicationController () |
| |
| @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged; |
| @property (copy, nonatomic) NSArray* cursorFrames; |
| @property (retain, nonatomic) NSTimer* cursorTimer; |
| @property (retain, nonatomic) NSCursor* cursor; |
| @property (retain, nonatomic) NSImage* applicationIcon; |
| @property (readonly, nonatomic) BOOL inputSourceIsInputMethod; |
| @property (retain, nonatomic) WineWindow* mouseCaptureWindow; |
| |
| - (void) setupObservations; |
| - (void) applicationDidBecomeActive:(NSNotification *)notification; |
| |
| static void PerformRequest(void *info); |
| |
| @end |
| |
| |
| @implementation WineApplicationController |
| |
| @synthesize keyboardType, lastFlagsChanged; |
| @synthesize applicationIcon; |
| @synthesize cursorFrames, cursorTimer, cursor; |
| @synthesize mouseCaptureWindow; |
| |
| @synthesize clippingCursor; |
| |
| + (void) initialize |
| { |
| if (self == [WineApplicationController class]) |
| { |
| NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys: |
| @"", @"NSQuotedKeystrokeBinding", |
| @"", @"NSRepeatCountBinding", |
| [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled", |
| nil]; |
| [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; |
| } |
| } |
| |
| + (WineApplicationController*) sharedController |
| { |
| static WineApplicationController* sharedController; |
| static dispatch_once_t once; |
| |
| dispatch_once(&once, ^{ |
| sharedController = [[self alloc] init]; |
| }); |
| |
| return sharedController; |
| } |
| |
| - (id) init |
| { |
| self = [super init]; |
| if (self != nil) |
| { |
| CFRunLoopSourceContext context = { 0 }; |
| context.perform = PerformRequest; |
| requestSource = CFRunLoopSourceCreate(NULL, 0, &context); |
| if (!requestSource) |
| { |
| [self release]; |
| return nil; |
| } |
| CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes); |
| CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode); |
| |
| requests = [[NSMutableArray alloc] init]; |
| requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL); |
| |
| eventQueues = [[NSMutableArray alloc] init]; |
| eventQueuesLock = [[NSLock alloc] init]; |
| |
| keyWindows = [[NSMutableArray alloc] init]; |
| |
| originalDisplayModes = [[NSMutableDictionary alloc] init]; |
| latentDisplayModes = [[NSMutableDictionary alloc] init]; |
| |
| warpRecords = [[NSMutableArray alloc] init]; |
| |
| windowsBeingDragged = [[NSMutableSet alloc] init]; |
| |
| if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock || |
| !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords) |
| { |
| [self release]; |
| return nil; |
| } |
| |
| [self setupObservations]; |
| |
| keyboardType = LMGetKbdType(); |
| |
| if ([NSApp isActive]) |
| [self applicationDidBecomeActive:nil]; |
| } |
| return self; |
| } |
| |
| - (void) dealloc |
| { |
| [windowsBeingDragged release]; |
| [cursor release]; |
| [screenFrameCGRects release]; |
| [applicationIcon release]; |
| [warpRecords release]; |
| [cursorTimer release]; |
| [cursorFrames release]; |
| [latentDisplayModes release]; |
| [originalDisplayModes release]; |
| [keyWindows release]; |
| [eventQueues release]; |
| [eventQueuesLock release]; |
| if (requestsManipQueue) dispatch_release(requestsManipQueue); |
| [requests release]; |
| if (requestSource) |
| { |
| CFRunLoopSourceInvalidate(requestSource); |
| CFRelease(requestSource); |
| } |
| [super dealloc]; |
| } |
| |
| - (void) transformProcessToForeground |
| { |
| if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular) |
| { |
| NSMenu* mainMenu; |
| NSMenu* submenu; |
| NSString* bundleName; |
| NSString* title; |
| NSMenuItem* item; |
| |
| [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; |
| [NSApp activateIgnoringOtherApps:YES]; |
| |
| mainMenu = [[[NSMenu alloc] init] autorelease]; |
| |
| // Application menu |
| submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease]; |
| bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey]; |
| |
| if ([bundleName length]) |
| title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName]; |
| else |
| title = WineLocalizedString(STRING_MENU_ITEM_HIDE); |
| item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""]; |
| |
| item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS) |
| action:@selector(hideOtherApplications:) |
| keyEquivalent:@"h"]; |
| [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask]; |
| |
| item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL) |
| action:@selector(unhideAllApplications:) |
| keyEquivalent:@""]; |
| |
| [submenu addItem:[NSMenuItem separatorItem]]; |
| |
| if ([bundleName length]) |
| title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName]; |
| else |
| title = WineLocalizedString(STRING_MENU_ITEM_QUIT); |
| item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; |
| [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask]; |
| item = [[[NSMenuItem alloc] init] autorelease]; |
| [item setTitle:WineLocalizedString(STRING_MENU_WINE)]; |
| [item setSubmenu:submenu]; |
| [mainMenu addItem:item]; |
| |
| // Window menu |
| submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease]; |
| [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE) |
| action:@selector(performMiniaturize:) |
| keyEquivalent:@""]; |
| [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM) |
| action:@selector(performZoom:) |
| keyEquivalent:@""]; |
| if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)]) |
| { |
| item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN) |
| action:@selector(toggleFullScreen:) |
| keyEquivalent:@"f"]; |
| [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask]; |
| } |
| [submenu addItem:[NSMenuItem separatorItem]]; |
| [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT) |
| action:@selector(arrangeInFront:) |
| keyEquivalent:@""]; |
| item = [[[NSMenuItem alloc] init] autorelease]; |
| [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)]; |
| [item setSubmenu:submenu]; |
| [mainMenu addItem:item]; |
| |
| [NSApp setMainMenu:mainMenu]; |
| [NSApp setWindowsMenu:submenu]; |
| |
| [NSApp setApplicationIconImage:self.applicationIcon]; |
| } |
| } |
| |
| - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents |
| { |
| PerformRequest(NULL); |
| |
| do |
| { |
| if (processEvents) |
| { |
| NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
| NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask |
| untilDate:timeout |
| inMode:NSDefaultRunLoopMode |
| dequeue:YES]; |
| if (event) |
| [NSApp sendEvent:event]; |
| [pool release]; |
| } |
| else |
| [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout]; |
| } while (!*done && [timeout timeIntervalSinceNow] >= 0); |
| |
| return *done; |
| } |
| |
| - (BOOL) registerEventQueue:(WineEventQueue*)queue |
| { |
| [eventQueuesLock lock]; |
| [eventQueues addObject:queue]; |
| [eventQueuesLock unlock]; |
| return TRUE; |
| } |
| |
| - (void) unregisterEventQueue:(WineEventQueue*)queue |
| { |
| [eventQueuesLock lock]; |
| [eventQueues removeObjectIdenticalTo:queue]; |
| [eventQueuesLock unlock]; |
| } |
| |
| - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns |
| { |
| eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC); |
| } |
| |
| - (double) ticksForEventTime:(NSTimeInterval)eventTime |
| { |
| return (eventTime + eventTimeAdjustment) * 1000; |
| } |
| |
| /* Invalidate old focus offers across all queues. */ |
| - (void) invalidateGotFocusEvents |
| { |
| WineEventQueue* queue; |
| |
| windowFocusSerial++; |
| |
| [eventQueuesLock lock]; |
| for (queue in eventQueues) |
| { |
| [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
| forWindow:nil]; |
| } |
| [eventQueuesLock unlock]; |
| } |
| |
| - (void) windowGotFocus:(WineWindow*)window |
| { |
| macdrv_event* event; |
| |
| [self invalidateGotFocusEvents]; |
| |
| event = macdrv_create_event(WINDOW_GOT_FOCUS, window); |
| event->window_got_focus.serial = windowFocusSerial; |
| if (triedWindows) |
| event->window_got_focus.tried_windows = [triedWindows retain]; |
| else |
| event->window_got_focus.tried_windows = [[NSMutableSet alloc] init]; |
| [window.queue postEvent:event]; |
| macdrv_release_event(event); |
| } |
| |
| - (void) windowRejectedFocusEvent:(const macdrv_event*)event |
| { |
| if (event->window_got_focus.serial == windowFocusSerial) |
| { |
| NSMutableArray* windows = [keyWindows mutableCopy]; |
| NSNumber* windowNumber; |
| WineWindow* window; |
| |
| for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces]) |
| { |
| window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]]; |
| if ([window isKindOfClass:[WineWindow class]] && [window screen] && |
| ![windows containsObject:window]) |
| [windows addObject:window]; |
| } |
| |
| triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows; |
| [triedWindows addObject:(WineWindow*)event->window]; |
| for (window in windows) |
| { |
| if (![triedWindows containsObject:window] && [window canBecomeKeyWindow]) |
| { |
| [window makeKeyWindow]; |
| break; |
| } |
| } |
| triedWindows = nil; |
| [windows release]; |
| } |
| } |
| |
| - (void) keyboardSelectionDidChange |
| { |
| TISInputSourceRef inputSourceLayout; |
| |
| inputSourceIsInputMethodValid = FALSE; |
| |
| inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource(); |
| if (inputSourceLayout) |
| { |
| CFDataRef uchr; |
| uchr = TISGetInputSourceProperty(inputSourceLayout, |
| kTISPropertyUnicodeKeyLayoutData); |
| if (uchr) |
| { |
| macdrv_event* event; |
| WineEventQueue* queue; |
| |
| event = macdrv_create_event(KEYBOARD_CHANGED, nil); |
| event->keyboard_changed.keyboard_type = self.keyboardType; |
| event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO); |
| event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr); |
| event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource(); |
| |
| if (event->keyboard_changed.uchr) |
| { |
| [eventQueuesLock lock]; |
| |
| for (queue in eventQueues) |
| [queue postEvent:event]; |
| |
| [eventQueuesLock unlock]; |
| } |
| |
| macdrv_release_event(event); |
| } |
| |
| CFRelease(inputSourceLayout); |
| } |
| } |
| |
| - (void) enabledKeyboardInputSourcesChanged |
| { |
| macdrv_layout_list_needs_update = TRUE; |
| } |
| |
| - (CGFloat) primaryScreenHeight |
| { |
| if (!primaryScreenHeightValid) |
| { |
| NSArray* screens = [NSScreen screens]; |
| NSUInteger count = [screens count]; |
| if (count) |
| { |
| NSUInteger size; |
| CGRect* rect; |
| NSScreen* screen; |
| |
| primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]); |
| primaryScreenHeightValid = TRUE; |
| |
| size = count * sizeof(CGRect); |
| if (!screenFrameCGRects) |
| screenFrameCGRects = [[NSMutableData alloc] initWithLength:size]; |
| else |
| [screenFrameCGRects setLength:size]; |
| |
| rect = [screenFrameCGRects mutableBytes]; |
| for (screen in screens) |
| { |
| CGRect temp = NSRectToCGRect([screen frame]); |
| temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp); |
| *rect++ = temp; |
| } |
| } |
| else |
| return 1280; /* arbitrary value */ |
| } |
| |
| return primaryScreenHeight; |
| } |
| |
| - (NSPoint) flippedMouseLocation:(NSPoint)point |
| { |
| /* This relies on the fact that Cocoa's mouse location points are |
| actually off by one (precisely because they were flipped from |
| Quartz screen coordinates using this same technique). */ |
| point.y = [self primaryScreenHeight] - point.y; |
| return point; |
| } |
| |
| - (void) flipRect:(NSRect*)rect |
| { |
| // We don't use -primaryScreenHeight here so there's no chance of having |
| // out-of-date cached info. This method is called infrequently enough |
| // that getting the screen height each time is not prohibitively expensive. |
| rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect); |
| } |
| |
| - (WineWindow*) frontWineWindow |
| { |
| NSNumber* windowNumber; |
| for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces]) |
| { |
| NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]]; |
| if ([window isKindOfClass:[WineWindow class]] && [window screen]) |
| return (WineWindow*)window; |
| } |
| |
| return nil; |
| } |
| |
| - (void) adjustWindowLevels:(BOOL)active |
| { |
| NSArray* windowNumbers; |
| NSMutableArray* wineWindows; |
| NSNumber* windowNumber; |
| NSUInteger nextFloatingIndex = 0; |
| __block NSInteger maxLevel = NSIntegerMin; |
| __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel; |
| __block NSInteger minFloatingLevel = NSFloatingWindowLevel; |
| __block WineWindow* prev = nil; |
| WineWindow* window; |
| |
| if ([NSApp isHidden]) return; |
| |
| windowNumbers = [NSWindow windowNumbersWithOptions:0]; |
| wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]]; |
| |
| // For the most part, we rely on the window server's ordering of the windows |
| // to be authoritative. The one exception is if the "floating" property of |
| // one of the windows has been changed, it may be in the wrong level and thus |
| // in the order. This method is what's supposed to fix that up. So build |
| // a list of Wine windows sorted first by floating-ness and then by order |
| // as indicated by the window server. |
| for (windowNumber in windowNumbers) |
| { |
| window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]]; |
| if ([window isKindOfClass:[WineWindow class]]) |
| { |
| if (window.floating) |
| [wineWindows insertObject:window atIndex:nextFloatingIndex++]; |
| else |
| [wineWindows addObject:window]; |
| } |
| } |
| |
| NSDisableScreenUpdates(); |
| |
| // Go from back to front so that all windows in front of one which is |
| // elevated for full-screen are also elevated. |
| [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse |
| usingBlock:^(id obj, NSUInteger idx, BOOL *stop){ |
| WineWindow* window = (WineWindow*)obj; |
| NSInteger origLevel = [window level]; |
| NSInteger newLevel = [window minimumLevelForActive:active]; |
| |
| if (window.floating) |
| { |
| if (minFloatingLevel <= maxNonfloatingLevel) |
| minFloatingLevel = maxNonfloatingLevel + 1; |
| if (newLevel < minFloatingLevel) |
| newLevel = minFloatingLevel; |
| } |
| |
| if (newLevel < maxLevel) |
| newLevel = maxLevel; |
| else |
| maxLevel = newLevel; |
| |
| if (!window.floating && maxNonfloatingLevel < newLevel) |
| maxNonfloatingLevel = newLevel; |
| |
| if (newLevel != origLevel) |
| { |
| [window setLevel:newLevel]; |
| |
| // -setLevel: puts the window at the front of its new level. If |
| // we decreased the level, that's good (it was in front of that |
| // level before, so it should still be now). But if we increased |
| // the level, the window should be toward the back (but still |
| // ahead of the previous windows we did this to). |
| if (origLevel < newLevel) |
| { |
| if (prev) |
| [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]]; |
| else |
| [window orderBack:nil]; |
| } |
| } |
| |
| prev = window; |
| }]; |
| |
| NSEnableScreenUpdates(); |
| |
| [wineWindows release]; |
| |
| // The above took care of the visible windows on the current space. That |
| // leaves windows on other spaces, minimized windows, and windows which |
| // are not ordered in. We want to leave windows on other spaces alone |
| // so the space remains just as they left it (when viewed in Exposé or |
| // Mission Control, for example). We'll adjust the window levels again |
| // after we switch to another space, anyway. Windows which aren't |
| // ordered in will be handled when we order them in. Minimized windows |
| // on the current space should be set to the level they would have gotten |
| // if they were at the front of the windows with the same floating-ness, |
| // because that's where they'll go if/when they are unminimized. Again, |
| // for good measure we'll adjust window levels again when a window is |
| // unminimized, too. |
| for (window in [NSApp windows]) |
| { |
| if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] && |
| [window isOnActiveSpace]) |
| { |
| NSInteger origLevel = [window level]; |
| NSInteger newLevel = [window minimumLevelForActive:YES]; |
| NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel; |
| |
| if (newLevel < maxLevelForType) |
| newLevel = maxLevelForType; |
| |
| if (newLevel != origLevel) |
| [window setLevel:newLevel]; |
| } |
| } |
| } |
| |
| - (void) adjustWindowLevels |
| { |
| [self adjustWindowLevels:[NSApp isActive]]; |
| } |
| |
| - (void) updateFullscreenWindows |
| { |
| if (capture_displays_for_fullscreen && [NSApp isActive]) |
| { |
| BOOL anyFullscreen = FALSE; |
| NSNumber* windowNumber; |
| for (windowNumber in [NSWindow windowNumbersWithOptions:0]) |
| { |
| WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]]; |
| if ([window isKindOfClass:[WineWindow class]] && window.fullscreen) |
| { |
| anyFullscreen = TRUE; |
| break; |
| } |
| } |
| |
| if (anyFullscreen) |
| { |
| if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr) |
| displaysCapturedForFullscreen = TRUE; |
| } |
| else if (displaysCapturedForFullscreen) |
| { |
| if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr) |
| displaysCapturedForFullscreen = FALSE; |
| } |
| } |
| } |
| |
| - (void) activeSpaceDidChange |
| { |
| [self updateFullscreenWindows]; |
| [self adjustWindowLevels]; |
| } |
| |
| - (void) sendDisplaysChanged:(BOOL)activating |
| { |
| macdrv_event* event; |
| WineEventQueue* queue; |
| |
| event = macdrv_create_event(DISPLAYS_CHANGED, nil); |
| event->displays_changed.activating = activating; |
| |
| [eventQueuesLock lock]; |
| |
| // If we're activating, then we just need one of our threads to get the |
| // event, so it can send it directly to the desktop window. Otherwise, |
| // we need all of the threads to get it because we don't know which owns |
| // the desktop window and only that one will do anything with it. |
| if (activating) event->deliver = 1; |
| |
| for (queue in eventQueues) |
| [queue postEvent:event]; |
| [eventQueuesLock unlock]; |
| |
| macdrv_release_event(event); |
| } |
| |
| // We can compare two modes directly using CFEqual, but that may require that |
| // they are identical to a level that we don't need. In particular, when the |
| // OS switches between the integrated and discrete GPUs, the set of display |
| // modes can change in subtle ways. We're interested in whether two modes |
| // match in their most salient features, even if they aren't identical. |
| - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2 |
| { |
| NSString *encoding1, *encoding2; |
| uint32_t ioflags1, ioflags2, different; |
| double refresh1, refresh2; |
| |
| if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE; |
| if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE; |
| |
| #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 |
| if (CGDisplayModeGetPixelWidth != NULL && |
| CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE; |
| if (CGDisplayModeGetPixelHeight != NULL && |
| CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE; |
| #endif |
| |
| encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease]; |
| encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease]; |
| if (![encoding1 isEqualToString:encoding2]) return FALSE; |
| |
| ioflags1 = CGDisplayModeGetIOFlags(mode1); |
| ioflags2 = CGDisplayModeGetIOFlags(mode2); |
| different = ioflags1 ^ ioflags2; |
| if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag | |
| kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag)) |
| return FALSE; |
| |
| refresh1 = CGDisplayModeGetRefreshRate(mode1); |
| if (refresh1 == 0) refresh1 = 60; |
| refresh2 = CGDisplayModeGetRefreshRate(mode2); |
| if (refresh2 == 0) refresh2 = 60; |
| if (fabs(refresh1 - refresh2) > 0.1) return FALSE; |
| |
| return TRUE; |
| } |
| |
| - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID |
| { |
| NSMutableArray* ret = [NSMutableArray array]; |
| NSDictionary* options = nil; |
| |
| #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 |
| if (&kCGDisplayShowDuplicateLowResolutionModes != NULL) |
| options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE] |
| forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes]; |
| #endif |
| |
| NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease]; |
| for (id candidateModeObject in modes) |
| { |
| CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject; |
| if ([self mode:candidateMode matchesMode:mode]) |
| [ret addObject:candidateModeObject]; |
| } |
| return ret; |
| } |
| |
| - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID |
| { |
| BOOL ret = FALSE; |
| NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID]; |
| CGDisplayModeRef originalMode; |
| |
| originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey]; |
| |
| if (originalMode && [self mode:mode matchesMode:originalMode]) |
| { |
| if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset |
| { |
| CGRestorePermanentDisplayConfiguration(); |
| if (!displaysCapturedForFullscreen) |
| CGReleaseAllDisplays(); |
| [originalDisplayModes removeAllObjects]; |
| ret = TRUE; |
| } |
| else // ... otherwise, try to restore just the one display |
| { |
| for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID]) |
| { |
| mode = (CGDisplayModeRef)modeObject; |
| if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr) |
| { |
| [originalDisplayModes removeObjectForKey:displayIDKey]; |
| ret = TRUE; |
| break; |
| } |
| } |
| } |
| } |
| else |
| { |
| BOOL active = [NSApp isActive]; |
| CGDisplayModeRef currentMode; |
| NSArray* modes; |
| |
| currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]); |
| if (!currentMode) |
| currentMode = CGDisplayCopyDisplayMode(displayID); |
| if (!currentMode) // Invalid display ID |
| return FALSE; |
| |
| if ([self mode:mode matchesMode:currentMode]) // Already there! |
| { |
| CGDisplayModeRelease(currentMode); |
| return TRUE; |
| } |
| |
| CGDisplayModeRelease(currentMode); |
| currentMode = NULL; |
| |
| modes = [self modesMatchingMode:mode forDisplay:displayID]; |
| if (!modes.count) |
| return FALSE; |
| |
| if ([originalDisplayModes count] || displaysCapturedForFullscreen || |
| !active || CGCaptureAllDisplays() == CGDisplayNoErr) |
| { |
| if (active) |
| { |
| // If we get here, we have the displays captured. If we don't |
| // know the original mode of the display, the current mode must |
| // be the original. We should re-query the current mode since |
| // another process could have changed it between when we last |
| // checked and when we captured the displays. |
| if (!originalMode) |
| originalMode = currentMode = CGDisplayCopyDisplayMode(displayID); |
| |
| if (originalMode) |
| { |
| for (id modeObject in modes) |
| { |
| mode = (CGDisplayModeRef)modeObject; |
| if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr) |
| { |
| ret = TRUE; |
| break; |
| } |
| } |
| } |
| if (ret && !(currentMode && [self mode:mode matchesMode:currentMode])) |
| [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey]; |
| else if (![originalDisplayModes count]) |
| { |
| CGRestorePermanentDisplayConfiguration(); |
| if (!displaysCapturedForFullscreen) |
| CGReleaseAllDisplays(); |
| } |
| |
| if (currentMode) |
| CGDisplayModeRelease(currentMode); |
| } |
| else |
| { |
| [latentDisplayModes setObject:(id)mode forKey:displayIDKey]; |
| ret = TRUE; |
| } |
| } |
| } |
| |
| if (ret) |
| [self adjustWindowLevels]; |
| |
| return ret; |
| } |
| |
| - (BOOL) areDisplaysCaptured |
| { |
| return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen); |
| } |
| |
| - (void) updateCursor:(BOOL)force |
| { |
| if (force || lastTargetWindow) |
| { |
| if (clientWantsCursorHidden && !cursorHidden) |
| { |
| [NSCursor hide]; |
| cursorHidden = TRUE; |
| } |
| |
| if (!cursorIsCurrent) |
| { |
| [cursor set]; |
| cursorIsCurrent = TRUE; |
| } |
| |
| if (!clientWantsCursorHidden && cursorHidden) |
| { |
| [NSCursor unhide]; |
| cursorHidden = FALSE; |
| } |
| } |
| else |
| { |
| if (cursorIsCurrent) |
| { |
| [[NSCursor arrowCursor] set]; |
| cursorIsCurrent = FALSE; |
| } |
| if (cursorHidden) |
| { |
| [NSCursor unhide]; |
| cursorHidden = FALSE; |
| } |
| } |
| } |
| |
| - (void) hideCursor |
| { |
| if (!clientWantsCursorHidden) |
| { |
| clientWantsCursorHidden = TRUE; |
| [self updateCursor:TRUE]; |
| } |
| } |
| |
| - (void) unhideCursor |
| { |
| if (clientWantsCursorHidden) |
| { |
| clientWantsCursorHidden = FALSE; |
| [self updateCursor:FALSE]; |
| } |
| } |
| |
| - (void) setCursor:(NSCursor*)newCursor |
| { |
| if (newCursor != cursor) |
| { |
| [cursor release]; |
| cursor = [newCursor retain]; |
| cursorIsCurrent = FALSE; |
| [self updateCursor:FALSE]; |
| } |
| } |
| |
| - (void) setCursor |
| { |
| NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame]; |
| CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"]; |
| NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize]; |
| CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"]; |
| CGPoint hotSpot; |
| |
| if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot)) |
| hotSpot = CGPointZero; |
| self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease]; |
| [image release]; |
| [self unhideCursor]; |
| } |
| |
| - (void) nextCursorFrame:(NSTimer*)theTimer |
| { |
| NSDictionary* frame; |
| NSTimeInterval duration; |
| NSDate* date; |
| |
| cursorFrame++; |
| if (cursorFrame >= [cursorFrames count]) |
| cursorFrame = 0; |
| [self setCursor]; |
| |
| frame = [cursorFrames objectAtIndex:cursorFrame]; |
| duration = [[frame objectForKey:@"duration"] doubleValue]; |
| date = [[theTimer fireDate] dateByAddingTimeInterval:duration]; |
| [cursorTimer setFireDate:date]; |
| } |
| |
| - (void) setCursorWithFrames:(NSArray*)frames |
| { |
| if (self.cursorFrames == frames) |
| return; |
| |
| self.cursorFrames = frames; |
| cursorFrame = 0; |
| [cursorTimer invalidate]; |
| self.cursorTimer = nil; |
| |
| if ([frames count]) |
| { |
| if ([frames count] > 1) |
| { |
| NSDictionary* frame = [frames objectAtIndex:0]; |
| NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue]; |
| NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration]; |
| self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date |
| interval:1000000 |
| target:self |
| selector:@selector(nextCursorFrame:) |
| userInfo:nil |
| repeats:YES] autorelease]; |
| [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes]; |
| } |
| |
| [self setCursor]; |
| } |
| } |
| |
| - (void) setApplicationIconFromCGImageArray:(NSArray*)images |
| { |
| NSImage* nsimage = nil; |
| |
| if ([images count]) |
| { |
| NSSize bestSize = NSZeroSize; |
| id image; |
| |
| nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease]; |
| |
| for (image in images) |
| { |
| CGImageRef cgimage = (CGImageRef)image; |
| NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage]; |
| if (imageRep) |
| { |
| NSSize size = [imageRep size]; |
| |
| [nsimage addRepresentation:imageRep]; |
| [imageRep release]; |
| |
| if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height)) |
| bestSize = size; |
| } |
| } |
| |
| if ([[nsimage representations] count] && bestSize.width && bestSize.height) |
| [nsimage setSize:bestSize]; |
| else |
| nsimage = nil; |
| } |
| |
| self.applicationIcon = nsimage; |
| } |
| |
| - (void) handleCommandTab |
| { |
| if ([NSApp isActive]) |
| { |
| NSRunningApplication* thisApp = [NSRunningApplication currentApplication]; |
| NSRunningApplication* app; |
| NSRunningApplication* otherValidApp = nil; |
| |
| if ([originalDisplayModes count] || displaysCapturedForFullscreen) |
| { |
| NSNumber* displayID; |
| for (displayID in originalDisplayModes) |
| { |
| CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]); |
| [latentDisplayModes setObject:(id)mode forKey:displayID]; |
| CGDisplayModeRelease(mode); |
| } |
| |
| CGRestorePermanentDisplayConfiguration(); |
| CGReleaseAllDisplays(); |
| [originalDisplayModes removeAllObjects]; |
| displaysCapturedForFullscreen = FALSE; |
| } |
| |
| for (app in [[NSWorkspace sharedWorkspace] runningApplications]) |
| { |
| if (![app isEqual:thisApp] && !app.terminated && |
| app.activationPolicy == NSApplicationActivationPolicyRegular) |
| { |
| if (!app.hidden) |
| { |
| // There's another visible app. Just hide ourselves and let |
| // the system activate the other app. |
| [NSApp hide:self]; |
| return; |
| } |
| |
| if (!otherValidApp) |
| otherValidApp = app; |
| } |
| } |
| |
| // Didn't find a visible GUI app. Try the Finder or, if that's not |
| // running, the first hidden GUI app. If even that doesn't work, we |
| // just fail to switch and remain the active app. |
| app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject]; |
| if (!app) app = otherValidApp; |
| [app unhide]; |
| [app activateWithOptions:0]; |
| } |
| } |
| |
| /* |
| * ---------- Cursor clipping methods ---------- |
| * |
| * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping. |
| * For one simple case, clipping to a 1x1 rectangle, Quartz does have an |
| * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the |
| * general case, we leverage that. We disassociate mouse movements from |
| * the cursor position and then move the cursor manually, keeping it within |
| * the clipping rectangle. |
| * |
| * Moving the cursor manually isn't enough. We need to modify the event |
| * stream so that the events have the new location, too. We need to do |
| * this at a point before the events enter Cocoa, so that Cocoa will assign |
| * the correct window to the event. So, we install a Quartz event tap to |
| * do that. |
| * |
| * Also, there's a complication when we move the cursor. We use |
| * CGWarpMouseCursorPosition(). That doesn't generate mouse movement |
| * events, but the change of cursor position is incorporated into the |
| * deltas of the next mouse move event. When the mouse is disassociated |
| * from the cursor position, we need the deltas to only reflect actual |
| * device movement, not programmatic changes. So, the event tap cancels |
| * out the change caused by our calls to CGWarpMouseCursorPosition(). |
| */ |
| - (void) clipCursorLocation:(CGPoint*)location |
| { |
| if (location->x < CGRectGetMinX(cursorClipRect)) |
| location->x = CGRectGetMinX(cursorClipRect); |
| if (location->y < CGRectGetMinY(cursorClipRect)) |
| location->y = CGRectGetMinY(cursorClipRect); |
| if (location->x > CGRectGetMaxX(cursorClipRect) - 1) |
| location->x = CGRectGetMaxX(cursorClipRect) - 1; |
| if (location->y > CGRectGetMaxY(cursorClipRect) - 1) |
| location->y = CGRectGetMaxY(cursorClipRect) - 1; |
| } |
| |
| - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation |
| { |
| CGPoint oldLocation; |
| |
| if (currentLocation) |
| oldLocation = *currentLocation; |
| else |
| oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]); |
| |
| if (!CGPointEqualToPoint(oldLocation, *newLocation)) |
| { |
| WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease]; |
| CGError err; |
| |
| warpRecord.from = oldLocation; |
| warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC; |
| |
| /* Actually move the cursor. */ |
| err = CGWarpMouseCursorPosition(*newLocation); |
| if (err != kCGErrorSuccess) |
| return FALSE; |
| |
| warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC; |
| *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]); |
| |
| if (!CGPointEqualToPoint(oldLocation, *newLocation)) |
| { |
| warpRecord.to = *newLocation; |
| [warpRecords addObject:warpRecord]; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| - (BOOL) isMouseMoveEventType:(CGEventType)type |
| { |
| switch(type) |
| { |
| case kCGEventMouseMoved: |
| case kCGEventLeftMouseDragged: |
| case kCGEventRightMouseDragged: |
| case kCGEventOtherMouseDragged: |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation |
| { |
| int warpsFinished = 0; |
| for (WarpRecord* warpRecord in warpRecords) |
| { |
| if (warpRecord.timeAfter < eventTime || |
| (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to))) |
| warpsFinished++; |
| else |
| break; |
| } |
| |
| return warpsFinished; |
| } |
| |
| - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy |
| type:(CGEventType)type |
| event:(CGEventRef)event |
| { |
| CGEventTimestamp eventTime; |
| CGPoint eventLocation, cursorLocation; |
| |
| if (type == kCGEventTapDisabledByUserInput) |
| return event; |
| if (type == kCGEventTapDisabledByTimeout) |
| { |
| CGEventTapEnable(cursorClippingEventTap, TRUE); |
| return event; |
| } |
| |
| if (!clippingCursor) |
| return event; |
| |
| eventTime = CGEventGetTimestamp(event); |
| lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC; |
| |
| eventLocation = CGEventGetLocation(event); |
| |
| cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]); |
| |
| if ([self isMouseMoveEventType:type]) |
| { |
| double deltaX, deltaY; |
| int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation]; |
| int i; |
| |
| deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX); |
| deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY); |
| |
| for (i = 0; i < warpsFinished; i++) |
| { |
| WarpRecord* warpRecord = [warpRecords objectAtIndex:0]; |
| deltaX -= warpRecord.to.x - warpRecord.from.x; |
| deltaY -= warpRecord.to.y - warpRecord.from.y; |
| [warpRecords removeObjectAtIndex:0]; |
| } |
| |
| if (warpsFinished) |
| { |
| CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX); |
| CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY); |
| } |
| |
| synthesizedLocation.x += deltaX; |
| synthesizedLocation.y += deltaY; |
| } |
| |
| // If the event is destined for another process, don't clip it. This may |
| // happen if the user activates Exposé or Mission Control. In that case, |
| // our app does not resign active status, so clipping is still in effect, |
| // but the cursor should not actually be clipped. |
| // |
| // In addition, the fact that mouse moves may have been delivered to a |
| // different process means we have to treat the next one we receive as |
| // absolute rather than relative. |
| if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid()) |
| [self clipCursorLocation:&synthesizedLocation]; |
| else |
| lastSetCursorPositionTime = lastEventTapEventTime; |
| |
| [self warpCursorTo:&synthesizedLocation from:&cursorLocation]; |
| if (!CGPointEqualToPoint(eventLocation, synthesizedLocation)) |
| CGEventSetLocation(event, synthesizedLocation); |
| |
| return event; |
| } |
| |
| CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type, |
| CGEventRef event, void *refcon) |
| { |
| WineApplicationController* controller = refcon; |
| return [controller eventTapWithProxy:proxy type:type event:event]; |
| } |
| |
| - (BOOL) installEventTap |
| { |
| ProcessSerialNumber psn; |
| OSErr err; |
| CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) | |
| CGEventMaskBit(kCGEventLeftMouseUp) | |
| CGEventMaskBit(kCGEventRightMouseDown) | |
| CGEventMaskBit(kCGEventRightMouseUp) | |
| CGEventMaskBit(kCGEventMouseMoved) | |
| CGEventMaskBit(kCGEventLeftMouseDragged) | |
| CGEventMaskBit(kCGEventRightMouseDragged) | |
| CGEventMaskBit(kCGEventOtherMouseDown) | |
| CGEventMaskBit(kCGEventOtherMouseUp) | |
| CGEventMaskBit(kCGEventOtherMouseDragged) | |
| CGEventMaskBit(kCGEventScrollWheel); |
| CFRunLoopSourceRef source; |
| void* appServices; |
| OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN); |
| |
| if (cursorClippingEventTap) |
| return TRUE; |
| |
| // We need to get the Mac GetCurrentProcess() from the ApplicationServices |
| // framework with dlsym() because the Win32 function of the same name |
| // obscures it. |
| appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY); |
| if (!appServices) |
| return FALSE; |
| |
| pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess"); |
| if (!pGetCurrentProcess) |
| { |
| dlclose(appServices); |
| return FALSE; |
| } |
| |
| err = pGetCurrentProcess(&psn); |
| dlclose(appServices); |
| if (err != noErr) |
| return FALSE; |
| |
| // We create an annotated session event tap rather than a process-specific |
| // event tap because we need to programmatically move the cursor even when |
| // mouse moves are directed to other processes. We disable our tap when |
| // other processes are active, but things like Exposé are handled by other |
| // processes even when we remain active. |
| cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap, |
| kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self); |
| if (!cursorClippingEventTap) |
| return FALSE; |
| |
| CGEventTapEnable(cursorClippingEventTap, FALSE); |
| |
| source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0); |
| if (!source) |
| { |
| CFRelease(cursorClippingEventTap); |
| cursorClippingEventTap = NULL; |
| return FALSE; |
| } |
| |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); |
| CFRelease(source); |
| return TRUE; |
| } |
| |
| - (BOOL) setCursorPosition:(CGPoint)pos |
| { |
| BOOL ret; |
| |
| if ([windowsBeingDragged count]) |
| ret = FALSE; |
| else if (clippingCursor) |
| { |
| [self clipCursorLocation:&pos]; |
| |
| ret = [self warpCursorTo:&pos from:NULL]; |
| synthesizedLocation = pos; |
| if (ret) |
| { |
| // We want to discard mouse-move events that have already been |
| // through the event tap, because it's too late to account for |
| // the setting of the cursor position with them. However, the |
| // events that may be queued with times after that but before |
| // the above warp can still be used. So, use the last event |
| // tap event time so that -sendEvent: doesn't discard them. |
| lastSetCursorPositionTime = lastEventTapEventTime; |
| } |
| } |
| else |
| { |
| // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates |
| // the mouse from the cursor position for 0.25 seconds. This means |
| // that mouse movement during that interval doesn't move the cursor |
| // and events carry a constant location (the warped-to position) |
| // even though they have delta values. For apps which warp the |
| // cursor frequently (like after every mouse move), this makes |
| // cursor movement horribly laggy and jerky, as only a fraction of |
| // mouse move events have any effect. |
| // |
| // On some versions of OS X, it's sufficient to forcibly reassociate |
| // the mouse and cursor position. On others, it's necessary to set |
| // the local events suppression interval to 0 for the warp. That's |
| // deprecated, but I'm not aware of any other way. For good |
| // measure, we do both. |
| CGSetLocalEventsSuppressionInterval(0); |
| ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess); |
| CGSetLocalEventsSuppressionInterval(0.25); |
| if (ret) |
| { |
| lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime]; |
| |
| CGAssociateMouseAndMouseCursorPosition(true); |
| } |
| } |
| |
| if (ret) |
| { |
| WineEventQueue* queue; |
| |
| // Discard all pending mouse move events. |
| [eventQueuesLock lock]; |
| for (queue in eventQueues) |
| { |
| [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) | |
| event_mask_for_type(MOUSE_MOVED_ABSOLUTE) |
| forWindow:nil]; |
| [queue resetMouseEventPositions:pos]; |
| } |
| [eventQueuesLock unlock]; |
| } |
| |
| return ret; |
| } |
| |
| - (void) activateCursorClipping |
| { |
| if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap)) |
| { |
| CGEventTapEnable(cursorClippingEventTap, TRUE); |
| [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])]; |
| } |
| } |
| |
| - (void) deactivateCursorClipping |
| { |
| if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap)) |
| { |
| CGEventTapEnable(cursorClippingEventTap, FALSE); |
| [warpRecords removeAllObjects]; |
| lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime]; |
| } |
| } |
| |
| - (void) updateCursorClippingState |
| { |
| if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count]) |
| [self activateCursorClipping]; |
| else |
| [self deactivateCursorClipping]; |
| } |
| |
| - (void) updateWindowsForCursorClipping |
| { |
| WineWindow* window; |
| for (window in [NSApp windows]) |
| { |
| if ([window isKindOfClass:[WineWindow class]]) |
| [window updateForCursorClipping]; |
| } |
| } |
| |
| - (BOOL) startClippingCursor:(CGRect)rect |
| { |
| CGError err; |
| |
| if (!cursorClippingEventTap && ![self installEventTap]) |
| return FALSE; |
| |
| if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) && |
| CGEventTapIsEnabled(cursorClippingEventTap)) |
| return TRUE; |
| |
| err = CGAssociateMouseAndMouseCursorPosition(false); |
| if (err != kCGErrorSuccess) |
| return FALSE; |
| |
| clippingCursor = TRUE; |
| cursorClipRect = rect; |
| [self updateCursorClippingState]; |
| [self updateWindowsForCursorClipping]; |
| |
| return TRUE; |
| } |
| |
| - (BOOL) stopClippingCursor |
| { |
| CGError err = CGAssociateMouseAndMouseCursorPosition(true); |
| if (err != kCGErrorSuccess) |
| return FALSE; |
| |
| clippingCursor = FALSE; |
| [self updateCursorClippingState]; |
| [self updateWindowsForCursorClipping]; |
| |
| return TRUE; |
| } |
| |
| - (BOOL) isKeyPressed:(uint16_t)keyCode |
| { |
| int bits = sizeof(pressedKeyCodes[0]) * 8; |
| int index = keyCode / bits; |
| uint32_t mask = 1 << (keyCode % bits); |
| return (pressedKeyCodes[index] & mask) != 0; |
| } |
| |
| - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed |
| { |
| int bits = sizeof(pressedKeyCodes[0]) * 8; |
| int index = keyCode / bits; |
| uint32_t mask = 1 << (keyCode % bits); |
| if (pressed) |
| pressedKeyCodes[index] |= mask; |
| else |
| pressedKeyCodes[index] &= ~mask; |
| } |
| |
| - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged |
| { |
| if (dragged) |
| [windowsBeingDragged addObject:window]; |
| else |
| [windowsBeingDragged removeObject:window]; |
| [self updateCursorClippingState]; |
| } |
| |
| - (void) handleMouseMove:(NSEvent*)anEvent |
| { |
| WineWindow* targetWindow; |
| BOOL drag = [anEvent type] != NSMouseMoved; |
| |
| if ([windowsBeingDragged count]) |
| targetWindow = nil; |
| else if (mouseCaptureWindow) |
| targetWindow = mouseCaptureWindow; |
| else if (drag) |
| targetWindow = (WineWindow*)[anEvent window]; |
| else |
| { |
| /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the |
| event indicates its window is the main window, even if the cursor is |
| over a different window. Find the actual WineWindow that is under the |
| cursor and post the event as being for that window. */ |
| CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]); |
| NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)]; |
| NSInteger windowUnderNumber; |
| |
| windowUnderNumber = [NSWindow windowNumberAtPoint:point |
| belowWindowWithWindowNumber:0]; |
| targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber]; |
| if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO)) |
| targetWindow = nil; |
| } |
| |
| if ([targetWindow isKindOfClass:[WineWindow class]]) |
| { |
| CGPoint point = CGEventGetLocation([anEvent CGEvent]); |
| macdrv_event* event; |
| BOOL absolute; |
| |
| // If we recently warped the cursor (other than in our cursor-clipping |
| // event tap), discard mouse move events until we see an event which is |
| // later than that time. |
| if (lastSetCursorPositionTime) |
| { |
| if ([anEvent timestamp] <= lastSetCursorPositionTime) |
| return; |
| |
| lastSetCursorPositionTime = 0; |
| forceNextMouseMoveAbsolute = TRUE; |
| } |
| |
| if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow) |
| { |
| absolute = TRUE; |
| forceNextMouseMoveAbsolute = FALSE; |
| } |
| else |
| { |
| // Send absolute move events if the cursor is in the interior of |
| // its range. Only send relative moves if the cursor is pinned to |
| // the boundaries of where it can go. We compute the position |
| // that's one additional point in the direction of movement. If |
| // that is outside of the clipping rect or desktop region (the |
| // union of the screen frames), then we figure the cursor would |
| // have moved outside if it could but it was pinned. |
| CGPoint computedPoint = point; |
| CGFloat deltaX = [anEvent deltaX]; |
| CGFloat deltaY = [anEvent deltaY]; |
| |
| if (deltaX > 0.001) |
| computedPoint.x++; |
| else if (deltaX < -0.001) |
| computedPoint.x--; |
| |
| if (deltaY > 0.001) |
| computedPoint.y++; |
| else if (deltaY < -0.001) |
| computedPoint.y--; |
| |
| // Assume cursor is pinned for now |
| absolute = FALSE; |
| if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint)) |
| { |
| const CGRect* rects; |
| NSUInteger count, i; |
| |
| // Caches screenFrameCGRects if necessary |
| [self primaryScreenHeight]; |
| |
| rects = [screenFrameCGRects bytes]; |
| count = [screenFrameCGRects length] / sizeof(rects[0]); |
| |
| for (i = 0; i < count; i++) |
| { |
| if (CGRectContainsPoint(rects[i], computedPoint)) |
| { |
| absolute = TRUE; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (absolute) |
| { |
| if (clippingCursor) |
| [self clipCursorLocation:&point]; |
| point = cgpoint_win_from_mac(point); |
| |
| event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow); |
| event->mouse_moved.x = floor(point.x); |
| event->mouse_moved.y = floor(point.y); |
| |
| mouseMoveDeltaX = 0; |
| mouseMoveDeltaY = 0; |
| } |
| else |
| { |
| double scale = retina_on ? 2 : 1; |
| |
| /* Add event delta to accumulated delta error */ |
| /* deltaY is already flipped */ |
| mouseMoveDeltaX += [anEvent deltaX]; |
| mouseMoveDeltaY += [anEvent deltaY]; |
| |
| event = macdrv_create_event(MOUSE_MOVED, targetWindow); |
| event->mouse_moved.x = mouseMoveDeltaX * scale; |
| event->mouse_moved.y = mouseMoveDeltaY * scale; |
| |
| /* Keep the remainder after integer truncation. */ |
| mouseMoveDeltaX -= event->mouse_moved.x / scale; |
| mouseMoveDeltaY -= event->mouse_moved.y / scale; |
| } |
| |
| if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y) |
| { |
| event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]]; |
| event->mouse_moved.drag = drag; |
| |
| [targetWindow.queue postEvent:event]; |
| } |
| |
| macdrv_release_event(event); |
| |
| lastTargetWindow = targetWindow; |
| } |
| else |
| lastTargetWindow = nil; |
| |
| [self updateCursor:FALSE]; |
| } |
| |
| - (void) handleMouseButton:(NSEvent*)theEvent |
| { |
| WineWindow* window = (WineWindow*)[theEvent window]; |
| NSEventType type = [theEvent type]; |
| WineWindow* windowBroughtForward = nil; |
| BOOL process = FALSE; |
| |
| if ([window isKindOfClass:[WineWindow class]] && |
| type == NSLeftMouseDown && |
| (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask)) |
| { |
| NSWindowButton windowButton; |
| |
| windowBroughtForward = window; |
| |
| /* Any left-click on our window anyplace other than the close or |
| minimize buttons will bring it forward. */ |
| for (windowButton = NSWindowCloseButton; |
| windowButton <= NSWindowMiniaturizeButton; |
| windowButton++) |
| { |
| NSButton* button = [window standardWindowButton:windowButton]; |
| if (button) |
| { |
| NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil]; |
| if ([button mouse:point inRect:[button bounds]]) |
| { |
| windowBroughtForward = nil; |
| break; |
| } |
| } |
| } |
| } |
| |
| if ([windowsBeingDragged count]) |
| window = nil; |
| else if (mouseCaptureWindow) |
| window = mouseCaptureWindow; |
| |
| if ([window isKindOfClass:[WineWindow class]]) |
| { |
| BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown); |
| CGPoint pt = CGEventGetLocation([theEvent CGEvent]); |
| |
| if (clippingCursor) |
| [self clipCursorLocation:&pt]; |
| |
| if (pressed) |
| { |
| if (mouseCaptureWindow) |
| process = TRUE; |
| else |
| { |
| // Test if the click was in the window's content area. |
| NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)]; |
| NSRect contentRect = [window contentRectForFrameRect:[window frame]]; |
| process = NSMouseInRect(nspoint, contentRect, NO); |
| if (process && [window styleMask] & NSResizableWindowMask) |
| { |
| // Ignore clicks in the grow box (resize widget). |
| HIPoint origin = { 0, 0 }; |
| HIThemeGrowBoxDrawInfo info = { 0 }; |
| HIRect bounds; |
| OSStatus status; |
| |
| info.kind = kHIThemeGrowBoxKindNormal; |
| info.direction = kThemeGrowRight | kThemeGrowDown; |
| if ([window styleMask] & NSUtilityWindowMask) |
| info.size = kHIThemeGrowBoxSizeSmall; |
| else |
| info.size = kHIThemeGrowBoxSizeNormal; |
| |
| status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds); |
| if (status == noErr) |
| { |
| NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width, |
| NSMinY(contentRect), |
| bounds.size.width, |
| bounds.size.height); |
| process = !NSMouseInRect(nspoint, growBox, NO); |
| } |
| } |
| } |
| if (process) |
| unmatchedMouseDowns |= NSEventMaskFromType(type); |
| } |
| else |
| { |
| NSEventType downType = type - 1; |
| NSUInteger downMask = NSEventMaskFromType(downType); |
| process = (unmatchedMouseDowns & downMask) != 0; |
| unmatchedMouseDowns &= ~downMask; |
| } |
| |
| if (process) |
| { |
| macdrv_event* event; |
| |
| pt = cgpoint_win_from_mac(pt); |
| |
| event = macdrv_create_event(MOUSE_BUTTON, window); |
| event->mouse_button.button = [theEvent buttonNumber]; |
| event->mouse_button.pressed = pressed; |
| event->mouse_button.x = floor(pt.x); |
| event->mouse_button.y = floor(pt.y); |
| event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]]; |
| |
| [window.queue postEvent:event]; |
| |
| macdrv_release_event(event); |
| } |
| } |
| |
| if (windowBroughtForward) |
| { |
| WineWindow* ancestor = [windowBroughtForward ancestorWineWindow]; |
| NSInteger ancestorNumber = [ancestor windowNumber]; |
| NSInteger ancestorLevel = [ancestor level]; |
| |
| for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0]) |
| { |
| NSInteger windowNumber = [windowNumberObject integerValue]; |
| if (windowNumber == ancestorNumber) |
| break; |
| WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber]; |
| if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] && |
| [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow]) |
| { |
| [ancestor postBroughtForwardEvent]; |
| break; |
| } |
| } |
| if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate) |
| [self windowGotFocus:windowBroughtForward]; |
| } |
| |
| // Since mouse button events deliver absolute cursor position, the |
| // accumulating delta from move events is invalidated. Make sure |
| // next mouse move event starts over from an absolute baseline. |
| // Also, it's at least possible that the title bar widgets (e.g. close |
| // button, etc.) could enter an internal event loop on a mouse down that |
| // wouldn't exit until a mouse up. In that case, we'd miss any mouse |
| // dragged events and, after that, any notion of the cursor position |
| // computed from accumulating deltas would be wrong. |
| forceNextMouseMoveAbsolute = TRUE; |
| } |
| |
| - (void) handleScrollWheel:(NSEvent*)theEvent |
| { |
| WineWindow* window; |
| |
| if (mouseCaptureWindow) |
| window = mouseCaptureWindow; |
| else |
| window = (WineWindow*)[theEvent window]; |
| |
| if ([window isKindOfClass:[WineWindow class]]) |
| { |
| CGEventRef cgevent = [theEvent CGEvent]; |
| CGPoint pt = CGEventGetLocation(cgevent); |
| BOOL process; |
| |
| if (clippingCursor) |
| [self clipCursorLocation:&pt]; |
| |
| if (mouseCaptureWindow) |
| process = TRUE; |
| else |
| { |
| // Only process the event if it was in the window's content area. |
| NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)]; |
| NSRect contentRect = [window contentRectForFrameRect:[window frame]]; |
| process = NSMouseInRect(nspoint, contentRect, NO); |
| } |
| |
| if (process) |
| { |
| macdrv_event* event; |
| double x, y; |
| BOOL continuous = FALSE; |
| |
| pt = cgpoint_win_from_mac(pt); |
| |
| event = macdrv_create_event(MOUSE_SCROLL, window); |
| event->mouse_scroll.x = floor(pt.x); |
| event->mouse_scroll.y = floor(pt.y); |
| event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]]; |
| |
| if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous)) |
| { |
| continuous = TRUE; |
| |
| /* Continuous scroll wheel events come from high-precision scrolling |
| hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads. |
| For these, we can get more precise data from the CGEvent API. */ |
| /* Axis 1 is vertical, axis 2 is horizontal. */ |
| x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2); |
| y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1); |
| } |
| else |
| { |
| double pixelsPerLine = 10; |
| CGEventSourceRef source; |
| |
| /* The non-continuous values are in units of "lines", not pixels. */ |
| if ((source = CGEventCreateSourceFromEvent(cgevent))) |
| { |
| pixelsPerLine = CGEventSourceGetPixelsPerLine(source); |
| CFRelease(source); |
| } |
| |
| x = pixelsPerLine * [theEvent deltaX]; |
| y = pixelsPerLine * [theEvent deltaY]; |
| } |
| |
| /* Mac: negative is right or down, positive is left or up. |
| Win32: negative is left or down, positive is right or up. |
| So, negate the X scroll value to translate. */ |
| x = -x; |
| |
| /* The x,y values so far are in pixels. Win32 expects to receive some |
| fraction of WHEEL_DELTA == 120. By my estimation, that's roughly |
| 6 times the pixel value. */ |
| x *= 6; |
| y *= 6; |
| |
| if (use_precise_scrolling) |
| { |
| event->mouse_scroll.x_scroll = x; |
| event->mouse_scroll.y_scroll = y; |
| |
| if (!continuous) |
| { |
| /* For non-continuous "clicky" wheels, if there was any motion, make |
| sure there was at least WHEEL_DELTA motion. This is so, at slow |
| speeds where the system's acceleration curve is actually reducing the |
| scroll distance, the user is sure to get some action out of each click. |
| For example, this is important for rotating though weapons in a |
| first-person shooter. */ |
| if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120) |
| event->mouse_scroll.x_scroll = 120; |
| else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0) |
| event->mouse_scroll.x_scroll = -120; |
| |
| if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120) |
| event->mouse_scroll.y_scroll = 120; |
| else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0) |
| event->mouse_scroll.y_scroll = -120; |
| } |
| } |
| else |
| { |
| /* If it's been a while since the last scroll event or if the scrolling has |
| reversed direction, reset the accumulated scroll value. */ |
| if ([theEvent timestamp] - lastScrollTime > 1) |
| accumScrollX = accumScrollY = 0; |
| else |
| { |
| /* The accumulated scroll value is in the opposite direction/sign of the last |
| scroll. That's because it's the "debt" resulting from over-scrolling in |
| that direction. We accumulate by adding in the scroll amount and then, if |
| it has the same sign as the scroll value, we subtract any whole or partial |
| WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched |
| scroll direction if the accumulated debt and the new scroll value have the |
| same sign. */ |
| if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0)) |
| accumScrollX = 0; |
| if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0)) |
| accumScrollY = 0; |
| } |
| lastScrollTime = [theEvent timestamp]; |
| |
| accumScrollX += x; |
| accumScrollY += y; |
| |
| if (accumScrollX > 0 && x > 0) |
| event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120); |
| if (accumScrollX < 0 && x < 0) |
| event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120); |
| if (accumScrollY > 0 && y > 0) |
| event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120); |
| if (accumScrollY < 0 && y < 0) |
| event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120); |
| |
| accumScrollX -= event->mouse_scroll.x_scroll; |
| accumScrollY -= event->mouse_scroll.y_scroll; |
| } |
| |
| if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll) |
| [window.queue postEvent:event]; |
| |
| macdrv_release_event(event); |
| |
| // Since scroll wheel events deliver absolute cursor position, the |
| // accumulating delta from move events is invalidated. Make sure next |
| // mouse move event starts over from an absolute baseline. |
| forceNextMouseMoveAbsolute = TRUE; |
| } |
| } |
| } |
| |
| // Returns TRUE if the event was handled and caller should do nothing more |
| // with it. Returns FALSE if the caller should process it as normal and |
| // then call -didSendEvent:. |
| - (BOOL) handleEvent:(NSEvent*)anEvent |
| { |
| BOOL ret = FALSE; |
| NSEventType type = [anEvent type]; |
| |
| if (type == NSFlagsChanged) |
| self.lastFlagsChanged = anEvent; |
| else if (type == NSMouseMoved || type == NSLeftMouseDragged || |
| type == NSRightMouseDragged || type == NSOtherMouseDragged) |
| { |
| [self handleMouseMove:anEvent]; |
| ret = mouseCaptureWindow && ![windowsBeingDragged count]; |
| } |
| else if (type == NSLeftMouseDown || type == NSLeftMouseUp || |
| type == NSRightMouseDown || type == NSRightMouseUp || |
| type == NSOtherMouseDown || type == NSOtherMouseUp) |
| { |
| [self handleMouseButton:anEvent]; |
| ret = mouseCaptureWindow && ![windowsBeingDragged count]; |
| } |
| else if (type == NSScrollWheel) |
| { |
| [self handleScrollWheel:anEvent]; |
| ret = mouseCaptureWindow != nil; |
| } |
| else if (type == NSKeyUp) |
| { |
| uint16_t keyCode = [anEvent keyCode]; |
| if ([self isKeyPressed:keyCode]) |
| { |
| WineWindow* window = (WineWindow*)[anEvent window]; |
| [self noteKey:keyCode pressed:FALSE]; |
| if ([window isKindOfClass:[WineWindow class]]) |
| [window postKeyEvent:anEvent]; |
| } |
| } |
| else if (type == NSAppKitDefined) |
| { |
| short subtype = [anEvent subtype]; |
| |
| // These subtypes are not documented but they appear to mean |
| // "a window is being dragged" and "a window is no longer being |
| // dragged", respectively. |
| if (subtype == 20 || subtype == 21) |
| { |
| WineWindow* window = (WineWindow*)[anEvent window]; |
| if ([window isKindOfClass:[WineWindow class]]) |
| { |
| macdrv_event* event; |
| int eventType; |
| |
| if (subtype == 20) |
| { |
| [windowsBeingDragged addObject:window]; |
| eventType = WINDOW_DRAG_BEGIN; |
| } |
| else |
| { |
| [windowsBeingDragged removeObject:window]; |
| eventType = WINDOW_DRAG_END; |
| } |
| [self updateCursorClippingState]; |
| |
| event = macdrv_create_event(eventType, window); |
| [window.queue postEvent:event]; |
| macdrv_release_event(event); |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| - (void) didSendEvent:(NSEvent*)anEvent |
| { |
| NSEventType type = [anEvent type]; |
| |
| if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab) |
| { |
| NSUInteger modifiers = [anEvent modifierFlags]; |
| if ((modifiers & NSCommandKeyMask) && |
| !(modifiers & (NSControlKeyMask | NSAlternateKeyMask))) |
| { |
| // Command-Tab and Command-Shift-Tab would normally be intercepted |
| // by the system to switch applications. If we're seeing it, it's |
| // presumably because we've captured the displays, preventing |
| // normal application switching. Do it manually. |
| [self handleCommandTab]; |
| } |
| } |
| } |
| |
| - (void) setupObservations |
| { |
| NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; |
| NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter]; |
| |
| [nc addObserverForName:NSWindowDidBecomeKeyNotification |
| object:nil |
| queue:nil |
| usingBlock:^(NSNotification *note){ |
| NSWindow* window = [note object]; |
| [keyWindows removeObjectIdenticalTo:window]; |
| [keyWindows insertObject:window atIndex:0]; |
| }]; |
| |
| [nc addObserverForName:NSWindowWillCloseNotification |
| object:nil |
| queue:[NSOperationQueue mainQueue] |
| usingBlock:^(NSNotification *note){ |
| NSWindow* window = [note object]; |
| if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose]) |
| return; |
| [keyWindows removeObjectIdenticalTo:window]; |
| if (window == lastTargetWindow) |
| lastTargetWindow = nil; |
| if (window == self.mouseCaptureWindow) |
| self.mouseCaptureWindow = nil; |
| if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen]) |
| { |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{ |
| [self updateFullscreenWindows]; |
| }); |
| } |
| [windowsBeingDragged removeObject:window]; |
| [self updateCursorClippingState]; |
| }]; |
| |
| [nc addObserver:self |
| selector:@selector(keyboardSelectionDidChange) |
| name:NSTextInputContextKeyboardSelectionDidChangeNotification |
| object:nil]; |
| |
| /* The above notification isn't sent unless the NSTextInputContext |
| class has initialized itself. Poke it. */ |
| [NSTextInputContext self]; |
| |
| [wsnc addObserver:self |
| selector:@selector(activeSpaceDidChange) |
| name:NSWorkspaceActiveSpaceDidChangeNotification |
| object:nil]; |
| |
| [nc addObserver:self |
| selector:@selector(releaseMouseCapture) |
| name:NSMenuDidBeginTrackingNotification |
| object:nil]; |
| |
| [dnc addObserver:self |
| selector:@selector(releaseMouseCapture) |
| name:@"com.apple.HIToolbox.beginMenuTrackingNotification" |
| object:nil |
| suspensionBehavior:NSNotificationSuspensionBehaviorDrop]; |
| |
| [dnc addObserver:self |
| selector:@selector(enabledKeyboardInputSourcesChanged) |
| name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged |
| object:nil]; |
| } |
| |
| - (BOOL) inputSourceIsInputMethod |
| { |
| if (!inputSourceIsInputMethodValid) |
| { |
| TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource(); |
| if (inputSource) |
| { |
| CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType); |
| inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout); |
| CFRelease(inputSource); |
| } |
| else |
| inputSourceIsInputMethod = FALSE; |
| inputSourceIsInputMethodValid = TRUE; |
| } |
| |
| return inputSourceIsInputMethod; |
| } |
| |
| - (void) releaseMouseCapture |
| { |
| // This might be invoked on a background thread by the distributed |
| // notification center. Shunt it to the main thread. |
| if (![NSThread isMainThread]) |
| { |
| dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; }); |
| return; |
| } |
| |
| if (mouseCaptureWindow) |
| { |
| macdrv_event* event; |
| |
| event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow); |
| [mouseCaptureWindow.queue postEvent:event]; |
| macdrv_release_event(event); |
| } |
| } |
| |
| - (void) unminimizeWindowIfNoneVisible |
| { |
| if (![self frontWineWindow]) |
| { |
| for (WineWindow* window in [NSApp windows]) |
| { |
| if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized]) |
| { |
| [window deminiaturize:self]; |
| break; |
| } |
| } |
| } |
| } |
| |
| - (void) setRetinaMode:(int)mode |
| { |
| retina_on = mode; |
| |
| if (clippingCursor) |
| { |
| double scale = mode ? 0.5 : 2.0; |
| cursorClipRect.origin.x *= scale; |
| cursorClipRect.origin.y *= scale; |
| cursorClipRect.size.width *= scale; |
| cursorClipRect.size.height *= scale; |
| } |
| |
| for (WineWindow* window in [NSApp windows]) |
| { |
| if ([window isKindOfClass:[WineWindow class]]) |
| [window setRetinaMode:mode]; |
| } |
| } |
| |
| |
| /* |
| * ---------- NSApplicationDelegate methods ---------- |
| */ |
| - (void)applicationDidBecomeActive:(NSNotification *)notification |
| { |
| NSNumber* displayID; |
| NSDictionary* modesToRealize = [latentDisplayModes autorelease]; |
| |
| latentDisplayModes = [[NSMutableDictionary alloc] init]; |
| for (displayID in modesToRealize) |
| { |
| CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID]; |
| [self setMode:mode forDisplay:[displayID unsignedIntValue]]; |
| } |
| |
| [self updateCursorClippingState]; |
| |
| [self updateFullscreenWindows]; |
| [self adjustWindowLevels:YES]; |
| |
| if (beenActive) |
| [self unminimizeWindowIfNoneVisible]; |
| beenActive = TRUE; |
| |
| // If a Wine process terminates abruptly while it has the display captured |
| // and switched to a different resolution, Mac OS X will uncapture the |
| // displays and switch their resolutions back. However, the other Wine |
| // processes won't have their notion of the desktop rect changed back. |
| // This can lead them to refuse to draw or acknowledge clicks in certain |
| // portions of their windows. |
| // |
| // To solve this, we synthesize a displays-changed event whenever we're |
| // activated. This will provoke a re-synchronization of Wine's notion of |
| // the desktop rect with the actual state. |
| [self sendDisplaysChanged:TRUE]; |
| |
| // The cursor probably moved while we were inactive. Accumulated mouse |
| // movement deltas are invalidated. Make sure the next mouse move event |
| // starts over from an absolute baseline. |
| forceNextMouseMoveAbsolute = TRUE; |
| } |
| |
| - (void)applicationDidChangeScreenParameters:(NSNotification *)notification |
| { |
| primaryScreenHeightValid = FALSE; |
| [self sendDisplaysChanged:FALSE]; |
| [self adjustWindowLevels]; |
| |
| // When the display configuration changes, the cursor position may jump. |
| // Accumulated mouse movement deltas are invalidated. Make sure the next |
| // mouse move event starts over from an absolute baseline. |
| forceNextMouseMoveAbsolute = TRUE; |
| } |
| |
| - (void)applicationDidResignActive:(NSNotification *)notification |
| { |
| macdrv_event* event; |
| WineEventQueue* queue; |
| |
| [self updateCursorClippingState]; |
| |
| [self invalidateGotFocusEvents]; |
| |
| event = macdrv_create_event(APP_DEACTIVATED, nil); |
| |
| [eventQueuesLock lock]; |
| for (queue in eventQueues) |
| [queue postEvent:event]; |
| [eventQueuesLock unlock]; |
| |
| macdrv_release_event(event); |
| |
| [self releaseMouseCapture]; |
| } |
| |
| - (void) applicationDidUnhide:(NSNotification*)aNotification |
| { |
| [self adjustWindowLevels]; |
| } |
| |
| - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag |
| { |
| // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels |
| // don't count as "visible windows" for this purpose. |
| [self unminimizeWindowIfNoneVisible]; |
| return YES; |
| } |
| |
| - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender |
| { |
| NSApplicationTerminateReply ret = NSTerminateNow; |
| NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager]; |
| NSAppleEventDescriptor* desc = [m currentAppleEvent]; |
| macdrv_event* event; |
| WineEventQueue* queue; |
| |
| event = macdrv_create_event(APP_QUIT_REQUESTED, nil); |
| event->deliver = 1; |
| switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value]) |
| { |
| case kAELogOut: |
| case kAEReallyLogOut: |
| event->app_quit_requested.reason = QUIT_REASON_LOGOUT; |
| break; |
| case kAEShowRestartDialog: |
| event->app_quit_requested.reason = QUIT_REASON_RESTART; |
| break; |
| case kAEShowShutdownDialog: |
| event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN; |
| break; |
| default: |
| event->app_quit_requested.reason = QUIT_REASON_NONE; |
| break; |
| } |
| |
| [eventQueuesLock lock]; |
| |
| if ([eventQueues count]) |
| { |
| for (queue in eventQueues) |
| [queue postEvent:event]; |
| ret = NSTerminateLater; |
| } |
| |
| [eventQueuesLock unlock]; |
| |
| macdrv_release_event(event); |
| |
| return ret; |
| } |
| |
| - (void)applicationWillBecomeActive:(NSNotification *)notification |
| { |
| macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil); |
| event->deliver = 1; |
| |
| [eventQueuesLock lock]; |
| for (WineEventQueue* queue in eventQueues) |
| [queue postEvent:event]; |
| [eventQueuesLock unlock]; |
| |
| macdrv_release_event(event); |
| } |
| |
| - (void)applicationWillResignActive:(NSNotification *)notification |
| { |
| [self adjustWindowLevels:NO]; |
| } |
| |
| /*********************************************************************** |
| * PerformRequest |
| * |
| * Run-loop-source perform callback. Pull request blocks from the |
| * array of queued requests and invoke them. |
| */ |
| static void PerformRequest(void *info) |
| { |
| NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| |
| for (;;) |
| { |
| __block dispatch_block_t block; |
| |
| dispatch_sync(controller->requestsManipQueue, ^{ |
| if ([controller->requests count]) |
| { |
| block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain]; |
| [controller->requests removeObjectAtIndex:0]; |
| } |
| else |
| block = nil; |
| }); |
| |
| if (!block) |
| break; |
| |
| block(); |
| [block release]; |
| |
| [pool release]; |
| pool = [[NSAutoreleasePool alloc] init]; |
| } |
| |
| [pool release]; |
| } |
| |
| /*********************************************************************** |
| * OnMainThreadAsync |
| * |
| * Run a block on the main thread asynchronously. |
| */ |
| void OnMainThreadAsync(dispatch_block_t block) |
| { |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| |
| block = [block copy]; |
| dispatch_sync(controller->requestsManipQueue, ^{ |
| [controller->requests addObject:block]; |
| }); |
| [block release]; |
| CFRunLoopSourceSignal(controller->requestSource); |
| CFRunLoopWakeUp(CFRunLoopGetMain()); |
| } |
| |
| @end |
| |
| /*********************************************************************** |
| * LogError |
| */ |
| void LogError(const char* func, NSString* format, ...) |
| { |
| va_list args; |
| va_start(args, format); |
| LogErrorv(func, format, args); |
| va_end(args); |
| } |
| |
| /*********************************************************************** |
| * LogErrorv |
| */ |
| void LogErrorv(const char* func, NSString* format, va_list args) |
| { |
| NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
| |
| NSString* message = [[NSString alloc] initWithFormat:format arguments:args]; |
| fprintf(stderr, "err:%s:%s", func, [message UTF8String]); |
| [message release]; |
| |
| [pool release]; |
| } |
| |
| /*********************************************************************** |
| * macdrv_window_rejected_focus |
| * |
| * Pass focus to the next window that hasn't already rejected this same |
| * WINDOW_GOT_FOCUS event. |
| */ |
| void macdrv_window_rejected_focus(const macdrv_event *event) |
| { |
| OnMainThread(^{ |
| [[WineApplicationController sharedController] windowRejectedFocusEvent:event]; |
| }); |
| } |
| |
| /*********************************************************************** |
| * macdrv_get_input_source_info |
| * |
| * Returns the keyboard layout uchr data, keyboard type and input source. |
| */ |
| void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source) |
| { |
| OnMainThread(^{ |
| TISInputSourceRef inputSourceLayout; |
| |
| inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource(); |
| if (inputSourceLayout) |
| { |
| CFDataRef data = TISGetInputSourceProperty(inputSourceLayout, |
| kTISPropertyUnicodeKeyLayoutData); |
| *uchr = CFDataCreateCopy(NULL, data); |
| CFRelease(inputSourceLayout); |
| |
| *keyboard_type = [WineApplicationController sharedController].keyboardType; |
| *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO); |
| if (input_source) |
| *input_source = TISCopyCurrentKeyboardInputSource(); |
| } |
| }); |
| } |
| |
| /*********************************************************************** |
| * macdrv_beep |
| * |
| * Play the beep sound configured by the user in System Preferences. |
| */ |
| void macdrv_beep(void) |
| { |
| OnMainThreadAsync(^{ |
| NSBeep(); |
| }); |
| } |
| |
| /*********************************************************************** |
| * macdrv_set_display_mode |
| */ |
| int macdrv_set_display_mode(const struct macdrv_display* display, |
| CGDisplayModeRef display_mode) |
| { |
| __block int ret; |
| |
| OnMainThread(^{ |
| ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID]; |
| }); |
| |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * macdrv_set_cursor |
| * |
| * Set the cursor. |
| * |
| * If name is non-NULL, it is a selector for a class method on NSCursor |
| * identifying the cursor to set. In that case, frames is ignored. If |
| * name is NULL, then frames is used. |
| * |
| * frames is an array of dictionaries. Each dictionary is a frame of |
| * an animated cursor. Under the key "image" is a CGImage for the |
| * frame. Under the key "duration" is a CFNumber time interval, in |
| * seconds, for how long that frame is presented before proceeding to |
| * the next frame. Under the key "hotSpot" is a CFDictionary encoding a |
| * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation(). |
| * This is the hot spot, measured in pixels down and to the right of the |
| * top-left corner of the image. |
| * |
| * If the array has exactly 1 element, the cursor is static, not |
| * animated. If frames is NULL or has 0 elements, the cursor is hidden. |
| */ |
| void macdrv_set_cursor(CFStringRef name, CFArrayRef frames) |
| { |
| SEL sel; |
| |
| sel = NSSelectorFromString((NSString*)name); |
| if (sel) |
| { |
| OnMainThreadAsync(^{ |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| [controller setCursorWithFrames:nil]; |
| controller.cursor = [NSCursor performSelector:sel]; |
| [controller unhideCursor]; |
| }); |
| } |
| else |
| { |
| NSArray* nsframes = (NSArray*)frames; |
| if ([nsframes count]) |
| { |
| OnMainThreadAsync(^{ |
| [[WineApplicationController sharedController] setCursorWithFrames:nsframes]; |
| }); |
| } |
| else |
| { |
| OnMainThreadAsync(^{ |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| [controller setCursorWithFrames:nil]; |
| [controller hideCursor]; |
| }); |
| } |
| } |
| } |
| |
| /*********************************************************************** |
| * macdrv_get_cursor_position |
| * |
| * Obtains the current cursor position. Returns zero on failure, |
| * non-zero on success. |
| */ |
| int macdrv_get_cursor_position(CGPoint *pos) |
| { |
| OnMainThread(^{ |
| NSPoint location = [NSEvent mouseLocation]; |
| location = [[WineApplicationController sharedController] flippedMouseLocation:location]; |
| *pos = cgpoint_win_from_mac(NSPointToCGPoint(location)); |
| }); |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * macdrv_set_cursor_position |
| * |
| * Sets the cursor position without generating events. Returns zero on |
| * failure, non-zero on success. |
| */ |
| int macdrv_set_cursor_position(CGPoint pos) |
| { |
| __block int ret; |
| |
| OnMainThread(^{ |
| ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)]; |
| }); |
| |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * macdrv_clip_cursor |
| * |
| * Sets the cursor cursor clipping rectangle. If the rectangle is equal |
| * to or larger than the whole desktop region, the cursor is unclipped. |
| * Returns zero on failure, non-zero on success. |
| */ |
| int macdrv_clip_cursor(CGRect r) |
| { |
| __block int ret; |
| |
| OnMainThread(^{ |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| BOOL clipping = FALSE; |
| CGRect rect = r; |
| |
| if (!CGRectIsInfinite(rect)) |
| rect = cgrect_mac_from_win(rect); |
| |
| if (!CGRectIsInfinite(rect)) |
| { |
| NSRect nsrect = NSRectFromCGRect(rect); |
| NSScreen* screen; |
| |
| /* Convert the rectangle from top-down coords to bottom-up. */ |
| [controller flipRect:&nsrect]; |
| |
| clipping = FALSE; |
| for (screen in [NSScreen screens]) |
| { |
| if (!NSContainsRect(nsrect, [screen frame])) |
| { |
| clipping = TRUE; |
| break; |
| } |
| } |
| } |
| |
| if (clipping) |
| ret = [controller startClippingCursor:rect]; |
| else |
| ret = [controller stopClippingCursor]; |
| }); |
| |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * macdrv_set_application_icon |
| * |
| * Set the application icon. The images array contains CGImages. If |
| * there are more than one, then they represent different sizes or |
| * color depths from the icon resource. If images is NULL or empty, |
| * restores the default application image. |
| */ |
| void macdrv_set_application_icon(CFArrayRef images) |
| { |
| NSArray* imageArray = (NSArray*)images; |
| |
| OnMainThreadAsync(^{ |
| [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray]; |
| }); |
| } |
| |
| /*********************************************************************** |
| * macdrv_quit_reply |
| */ |
| void macdrv_quit_reply(int reply) |
| { |
| OnMainThread(^{ |
| [NSApp replyToApplicationShouldTerminate:reply]; |
| }); |
| } |
| |
| /*********************************************************************** |
| * macdrv_using_input_method |
| */ |
| int macdrv_using_input_method(void) |
| { |
| __block BOOL ret; |
| |
| OnMainThread(^{ |
| ret = [[WineApplicationController sharedController] inputSourceIsInputMethod]; |
| }); |
| |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * macdrv_set_mouse_capture_window |
| */ |
| void macdrv_set_mouse_capture_window(macdrv_window window) |
| { |
| WineWindow* w = (WineWindow*)window; |
| |
| [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w]; |
| |
| OnMainThread(^{ |
| [[WineApplicationController sharedController] setMouseCaptureWindow:w]; |
| }); |
| } |
| |
| const CFStringRef macdrv_input_source_input_key = CFSTR("input"); |
| const CFStringRef macdrv_input_source_type_key = CFSTR("type"); |
| const CFStringRef macdrv_input_source_lang_key = CFSTR("lang"); |
| |
| /*********************************************************************** |
| * macdrv_create_input_source_list |
| */ |
| CFArrayRef macdrv_create_input_source_list(void) |
| { |
| CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); |
| |
| OnMainThread(^{ |
| CFArrayRef input_list; |
| CFDictionaryRef filter_dict; |
| const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable }; |
| const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue }; |
| int i; |
| |
| filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]), |
| &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| input_list = TISCreateInputSourceList(filter_dict, false); |
| |
| for (i = 0; i < CFArrayGetCount(input_list); i++) |
| { |
| TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i); |
| CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages); |
| CFDictionaryRef entry; |
| const void *input_keys[3] = { macdrv_input_source_input_key, |
| macdrv_input_source_type_key, |
| macdrv_input_source_lang_key }; |
| const void *input_values[3]; |
| |
| input_values[0] = input; |
| input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType); |
| input_values[2] = CFArrayGetValueAtIndex(source_langs, 0); |
| |
| entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]), |
| &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| |
| CFArrayAppendValue(ret, entry); |
| CFRelease(entry); |
| } |
| CFRelease(input_list); |
| CFRelease(filter_dict); |
| }); |
| |
| return ret; |
| } |
| |
| int macdrv_select_input_source(TISInputSourceRef input_source) |
| { |
| __block int ret = FALSE; |
| |
| OnMainThread(^{ |
| ret = (TISSelectInputSource(input_source) == noErr); |
| }); |
| |
| return ret; |
| } |
| |
| void macdrv_set_cocoa_retina_mode(int new_mode) |
| { |
| OnMainThread(^{ |
| [[WineApplicationController sharedController] setRetinaMode:new_mode]; |
| }); |
| } |