2 * MACDRV Cocoa application class
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #import <Carbon/Carbon.h>
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
35 @implementation WineApplication
37 @synthesize wineController;
39 - (void) sendEvent:(NSEvent*)anEvent
41 if (![wineController handleEvent:anEvent])
43 [super sendEvent:anEvent];
44 [wineController didSendEvent:anEvent];
48 - (void) setWineController:(WineApplicationController*)newController
50 wineController = newController;
51 [self setDelegate:wineController];
57 @interface WarpRecord : NSObject
59 CGEventTimestamp timeBefore, timeAfter;
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
71 @implementation WarpRecord
73 @synthesize timeBefore, timeAfter, from, to;
78 @interface WineApplicationController ()
80 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
81 @property (copy, nonatomic) NSArray* cursorFrames;
82 @property (retain, nonatomic) NSTimer* cursorTimer;
83 @property (retain, nonatomic) NSImage* applicationIcon;
84 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
85 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
87 - (void) setupObservations;
88 - (void) applicationDidBecomeActive:(NSNotification *)notification;
90 static void PerformRequest(void *info);
95 @implementation WineApplicationController
97 @synthesize keyboardType, lastFlagsChanged;
98 @synthesize applicationIcon;
99 @synthesize cursorFrames, cursorTimer;
100 @synthesize mouseCaptureWindow;
104 if (self == [WineApplicationController class])
106 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
107 @"", @"NSQuotedKeystrokeBinding",
108 @"", @"NSRepeatCountBinding",
109 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
111 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
115 + (WineApplicationController*) sharedController
117 static WineApplicationController* sharedController;
118 static dispatch_once_t once;
120 dispatch_once(&once, ^{
121 sharedController = [[self alloc] init];
124 return sharedController;
132 CFRunLoopSourceContext context = { 0 };
133 context.perform = PerformRequest;
134 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
140 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
141 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
143 requests = [[NSMutableArray alloc] init];
144 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
146 eventQueues = [[NSMutableArray alloc] init];
147 eventQueuesLock = [[NSLock alloc] init];
149 keyWindows = [[NSMutableArray alloc] init];
151 originalDisplayModes = [[NSMutableDictionary alloc] init];
153 warpRecords = [[NSMutableArray alloc] init];
155 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
156 !keyWindows || !originalDisplayModes || !warpRecords)
162 [self setupObservations];
164 keyboardType = LMGetKbdType();
166 if ([NSApp isActive])
167 [self applicationDidBecomeActive:nil];
174 [screenFrameCGRects release];
175 [applicationIcon release];
176 [warpRecords release];
177 [cursorTimer release];
178 [cursorFrames release];
179 [originalDisplayModes release];
180 [keyWindows release];
181 [eventQueues release];
182 [eventQueuesLock release];
183 if (requestsManipQueue) dispatch_release(requestsManipQueue);
187 CFRunLoopSourceInvalidate(requestSource);
188 CFRelease(requestSource);
193 - (void) transformProcessToForeground
195 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
199 NSString* bundleName;
203 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
204 [NSApp activateIgnoringOtherApps:YES];
206 mainMenu = [[[NSMenu alloc] init] autorelease];
209 submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
210 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
212 if ([bundleName length])
213 title = [NSString stringWithFormat:@"Hide %@", bundleName];
216 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
218 item = [submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
219 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
221 item = [submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
223 [submenu addItem:[NSMenuItem separatorItem]];
225 if ([bundleName length])
226 title = [NSString stringWithFormat:@"Quit %@", bundleName];
229 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
230 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
231 item = [[[NSMenuItem alloc] init] autorelease];
232 [item setTitle:@"Wine"];
233 [item setSubmenu:submenu];
234 [mainMenu addItem:item];
237 submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
238 [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
239 [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
240 [submenu addItem:[NSMenuItem separatorItem]];
241 [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
242 item = [[[NSMenuItem alloc] init] autorelease];
243 [item setTitle:@"Window"];
244 [item setSubmenu:submenu];
245 [mainMenu addItem:item];
247 [NSApp setMainMenu:mainMenu];
248 [NSApp setWindowsMenu:submenu];
250 [NSApp setApplicationIconImage:self.applicationIcon];
254 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
256 PerformRequest(NULL);
262 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
263 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
265 inMode:NSDefaultRunLoopMode
268 [NSApp sendEvent:event];
272 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
273 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
278 - (BOOL) registerEventQueue:(WineEventQueue*)queue
280 [eventQueuesLock lock];
281 [eventQueues addObject:queue];
282 [eventQueuesLock unlock];
286 - (void) unregisterEventQueue:(WineEventQueue*)queue
288 [eventQueuesLock lock];
289 [eventQueues removeObjectIdenticalTo:queue];
290 [eventQueuesLock unlock];
293 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
295 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
298 - (double) ticksForEventTime:(NSTimeInterval)eventTime
300 return (eventTime + eventTimeAdjustment) * 1000;
303 /* Invalidate old focus offers across all queues. */
304 - (void) invalidateGotFocusEvents
306 WineEventQueue* queue;
310 [eventQueuesLock lock];
311 for (queue in eventQueues)
313 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
316 [eventQueuesLock unlock];
319 - (void) windowGotFocus:(WineWindow*)window
323 [self invalidateGotFocusEvents];
325 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
326 event->window_got_focus.serial = windowFocusSerial;
328 event->window_got_focus.tried_windows = [triedWindows retain];
330 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
331 [window.queue postEvent:event];
332 macdrv_release_event(event);
335 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
337 if (event->window_got_focus.serial == windowFocusSerial)
339 NSMutableArray* windows = [keyWindows mutableCopy];
340 NSNumber* windowNumber;
343 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
345 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
346 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
347 ![windows containsObject:window])
348 [windows addObject:window];
351 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
352 [triedWindows addObject:(WineWindow*)event->window];
353 for (window in windows)
355 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
357 [window makeKeyWindow];
366 - (void) keyboardSelectionDidChange
368 TISInputSourceRef inputSource;
370 inputSourceIsInputMethodValid = FALSE;
372 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
376 uchr = TISGetInputSourceProperty(inputSource,
377 kTISPropertyUnicodeKeyLayoutData);
381 WineEventQueue* queue;
383 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
384 event->keyboard_changed.keyboard_type = self.keyboardType;
385 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
386 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
388 if (event->keyboard_changed.uchr)
390 [eventQueuesLock lock];
392 for (queue in eventQueues)
393 [queue postEvent:event];
395 [eventQueuesLock unlock];
398 macdrv_release_event(event);
401 CFRelease(inputSource);
405 - (CGFloat) primaryScreenHeight
407 if (!primaryScreenHeightValid)
409 NSArray* screens = [NSScreen screens];
410 NSUInteger count = [screens count];
417 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
418 primaryScreenHeightValid = TRUE;
420 size = count * sizeof(CGRect);
421 if (!screenFrameCGRects)
422 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
424 [screenFrameCGRects setLength:size];
426 rect = [screenFrameCGRects mutableBytes];
427 for (screen in screens)
429 CGRect temp = NSRectToCGRect([screen frame]);
430 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
435 return 1280; /* arbitrary value */
438 return primaryScreenHeight;
441 - (NSPoint) flippedMouseLocation:(NSPoint)point
443 /* This relies on the fact that Cocoa's mouse location points are
444 actually off by one (precisely because they were flipped from
445 Quartz screen coordinates using this same technique). */
446 point.y = [self primaryScreenHeight] - point.y;
450 - (void) flipRect:(NSRect*)rect
452 // We don't use -primaryScreenHeight here so there's no chance of having
453 // out-of-date cached info. This method is called infrequently enough
454 // that getting the screen height each time is not prohibitively expensive.
455 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
458 - (WineWindow*) frontWineWindow
460 NSNumber* windowNumber;
461 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
463 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
464 if ([window isKindOfClass:[WineWindow class]] && [window screen])
465 return (WineWindow*)window;
471 - (void) adjustWindowLevels:(BOOL)active
473 NSArray* windowNumbers = [NSWindow windowNumbersWithOptions:0];
474 NSMutableArray* wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
475 NSNumber* windowNumber;
476 NSUInteger nextFloatingIndex = 0;
477 __block NSInteger maxLevel = NSIntegerMin;
478 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
479 __block WineWindow* prev = nil;
482 // For the most part, we rely on the window server's ordering of the windows
483 // to be authoritative. The one exception is if the "floating" property of
484 // one of the windows has been changed, it may be in the wrong level and thus
485 // in the order. This method is what's supposed to fix that up. So build
486 // a list of Wine windows sorted first by floating-ness and then by order
487 // as indicated by the window server.
488 for (windowNumber in windowNumbers)
490 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
491 if ([window isKindOfClass:[WineWindow class]])
494 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
496 [wineWindows addObject:window];
500 NSDisableScreenUpdates();
502 // Go from back to front so that all windows in front of one which is
503 // elevated for full-screen are also elevated.
504 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
505 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
506 WineWindow* window = (WineWindow*)obj;
507 NSInteger origLevel = [window level];
508 NSInteger newLevel = [window minimumLevelForActive:active];
510 if (newLevel < maxLevel)
515 if (!window.floating && maxNonfloatingLevel < newLevel)
516 maxNonfloatingLevel = newLevel;
518 if (newLevel != origLevel)
520 [window setLevel:newLevel];
522 // -setLevel: puts the window at the front of its new level. If
523 // we decreased the level, that's good (it was in front of that
524 // level before, so it should still be now). But if we increased
525 // the level, the window should be toward the back (but still
526 // ahead of the previous windows we did this to).
527 if (origLevel < newLevel)
530 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
532 [window orderBack:nil];
539 NSEnableScreenUpdates();
541 [wineWindows release];
543 // The above took care of the visible windows on the current space. That
544 // leaves windows on other spaces, minimized windows, and windows which
545 // are not ordered in. We want to leave windows on other spaces alone
546 // so the space remains just as they left it (when viewed in Exposé or
547 // Mission Control, for example). We'll adjust the window levels again
548 // after we switch to another space, anyway. Windows which aren't
549 // ordered in will be handled when we order them in. Minimized windows
550 // on the current space should be set to the level they would have gotten
551 // if they were at the front of the windows with the same floating-ness,
552 // because that's where they'll go if/when they are unminimized. Again,
553 // for good measure we'll adjust window levels again when a window is
555 for (window in [NSApp windows])
557 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
558 [window isOnActiveSpace])
560 NSInteger origLevel = [window level];
561 NSInteger newLevel = [window minimumLevelForActive:YES];
562 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
564 if (newLevel < maxLevelForType)
565 newLevel = maxLevelForType;
567 if (newLevel != origLevel)
568 [window setLevel:newLevel];
573 - (void) adjustWindowLevels
575 [self adjustWindowLevels:[NSApp isActive]];
578 - (void) updateFullscreenWindows
580 if (capture_displays_for_fullscreen && [NSApp isActive])
582 BOOL anyFullscreen = FALSE;
583 NSNumber* windowNumber;
584 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
586 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
587 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
589 anyFullscreen = TRUE;
596 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
597 displaysCapturedForFullscreen = TRUE;
599 else if (displaysCapturedForFullscreen)
601 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
602 displaysCapturedForFullscreen = FALSE;
607 - (void) activeSpaceDidChange
609 [self updateFullscreenWindows];
610 [self adjustWindowLevels];
613 - (void) sendDisplaysChanged:(BOOL)activating
616 WineEventQueue* queue;
618 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
619 event->displays_changed.activating = activating;
621 [eventQueuesLock lock];
623 // If we're activating, then we just need one of our threads to get the
624 // event, so it can send it directly to the desktop window. Otherwise,
625 // we need all of the threads to get it because we don't know which owns
626 // the desktop window and only that one will do anything with it.
627 if (activating) event->deliver = 1;
629 for (queue in eventQueues)
630 [queue postEvent:event];
631 [eventQueuesLock unlock];
633 macdrv_release_event(event);
636 // We can compare two modes directly using CFEqual, but that may require that
637 // they are identical to a level that we don't need. In particular, when the
638 // OS switches between the integrated and discrete GPUs, the set of display
639 // modes can change in subtle ways. We're interested in whether two modes
640 // match in their most salient features, even if they aren't identical.
641 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
643 NSString *encoding1, *encoding2;
644 uint32_t ioflags1, ioflags2, different;
645 double refresh1, refresh2;
647 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
648 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
650 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
651 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
652 if (![encoding1 isEqualToString:encoding2]) return FALSE;
654 ioflags1 = CGDisplayModeGetIOFlags(mode1);
655 ioflags2 = CGDisplayModeGetIOFlags(mode2);
656 different = ioflags1 ^ ioflags2;
657 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
658 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
661 refresh1 = CGDisplayModeGetRefreshRate(mode1);
662 if (refresh1 == 0) refresh1 = 60;
663 refresh2 = CGDisplayModeGetRefreshRate(mode2);
664 if (refresh2 == 0) refresh2 = 60;
665 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
670 - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
672 CGDisplayModeRef ret = NULL;
673 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
674 for (id candidateModeObject in modes)
676 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
677 if ([self mode:candidateMode matchesMode:mode])
686 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
689 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
690 CGDisplayModeRef currentMode, originalMode;
692 currentMode = CGDisplayCopyDisplayMode(displayID);
693 if (!currentMode) // Invalid display ID
696 if ([self mode:mode matchesMode:currentMode]) // Already there!
698 CGDisplayModeRelease(currentMode);
702 mode = [self modeMatchingMode:mode forDisplay:displayID];
705 CGDisplayModeRelease(currentMode);
709 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
711 originalMode = currentMode;
713 if ([self mode:mode matchesMode:originalMode])
715 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
717 CGRestorePermanentDisplayConfiguration();
718 if (!displaysCapturedForFullscreen)
719 CGReleaseAllDisplays();
720 [originalDisplayModes removeAllObjects];
723 else // ... otherwise, try to restore just the one display
725 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
727 [originalDisplayModes removeObjectForKey:displayIDKey];
734 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
735 CGCaptureAllDisplays() == CGDisplayNoErr)
737 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
739 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
742 else if (![originalDisplayModes count])
744 CGRestorePermanentDisplayConfiguration();
745 if (!displaysCapturedForFullscreen)
746 CGReleaseAllDisplays();
751 CGDisplayModeRelease(currentMode);
754 [self adjustWindowLevels];
759 - (BOOL) areDisplaysCaptured
761 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
773 - (void) unhideCursor
778 cursorHidden = FALSE;
784 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
785 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
786 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
787 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
791 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
792 hotSpot = CGPointZero;
793 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
800 - (void) nextCursorFrame:(NSTimer*)theTimer
803 NSTimeInterval duration;
807 if (cursorFrame >= [cursorFrames count])
811 frame = [cursorFrames objectAtIndex:cursorFrame];
812 duration = [[frame objectForKey:@"duration"] doubleValue];
813 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
814 [cursorTimer setFireDate:date];
817 - (void) setCursorWithFrames:(NSArray*)frames
819 if (self.cursorFrames == frames)
822 self.cursorFrames = frames;
824 [cursorTimer invalidate];
825 self.cursorTimer = nil;
829 if ([frames count] > 1)
831 NSDictionary* frame = [frames objectAtIndex:0];
832 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
833 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
834 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
837 selector:@selector(nextCursorFrame:)
839 repeats:YES] autorelease];
840 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
847 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
849 NSImage* nsimage = nil;
853 NSSize bestSize = NSZeroSize;
856 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
858 for (image in images)
860 CGImageRef cgimage = (CGImageRef)image;
861 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
864 NSSize size = [imageRep size];
866 [nsimage addRepresentation:imageRep];
869 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
874 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
875 [nsimage setSize:bestSize];
880 self.applicationIcon = nsimage;
881 [NSApp setApplicationIconImage:nsimage];
884 - (void) handleCommandTab
886 if ([NSApp isActive])
888 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
889 NSRunningApplication* app;
890 NSRunningApplication* otherValidApp = nil;
892 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
894 CGRestorePermanentDisplayConfiguration();
895 CGReleaseAllDisplays();
896 [originalDisplayModes removeAllObjects];
897 displaysCapturedForFullscreen = FALSE;
900 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
902 if (![app isEqual:thisApp] && !app.terminated &&
903 app.activationPolicy == NSApplicationActivationPolicyRegular)
907 // There's another visible app. Just hide ourselves and let
908 // the system activate the other app.
918 // Didn't find a visible GUI app. Try the Finder or, if that's not
919 // running, the first hidden GUI app. If even that doesn't work, we
920 // just fail to switch and remain the active app.
921 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
922 if (!app) app = otherValidApp;
924 [app activateWithOptions:0];
929 * ---------- Cursor clipping methods ----------
931 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
932 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
933 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
934 * general case, we leverage that. We disassociate mouse movements from
935 * the cursor position and then move the cursor manually, keeping it within
936 * the clipping rectangle.
938 * Moving the cursor manually isn't enough. We need to modify the event
939 * stream so that the events have the new location, too. We need to do
940 * this at a point before the events enter Cocoa, so that Cocoa will assign
941 * the correct window to the event. So, we install a Quartz event tap to
944 * Also, there's a complication when we move the cursor. We use
945 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
946 * events, but the change of cursor position is incorporated into the
947 * deltas of the next mouse move event. When the mouse is disassociated
948 * from the cursor position, we need the deltas to only reflect actual
949 * device movement, not programmatic changes. So, the event tap cancels
950 * out the change caused by our calls to CGWarpMouseCursorPosition().
952 - (void) clipCursorLocation:(CGPoint*)location
954 if (location->x < CGRectGetMinX(cursorClipRect))
955 location->x = CGRectGetMinX(cursorClipRect);
956 if (location->y < CGRectGetMinY(cursorClipRect))
957 location->y = CGRectGetMinY(cursorClipRect);
958 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
959 location->x = CGRectGetMaxX(cursorClipRect) - 1;
960 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
961 location->y = CGRectGetMaxY(cursorClipRect) - 1;
964 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
969 oldLocation = *currentLocation;
971 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
973 if (!CGPointEqualToPoint(oldLocation, *newLocation))
975 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
978 warpRecord.from = oldLocation;
979 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
981 /* Actually move the cursor. */
982 err = CGWarpMouseCursorPosition(*newLocation);
983 if (err != kCGErrorSuccess)
986 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
987 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
989 if (!CGPointEqualToPoint(oldLocation, *newLocation))
991 warpRecord.to = *newLocation;
992 [warpRecords addObject:warpRecord];
999 - (BOOL) isMouseMoveEventType:(CGEventType)type
1003 case kCGEventMouseMoved:
1004 case kCGEventLeftMouseDragged:
1005 case kCGEventRightMouseDragged:
1006 case kCGEventOtherMouseDragged:
1013 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1015 int warpsFinished = 0;
1016 for (WarpRecord* warpRecord in warpRecords)
1018 if (warpRecord.timeAfter < eventTime ||
1019 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1025 return warpsFinished;
1028 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1029 type:(CGEventType)type
1030 event:(CGEventRef)event
1032 CGEventTimestamp eventTime;
1033 CGPoint eventLocation, cursorLocation;
1035 if (type == kCGEventTapDisabledByUserInput)
1037 if (type == kCGEventTapDisabledByTimeout)
1039 CGEventTapEnable(cursorClippingEventTap, TRUE);
1043 if (!clippingCursor)
1046 eventTime = CGEventGetTimestamp(event);
1047 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1049 eventLocation = CGEventGetLocation(event);
1051 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1053 if ([self isMouseMoveEventType:type])
1055 double deltaX, deltaY;
1056 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1059 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1060 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1062 for (i = 0; i < warpsFinished; i++)
1064 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1065 deltaX -= warpRecord.to.x - warpRecord.from.x;
1066 deltaY -= warpRecord.to.y - warpRecord.from.y;
1067 [warpRecords removeObjectAtIndex:0];
1072 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1073 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1076 synthesizedLocation.x += deltaX;
1077 synthesizedLocation.y += deltaY;
1080 // If the event is destined for another process, don't clip it. This may
1081 // happen if the user activates Exposé or Mission Control. In that case,
1082 // our app does not resign active status, so clipping is still in effect,
1083 // but the cursor should not actually be clipped.
1085 // In addition, the fact that mouse moves may have been delivered to a
1086 // different process means we have to treat the next one we receive as
1087 // absolute rather than relative.
1088 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1089 [self clipCursorLocation:&synthesizedLocation];
1091 lastSetCursorPositionTime = lastEventTapEventTime;
1093 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1094 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1095 CGEventSetLocation(event, synthesizedLocation);
1100 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1101 CGEventRef event, void *refcon)
1103 WineApplicationController* controller = refcon;
1104 return [controller eventTapWithProxy:proxy type:type event:event];
1107 - (BOOL) installEventTap
1109 ProcessSerialNumber psn;
1111 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1112 CGEventMaskBit(kCGEventLeftMouseUp) |
1113 CGEventMaskBit(kCGEventRightMouseDown) |
1114 CGEventMaskBit(kCGEventRightMouseUp) |
1115 CGEventMaskBit(kCGEventMouseMoved) |
1116 CGEventMaskBit(kCGEventLeftMouseDragged) |
1117 CGEventMaskBit(kCGEventRightMouseDragged) |
1118 CGEventMaskBit(kCGEventOtherMouseDown) |
1119 CGEventMaskBit(kCGEventOtherMouseUp) |
1120 CGEventMaskBit(kCGEventOtherMouseDragged) |
1121 CGEventMaskBit(kCGEventScrollWheel);
1122 CFRunLoopSourceRef source;
1124 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1126 if (cursorClippingEventTap)
1129 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1130 // framework with dlsym() because the Win32 function of the same name
1132 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1136 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1137 if (!pGetCurrentProcess)
1139 dlclose(appServices);
1143 err = pGetCurrentProcess(&psn);
1144 dlclose(appServices);
1148 // We create an annotated session event tap rather than a process-specific
1149 // event tap because we need to programmatically move the cursor even when
1150 // mouse moves are directed to other processes. We disable our tap when
1151 // other processes are active, but things like Exposé are handled by other
1152 // processes even when we remain active.
1153 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1154 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1155 if (!cursorClippingEventTap)
1158 CGEventTapEnable(cursorClippingEventTap, FALSE);
1160 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1163 CFRelease(cursorClippingEventTap);
1164 cursorClippingEventTap = NULL;
1168 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1173 - (BOOL) setCursorPosition:(CGPoint)pos
1179 [self clipCursorLocation:&pos];
1181 ret = [self warpCursorTo:&pos from:NULL];
1182 synthesizedLocation = pos;
1185 // We want to discard mouse-move events that have already been
1186 // through the event tap, because it's too late to account for
1187 // the setting of the cursor position with them. However, the
1188 // events that may be queued with times after that but before
1189 // the above warp can still be used. So, use the last event
1190 // tap event time so that -sendEvent: doesn't discard them.
1191 lastSetCursorPositionTime = lastEventTapEventTime;
1196 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1199 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1201 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1202 // the mouse from the cursor position for 0.25 seconds. This means
1203 // that mouse movement during that interval doesn't move the cursor
1204 // and events carry a constant location (the warped-to position)
1205 // even though they have delta values. This screws us up because
1206 // the accumulated deltas we send to Wine don't match any eventual
1207 // absolute position we send (like with a button press). We can
1208 // work around this by simply forcibly reassociating the mouse and
1210 CGAssociateMouseAndMouseCursorPosition(true);
1216 WineEventQueue* queue;
1218 // Discard all pending mouse move events.
1219 [eventQueuesLock lock];
1220 for (queue in eventQueues)
1222 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1223 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1225 [queue resetMouseEventPositions:pos];
1227 [eventQueuesLock unlock];
1233 - (void) activateCursorClipping
1237 CGEventTapEnable(cursorClippingEventTap, TRUE);
1238 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1242 - (void) deactivateCursorClipping
1246 CGEventTapEnable(cursorClippingEventTap, FALSE);
1247 [warpRecords removeAllObjects];
1248 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1252 - (BOOL) startClippingCursor:(CGRect)rect
1256 if (!cursorClippingEventTap && ![self installEventTap])
1259 err = CGAssociateMouseAndMouseCursorPosition(false);
1260 if (err != kCGErrorSuccess)
1263 clippingCursor = TRUE;
1264 cursorClipRect = rect;
1265 if ([NSApp isActive])
1266 [self activateCursorClipping];
1271 - (BOOL) stopClippingCursor
1273 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1274 if (err != kCGErrorSuccess)
1277 [self deactivateCursorClipping];
1278 clippingCursor = FALSE;
1283 - (void) handleMouseMove:(NSEvent*)anEvent
1285 WineWindow* targetWindow;
1286 BOOL drag = [anEvent type] != NSMouseMoved;
1288 if (mouseCaptureWindow)
1289 targetWindow = mouseCaptureWindow;
1291 targetWindow = (WineWindow*)[anEvent window];
1294 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1295 event indicates its window is the main window, even if the cursor is
1296 over a different window. Find the actual WineWindow that is under the
1297 cursor and post the event as being for that window. */
1298 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1299 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1300 NSInteger windowUnderNumber;
1302 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1303 belowWindowWithWindowNumber:0];
1304 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1307 if ([targetWindow isKindOfClass:[WineWindow class]])
1309 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1310 macdrv_event* event;
1313 // If we recently warped the cursor (other than in our cursor-clipping
1314 // event tap), discard mouse move events until we see an event which is
1315 // later than that time.
1316 if (lastSetCursorPositionTime)
1318 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1321 lastSetCursorPositionTime = 0;
1322 forceNextMouseMoveAbsolute = TRUE;
1325 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1328 forceNextMouseMoveAbsolute = FALSE;
1332 // Send absolute move events if the cursor is in the interior of
1333 // its range. Only send relative moves if the cursor is pinned to
1334 // the boundaries of where it can go. We compute the position
1335 // that's one additional point in the direction of movement. If
1336 // that is outside of the clipping rect or desktop region (the
1337 // union of the screen frames), then we figure the cursor would
1338 // have moved outside if it could but it was pinned.
1339 CGPoint computedPoint = point;
1340 CGFloat deltaX = [anEvent deltaX];
1341 CGFloat deltaY = [anEvent deltaY];
1345 else if (deltaX < -0.001)
1350 else if (deltaY < -0.001)
1353 // Assume cursor is pinned for now
1355 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1357 const CGRect* rects;
1358 NSUInteger count, i;
1360 // Caches screenFrameCGRects if necessary
1361 [self primaryScreenHeight];
1363 rects = [screenFrameCGRects bytes];
1364 count = [screenFrameCGRects length] / sizeof(rects[0]);
1366 for (i = 0; i < count; i++)
1368 if (CGRectContainsPoint(rects[i], computedPoint))
1380 [self clipCursorLocation:&point];
1382 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1383 event->mouse_moved.x = point.x;
1384 event->mouse_moved.y = point.y;
1386 mouseMoveDeltaX = 0;
1387 mouseMoveDeltaY = 0;
1391 /* Add event delta to accumulated delta error */
1392 /* deltaY is already flipped */
1393 mouseMoveDeltaX += [anEvent deltaX];
1394 mouseMoveDeltaY += [anEvent deltaY];
1396 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1397 event->mouse_moved.x = mouseMoveDeltaX;
1398 event->mouse_moved.y = mouseMoveDeltaY;
1400 /* Keep the remainder after integer truncation. */
1401 mouseMoveDeltaX -= event->mouse_moved.x;
1402 mouseMoveDeltaY -= event->mouse_moved.y;
1405 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1407 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1408 event->mouse_moved.drag = drag;
1410 [targetWindow.queue postEvent:event];
1413 macdrv_release_event(event);
1415 lastTargetWindow = targetWindow;
1417 else if (lastTargetWindow)
1419 [[NSCursor arrowCursor] set];
1420 [self unhideCursor];
1421 lastTargetWindow = nil;
1425 - (void) handleMouseButton:(NSEvent*)theEvent
1427 WineWindow* window = (WineWindow*)[theEvent window];
1428 NSEventType type = [theEvent type];
1430 if ([window isKindOfClass:[WineWindow class]] &&
1431 type == NSLeftMouseDown &&
1432 (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1434 NSWindowButton windowButton;
1435 BOOL broughtWindowForward = TRUE;
1437 /* Any left-click on our window anyplace other than the close or
1438 minimize buttons will bring it forward. */
1439 for (windowButton = NSWindowCloseButton;
1440 windowButton <= NSWindowMiniaturizeButton;
1443 NSButton* button = [window standardWindowButton:windowButton];
1446 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1447 if ([button mouse:point inRect:[button bounds]])
1449 broughtWindowForward = FALSE;
1455 if (broughtWindowForward)
1457 // Clicking on a child window does not normally reorder it with
1458 // respect to its siblings, but we want it to. We have to do it
1460 NSWindow* parent = [window parentWindow];
1461 [parent removeChildWindow:window];
1462 [parent addChildWindow:window ordered:NSWindowAbove];
1466 if (mouseCaptureWindow)
1467 window = mouseCaptureWindow;
1469 if ([window isKindOfClass:[WineWindow class]])
1471 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1472 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1476 [self clipCursorLocation:&pt];
1480 if (mouseCaptureWindow)
1484 // Test if the click was in the window's content area.
1485 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1486 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1487 process = NSPointInRect(nspoint, contentRect);
1488 if (process && [window styleMask] & NSResizableWindowMask)
1490 // Ignore clicks in the grow box (resize widget).
1491 HIPoint origin = { 0, 0 };
1492 HIThemeGrowBoxDrawInfo info = { 0 };
1496 info.kind = kHIThemeGrowBoxKindNormal;
1497 info.direction = kThemeGrowRight | kThemeGrowDown;
1498 if ([window styleMask] & NSUtilityWindowMask)
1499 info.size = kHIThemeGrowBoxSizeSmall;
1501 info.size = kHIThemeGrowBoxSizeNormal;
1503 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1504 if (status == noErr)
1506 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1507 NSMinY(contentRect),
1509 bounds.size.height);
1510 process = !NSPointInRect(nspoint, growBox);
1515 unmatchedMouseDowns |= NSEventMaskFromType(type);
1519 NSEventType downType = type - 1;
1520 NSUInteger downMask = NSEventMaskFromType(downType);
1521 process = (unmatchedMouseDowns & downMask) != 0;
1522 unmatchedMouseDowns &= ~downMask;
1527 macdrv_event* event;
1529 event = macdrv_create_event(MOUSE_BUTTON, window);
1530 event->mouse_button.button = [theEvent buttonNumber];
1531 event->mouse_button.pressed = pressed;
1532 event->mouse_button.x = pt.x;
1533 event->mouse_button.y = pt.y;
1534 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1536 [window.queue postEvent:event];
1538 macdrv_release_event(event);
1542 // Since mouse button events deliver absolute cursor position, the
1543 // accumulating delta from move events is invalidated. Make sure
1544 // next mouse move event starts over from an absolute baseline.
1545 // Also, it's at least possible that the title bar widgets (e.g. close
1546 // button, etc.) could enter an internal event loop on a mouse down that
1547 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1548 // dragged events and, after that, any notion of the cursor position
1549 // computed from accumulating deltas would be wrong.
1550 forceNextMouseMoveAbsolute = TRUE;
1553 - (void) handleScrollWheel:(NSEvent*)theEvent
1557 if (mouseCaptureWindow)
1558 window = mouseCaptureWindow;
1560 window = (WineWindow*)[theEvent window];
1562 if ([window isKindOfClass:[WineWindow class]])
1564 CGEventRef cgevent = [theEvent CGEvent];
1565 CGPoint pt = CGEventGetLocation(cgevent);
1569 [self clipCursorLocation:&pt];
1571 if (mouseCaptureWindow)
1575 // Only process the event if it was in the window's content area.
1576 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1577 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1578 process = NSPointInRect(nspoint, contentRect);
1583 macdrv_event* event;
1585 BOOL continuous = FALSE;
1587 event = macdrv_create_event(MOUSE_SCROLL, window);
1588 event->mouse_scroll.x = pt.x;
1589 event->mouse_scroll.y = pt.y;
1590 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1592 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1596 /* Continuous scroll wheel events come from high-precision scrolling
1597 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1598 For these, we can get more precise data from the CGEvent API. */
1599 /* Axis 1 is vertical, axis 2 is horizontal. */
1600 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1601 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1605 double pixelsPerLine = 10;
1606 CGEventSourceRef source;
1608 /* The non-continuous values are in units of "lines", not pixels. */
1609 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1611 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1615 x = pixelsPerLine * [theEvent deltaX];
1616 y = pixelsPerLine * [theEvent deltaY];
1619 /* Mac: negative is right or down, positive is left or up.
1620 Win32: negative is left or down, positive is right or up.
1621 So, negate the X scroll value to translate. */
1624 /* The x,y values so far are in pixels. Win32 expects to receive some
1625 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1626 6 times the pixel value. */
1627 event->mouse_scroll.x_scroll = 6 * x;
1628 event->mouse_scroll.y_scroll = 6 * y;
1632 /* For non-continuous "clicky" wheels, if there was any motion, make
1633 sure there was at least WHEEL_DELTA motion. This is so, at slow
1634 speeds where the system's acceleration curve is actually reducing the
1635 scroll distance, the user is sure to get some action out of each click.
1636 For example, this is important for rotating though weapons in a
1637 first-person shooter. */
1638 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1639 event->mouse_scroll.x_scroll = 120;
1640 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1641 event->mouse_scroll.x_scroll = -120;
1643 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1644 event->mouse_scroll.y_scroll = 120;
1645 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1646 event->mouse_scroll.y_scroll = -120;
1649 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1650 [window.queue postEvent:event];
1652 macdrv_release_event(event);
1654 // Since scroll wheel events deliver absolute cursor position, the
1655 // accumulating delta from move events is invalidated. Make sure next
1656 // mouse move event starts over from an absolute baseline.
1657 forceNextMouseMoveAbsolute = TRUE;
1662 // Returns TRUE if the event was handled and caller should do nothing more
1663 // with it. Returns FALSE if the caller should process it as normal and
1664 // then call -didSendEvent:.
1665 - (BOOL) handleEvent:(NSEvent*)anEvent
1668 NSEventType type = [anEvent type];
1670 if (type == NSFlagsChanged)
1671 self.lastFlagsChanged = anEvent;
1672 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1673 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1675 [self handleMouseMove:anEvent];
1676 ret = mouseCaptureWindow != nil;
1678 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1679 type == NSRightMouseDown || type == NSRightMouseUp ||
1680 type == NSOtherMouseDown || type == NSOtherMouseUp)
1682 [self handleMouseButton:anEvent];
1683 ret = mouseCaptureWindow != nil;
1685 else if (type == NSScrollWheel)
1687 [self handleScrollWheel:anEvent];
1688 ret = mouseCaptureWindow != nil;
1694 - (void) didSendEvent:(NSEvent*)anEvent
1696 NSEventType type = [anEvent type];
1698 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1700 NSUInteger modifiers = [anEvent modifierFlags];
1701 if ((modifiers & NSCommandKeyMask) &&
1702 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1704 // Command-Tab and Command-Shift-Tab would normally be intercepted
1705 // by the system to switch applications. If we're seeing it, it's
1706 // presumably because we've captured the displays, preventing
1707 // normal application switching. Do it manually.
1708 [self handleCommandTab];
1713 - (void) setupObservations
1715 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1716 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1718 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1721 usingBlock:^(NSNotification *note){
1722 NSWindow* window = [note object];
1723 [keyWindows removeObjectIdenticalTo:window];
1724 [keyWindows insertObject:window atIndex:0];
1727 [nc addObserverForName:NSWindowWillCloseNotification
1729 queue:[NSOperationQueue mainQueue]
1730 usingBlock:^(NSNotification *note){
1731 NSWindow* window = [note object];
1732 [keyWindows removeObjectIdenticalTo:window];
1733 if (window == lastTargetWindow)
1734 lastTargetWindow = nil;
1735 if (window == self.mouseCaptureWindow)
1736 self.mouseCaptureWindow = nil;
1737 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1739 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1740 [self updateFullscreenWindows];
1745 [nc addObserver:self
1746 selector:@selector(keyboardSelectionDidChange)
1747 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1750 /* The above notification isn't sent unless the NSTextInputContext
1751 class has initialized itself. Poke it. */
1752 [NSTextInputContext self];
1754 [wsnc addObserver:self
1755 selector:@selector(activeSpaceDidChange)
1756 name:NSWorkspaceActiveSpaceDidChangeNotification
1760 - (BOOL) inputSourceIsInputMethod
1762 if (!inputSourceIsInputMethodValid)
1764 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1767 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1768 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1769 CFRelease(inputSource);
1772 inputSourceIsInputMethod = FALSE;
1773 inputSourceIsInputMethodValid = TRUE;
1776 return inputSourceIsInputMethod;
1781 * ---------- NSApplicationDelegate methods ----------
1783 - (void)applicationDidBecomeActive:(NSNotification *)notification
1785 [self activateCursorClipping];
1787 [self updateFullscreenWindows];
1788 [self adjustWindowLevels:YES];
1790 if (beenActive && ![self frontWineWindow])
1792 for (WineWindow* window in [NSApp windows])
1794 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1796 [window deminiaturize:self];
1803 // If a Wine process terminates abruptly while it has the display captured
1804 // and switched to a different resolution, Mac OS X will uncapture the
1805 // displays and switch their resolutions back. However, the other Wine
1806 // processes won't have their notion of the desktop rect changed back.
1807 // This can lead them to refuse to draw or acknowledge clicks in certain
1808 // portions of their windows.
1810 // To solve this, we synthesize a displays-changed event whenever we're
1811 // activated. This will provoke a re-synchronization of Wine's notion of
1812 // the desktop rect with the actual state.
1813 [self sendDisplaysChanged:TRUE];
1815 // The cursor probably moved while we were inactive. Accumulated mouse
1816 // movement deltas are invalidated. Make sure the next mouse move event
1817 // starts over from an absolute baseline.
1818 forceNextMouseMoveAbsolute = TRUE;
1821 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1823 primaryScreenHeightValid = FALSE;
1824 [self sendDisplaysChanged:FALSE];
1825 [self adjustWindowLevels];
1827 // When the display configuration changes, the cursor position may jump.
1828 // Accumulated mouse movement deltas are invalidated. Make sure the next
1829 // mouse move event starts over from an absolute baseline.
1830 forceNextMouseMoveAbsolute = TRUE;
1833 - (void)applicationDidResignActive:(NSNotification *)notification
1835 macdrv_event* event;
1836 WineEventQueue* queue;
1838 [self invalidateGotFocusEvents];
1840 event = macdrv_create_event(APP_DEACTIVATED, nil);
1842 [eventQueuesLock lock];
1843 for (queue in eventQueues)
1844 [queue postEvent:event];
1845 [eventQueuesLock unlock];
1847 macdrv_release_event(event);
1850 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1852 NSApplicationTerminateReply ret = NSTerminateNow;
1853 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1854 NSAppleEventDescriptor* desc = [m currentAppleEvent];
1855 macdrv_event* event;
1856 WineEventQueue* queue;
1858 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1860 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1863 case kAEReallyLogOut:
1864 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1866 case kAEShowRestartDialog:
1867 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1869 case kAEShowShutdownDialog:
1870 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1873 event->app_quit_requested.reason = QUIT_REASON_NONE;
1877 [eventQueuesLock lock];
1879 if ([eventQueues count])
1881 for (queue in eventQueues)
1882 [queue postEvent:event];
1883 ret = NSTerminateLater;
1886 [eventQueuesLock unlock];
1888 macdrv_release_event(event);
1893 - (void)applicationWillResignActive:(NSNotification *)notification
1895 [self deactivateCursorClipping];
1897 [self adjustWindowLevels:NO];
1900 /***********************************************************************
1903 * Run-loop-source perform callback. Pull request blocks from the
1904 * array of queued requests and invoke them.
1906 static void PerformRequest(void *info)
1908 WineApplicationController* controller = [WineApplicationController sharedController];
1912 __block dispatch_block_t block;
1914 dispatch_sync(controller->requestsManipQueue, ^{
1915 if ([controller->requests count])
1917 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1918 [controller->requests removeObjectAtIndex:0];
1932 /***********************************************************************
1935 * Run a block on the main thread asynchronously.
1937 void OnMainThreadAsync(dispatch_block_t block)
1939 WineApplicationController* controller = [WineApplicationController sharedController];
1941 block = [block copy];
1942 dispatch_sync(controller->requestsManipQueue, ^{
1943 [controller->requests addObject:block];
1946 CFRunLoopSourceSignal(controller->requestSource);
1947 CFRunLoopWakeUp(CFRunLoopGetMain());
1952 /***********************************************************************
1955 void LogError(const char* func, NSString* format, ...)
1958 va_start(args, format);
1959 LogErrorv(func, format, args);
1963 /***********************************************************************
1966 void LogErrorv(const char* func, NSString* format, va_list args)
1968 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1970 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1971 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1977 /***********************************************************************
1978 * macdrv_window_rejected_focus
1980 * Pass focus to the next window that hasn't already rejected this same
1981 * WINDOW_GOT_FOCUS event.
1983 void macdrv_window_rejected_focus(const macdrv_event *event)
1986 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1990 /***********************************************************************
1991 * macdrv_get_keyboard_layout
1993 * Returns the keyboard layout uchr data.
1995 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1997 __block CFDataRef result = NULL;
2000 TISInputSourceRef inputSource;
2002 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
2005 CFDataRef uchr = TISGetInputSourceProperty(inputSource,
2006 kTISPropertyUnicodeKeyLayoutData);
2007 result = CFDataCreateCopy(NULL, uchr);
2008 CFRelease(inputSource);
2010 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2011 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2018 /***********************************************************************
2021 * Play the beep sound configured by the user in System Preferences.
2023 void macdrv_beep(void)
2025 OnMainThreadAsync(^{
2030 /***********************************************************************
2031 * macdrv_set_display_mode
2033 int macdrv_set_display_mode(const struct macdrv_display* display,
2034 CGDisplayModeRef display_mode)
2039 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2045 /***********************************************************************
2050 * If name is non-NULL, it is a selector for a class method on NSCursor
2051 * identifying the cursor to set. In that case, frames is ignored. If
2052 * name is NULL, then frames is used.
2054 * frames is an array of dictionaries. Each dictionary is a frame of
2055 * an animated cursor. Under the key "image" is a CGImage for the
2056 * frame. Under the key "duration" is a CFNumber time interval, in
2057 * seconds, for how long that frame is presented before proceeding to
2058 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2059 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2060 * This is the hot spot, measured in pixels down and to the right of the
2061 * top-left corner of the image.
2063 * If the array has exactly 1 element, the cursor is static, not
2064 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2066 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2070 sel = NSSelectorFromString((NSString*)name);
2073 OnMainThreadAsync(^{
2074 WineApplicationController* controller = [WineApplicationController sharedController];
2075 NSCursor* cursor = [NSCursor performSelector:sel];
2076 [controller setCursorWithFrames:nil];
2078 [controller unhideCursor];
2083 NSArray* nsframes = (NSArray*)frames;
2084 if ([nsframes count])
2086 OnMainThreadAsync(^{
2087 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2092 OnMainThreadAsync(^{
2093 WineApplicationController* controller = [WineApplicationController sharedController];
2094 [controller setCursorWithFrames:nil];
2095 [controller hideCursor];
2101 /***********************************************************************
2102 * macdrv_get_cursor_position
2104 * Obtains the current cursor position. Returns zero on failure,
2105 * non-zero on success.
2107 int macdrv_get_cursor_position(CGPoint *pos)
2110 NSPoint location = [NSEvent mouseLocation];
2111 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2112 *pos = NSPointToCGPoint(location);
2118 /***********************************************************************
2119 * macdrv_set_cursor_position
2121 * Sets the cursor position without generating events. Returns zero on
2122 * failure, non-zero on success.
2124 int macdrv_set_cursor_position(CGPoint pos)
2129 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2135 /***********************************************************************
2136 * macdrv_clip_cursor
2138 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2139 * to or larger than the whole desktop region, the cursor is unclipped.
2140 * Returns zero on failure, non-zero on success.
2142 int macdrv_clip_cursor(CGRect rect)
2147 WineApplicationController* controller = [WineApplicationController sharedController];
2148 BOOL clipping = FALSE;
2150 if (!CGRectIsInfinite(rect))
2152 NSRect nsrect = NSRectFromCGRect(rect);
2155 /* Convert the rectangle from top-down coords to bottom-up. */
2156 [controller flipRect:&nsrect];
2159 for (screen in [NSScreen screens])
2161 if (!NSContainsRect(nsrect, [screen frame]))
2170 ret = [controller startClippingCursor:rect];
2172 ret = [controller stopClippingCursor];
2178 /***********************************************************************
2179 * macdrv_set_application_icon
2181 * Set the application icon. The images array contains CGImages. If
2182 * there are more than one, then they represent different sizes or
2183 * color depths from the icon resource. If images is NULL or empty,
2184 * restores the default application image.
2186 void macdrv_set_application_icon(CFArrayRef images)
2188 NSArray* imageArray = (NSArray*)images;
2190 OnMainThreadAsync(^{
2191 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2195 /***********************************************************************
2198 void macdrv_quit_reply(int reply)
2201 [NSApp replyToApplicationShouldTerminate:reply];
2205 /***********************************************************************
2206 * macdrv_using_input_method
2208 int macdrv_using_input_method(void)
2213 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2219 /***********************************************************************
2220 * macdrv_set_mouse_capture_window
2222 void macdrv_set_mouse_capture_window(macdrv_window window)
2224 WineWindow* w = (WineWindow*)window;
2227 [[WineApplicationController sharedController] setMouseCaptureWindow:w];