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) NSCursor* cursor;
84 @property (retain, nonatomic) NSImage* applicationIcon;
85 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
86 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
88 - (void) setupObservations;
89 - (void) applicationDidBecomeActive:(NSNotification *)notification;
91 static void PerformRequest(void *info);
96 @implementation WineApplicationController
98 @synthesize keyboardType, lastFlagsChanged;
99 @synthesize applicationIcon;
100 @synthesize cursorFrames, cursorTimer, cursor;
101 @synthesize mouseCaptureWindow;
105 if (self == [WineApplicationController class])
107 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
108 @"", @"NSQuotedKeystrokeBinding",
109 @"", @"NSRepeatCountBinding",
110 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
112 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
116 + (WineApplicationController*) sharedController
118 static WineApplicationController* sharedController;
119 static dispatch_once_t once;
121 dispatch_once(&once, ^{
122 sharedController = [[self alloc] init];
125 return sharedController;
133 CFRunLoopSourceContext context = { 0 };
134 context.perform = PerformRequest;
135 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
141 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
142 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
144 requests = [[NSMutableArray alloc] init];
145 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
147 eventQueues = [[NSMutableArray alloc] init];
148 eventQueuesLock = [[NSLock alloc] init];
150 keyWindows = [[NSMutableArray alloc] init];
152 originalDisplayModes = [[NSMutableDictionary alloc] init];
153 latentDisplayModes = [[NSMutableDictionary alloc] init];
155 warpRecords = [[NSMutableArray alloc] init];
157 windowsBeingDragged = [[NSMutableSet alloc] init];
159 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
160 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
166 [self setupObservations];
168 keyboardType = LMGetKbdType();
170 if ([NSApp isActive])
171 [self applicationDidBecomeActive:nil];
178 [windowsBeingDragged release];
180 [screenFrameCGRects release];
181 [applicationIcon release];
182 [warpRecords release];
183 [cursorTimer release];
184 [cursorFrames release];
185 [latentDisplayModes release];
186 [originalDisplayModes release];
187 [keyWindows release];
188 [eventQueues release];
189 [eventQueuesLock release];
190 if (requestsManipQueue) dispatch_release(requestsManipQueue);
194 CFRunLoopSourceInvalidate(requestSource);
195 CFRelease(requestSource);
200 - (void) transformProcessToForeground
202 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
206 NSString* bundleName;
210 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
211 [NSApp activateIgnoringOtherApps:YES];
213 mainMenu = [[[NSMenu alloc] init] autorelease];
216 submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
217 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
219 if ([bundleName length])
220 title = [NSString stringWithFormat:@"Hide %@", bundleName];
223 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
225 item = [submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
226 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
228 item = [submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
230 [submenu addItem:[NSMenuItem separatorItem]];
232 if ([bundleName length])
233 title = [NSString stringWithFormat:@"Quit %@", bundleName];
236 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
237 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
238 item = [[[NSMenuItem alloc] init] autorelease];
239 [item setTitle:@"Wine"];
240 [item setSubmenu:submenu];
241 [mainMenu addItem:item];
244 submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
245 [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
246 [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
247 if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
249 item = [submenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
250 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
252 [submenu addItem:[NSMenuItem separatorItem]];
253 [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
254 item = [[[NSMenuItem alloc] init] autorelease];
255 [item setTitle:@"Window"];
256 [item setSubmenu:submenu];
257 [mainMenu addItem:item];
259 [NSApp setMainMenu:mainMenu];
260 [NSApp setWindowsMenu:submenu];
262 [NSApp setApplicationIconImage:self.applicationIcon];
266 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
268 PerformRequest(NULL);
274 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
275 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
277 inMode:NSDefaultRunLoopMode
280 [NSApp sendEvent:event];
284 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
285 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
290 - (BOOL) registerEventQueue:(WineEventQueue*)queue
292 [eventQueuesLock lock];
293 [eventQueues addObject:queue];
294 [eventQueuesLock unlock];
298 - (void) unregisterEventQueue:(WineEventQueue*)queue
300 [eventQueuesLock lock];
301 [eventQueues removeObjectIdenticalTo:queue];
302 [eventQueuesLock unlock];
305 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
307 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
310 - (double) ticksForEventTime:(NSTimeInterval)eventTime
312 return (eventTime + eventTimeAdjustment) * 1000;
315 /* Invalidate old focus offers across all queues. */
316 - (void) invalidateGotFocusEvents
318 WineEventQueue* queue;
322 [eventQueuesLock lock];
323 for (queue in eventQueues)
325 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
328 [eventQueuesLock unlock];
331 - (void) windowGotFocus:(WineWindow*)window
335 [self invalidateGotFocusEvents];
337 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
338 event->window_got_focus.serial = windowFocusSerial;
340 event->window_got_focus.tried_windows = [triedWindows retain];
342 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
343 [window.queue postEvent:event];
344 macdrv_release_event(event);
347 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
349 if (event->window_got_focus.serial == windowFocusSerial)
351 NSMutableArray* windows = [keyWindows mutableCopy];
352 NSNumber* windowNumber;
355 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
357 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
358 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
359 ![windows containsObject:window])
360 [windows addObject:window];
363 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
364 [triedWindows addObject:(WineWindow*)event->window];
365 for (window in windows)
367 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
369 [window makeKeyWindow];
378 - (void) keyboardSelectionDidChange
380 TISInputSourceRef inputSourceLayout;
382 inputSourceIsInputMethodValid = FALSE;
384 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
385 if (inputSourceLayout)
388 uchr = TISGetInputSourceProperty(inputSourceLayout,
389 kTISPropertyUnicodeKeyLayoutData);
393 WineEventQueue* queue;
395 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
396 event->keyboard_changed.keyboard_type = self.keyboardType;
397 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
398 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
399 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
401 if (event->keyboard_changed.uchr)
403 [eventQueuesLock lock];
405 for (queue in eventQueues)
406 [queue postEvent:event];
408 [eventQueuesLock unlock];
411 macdrv_release_event(event);
414 CFRelease(inputSourceLayout);
418 - (void) enabledKeyboardInputSourcesChanged
420 macdrv_layout_list_needs_update = TRUE;
423 - (CGFloat) primaryScreenHeight
425 if (!primaryScreenHeightValid)
427 NSArray* screens = [NSScreen screens];
428 NSUInteger count = [screens count];
435 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
436 primaryScreenHeightValid = TRUE;
438 size = count * sizeof(CGRect);
439 if (!screenFrameCGRects)
440 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
442 [screenFrameCGRects setLength:size];
444 rect = [screenFrameCGRects mutableBytes];
445 for (screen in screens)
447 CGRect temp = NSRectToCGRect([screen frame]);
448 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
453 return 1280; /* arbitrary value */
456 return primaryScreenHeight;
459 - (NSPoint) flippedMouseLocation:(NSPoint)point
461 /* This relies on the fact that Cocoa's mouse location points are
462 actually off by one (precisely because they were flipped from
463 Quartz screen coordinates using this same technique). */
464 point.y = [self primaryScreenHeight] - point.y;
468 - (void) flipRect:(NSRect*)rect
470 // We don't use -primaryScreenHeight here so there's no chance of having
471 // out-of-date cached info. This method is called infrequently enough
472 // that getting the screen height each time is not prohibitively expensive.
473 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
476 - (WineWindow*) frontWineWindow
478 NSNumber* windowNumber;
479 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
481 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
482 if ([window isKindOfClass:[WineWindow class]] && [window screen])
483 return (WineWindow*)window;
489 - (void) adjustWindowLevels:(BOOL)active
491 NSArray* windowNumbers;
492 NSMutableArray* wineWindows;
493 NSNumber* windowNumber;
494 NSUInteger nextFloatingIndex = 0;
495 __block NSInteger maxLevel = NSIntegerMin;
496 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
497 __block WineWindow* prev = nil;
500 if ([NSApp isHidden]) return;
502 windowNumbers = [NSWindow windowNumbersWithOptions:0];
503 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
505 // For the most part, we rely on the window server's ordering of the windows
506 // to be authoritative. The one exception is if the "floating" property of
507 // one of the windows has been changed, it may be in the wrong level and thus
508 // in the order. This method is what's supposed to fix that up. So build
509 // a list of Wine windows sorted first by floating-ness and then by order
510 // as indicated by the window server.
511 for (windowNumber in windowNumbers)
513 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
514 if ([window isKindOfClass:[WineWindow class]])
517 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
519 [wineWindows addObject:window];
523 NSDisableScreenUpdates();
525 // Go from back to front so that all windows in front of one which is
526 // elevated for full-screen are also elevated.
527 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
528 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
529 WineWindow* window = (WineWindow*)obj;
530 NSInteger origLevel = [window level];
531 NSInteger newLevel = [window minimumLevelForActive:active];
533 if (newLevel < maxLevel)
538 if (!window.floating && maxNonfloatingLevel < newLevel)
539 maxNonfloatingLevel = newLevel;
541 if (newLevel != origLevel)
543 [window setLevel:newLevel];
545 // -setLevel: puts the window at the front of its new level. If
546 // we decreased the level, that's good (it was in front of that
547 // level before, so it should still be now). But if we increased
548 // the level, the window should be toward the back (but still
549 // ahead of the previous windows we did this to).
550 if (origLevel < newLevel)
553 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
555 [window orderBack:nil];
562 NSEnableScreenUpdates();
564 [wineWindows release];
566 // The above took care of the visible windows on the current space. That
567 // leaves windows on other spaces, minimized windows, and windows which
568 // are not ordered in. We want to leave windows on other spaces alone
569 // so the space remains just as they left it (when viewed in Exposé or
570 // Mission Control, for example). We'll adjust the window levels again
571 // after we switch to another space, anyway. Windows which aren't
572 // ordered in will be handled when we order them in. Minimized windows
573 // on the current space should be set to the level they would have gotten
574 // if they were at the front of the windows with the same floating-ness,
575 // because that's where they'll go if/when they are unminimized. Again,
576 // for good measure we'll adjust window levels again when a window is
578 for (window in [NSApp windows])
580 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
581 [window isOnActiveSpace])
583 NSInteger origLevel = [window level];
584 NSInteger newLevel = [window minimumLevelForActive:YES];
585 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
587 if (newLevel < maxLevelForType)
588 newLevel = maxLevelForType;
590 if (newLevel != origLevel)
591 [window setLevel:newLevel];
596 - (void) adjustWindowLevels
598 [self adjustWindowLevels:[NSApp isActive]];
601 - (void) updateFullscreenWindows
603 if (capture_displays_for_fullscreen && [NSApp isActive])
605 BOOL anyFullscreen = FALSE;
606 NSNumber* windowNumber;
607 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
609 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
610 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
612 anyFullscreen = TRUE;
619 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
620 displaysCapturedForFullscreen = TRUE;
622 else if (displaysCapturedForFullscreen)
624 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
625 displaysCapturedForFullscreen = FALSE;
630 - (void) activeSpaceDidChange
632 [self updateFullscreenWindows];
633 [self adjustWindowLevels];
636 - (void) sendDisplaysChanged:(BOOL)activating
639 WineEventQueue* queue;
641 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
642 event->displays_changed.activating = activating;
644 [eventQueuesLock lock];
646 // If we're activating, then we just need one of our threads to get the
647 // event, so it can send it directly to the desktop window. Otherwise,
648 // we need all of the threads to get it because we don't know which owns
649 // the desktop window and only that one will do anything with it.
650 if (activating) event->deliver = 1;
652 for (queue in eventQueues)
653 [queue postEvent:event];
654 [eventQueuesLock unlock];
656 macdrv_release_event(event);
659 // We can compare two modes directly using CFEqual, but that may require that
660 // they are identical to a level that we don't need. In particular, when the
661 // OS switches between the integrated and discrete GPUs, the set of display
662 // modes can change in subtle ways. We're interested in whether two modes
663 // match in their most salient features, even if they aren't identical.
664 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
666 NSString *encoding1, *encoding2;
667 uint32_t ioflags1, ioflags2, different;
668 double refresh1, refresh2;
670 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
671 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
673 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
674 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
675 if (![encoding1 isEqualToString:encoding2]) return FALSE;
677 ioflags1 = CGDisplayModeGetIOFlags(mode1);
678 ioflags2 = CGDisplayModeGetIOFlags(mode2);
679 different = ioflags1 ^ ioflags2;
680 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
681 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
684 refresh1 = CGDisplayModeGetRefreshRate(mode1);
685 if (refresh1 == 0) refresh1 = 60;
686 refresh2 = CGDisplayModeGetRefreshRate(mode2);
687 if (refresh2 == 0) refresh2 = 60;
688 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
693 - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
695 CGDisplayModeRef ret = NULL;
696 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
697 for (id candidateModeObject in modes)
699 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
700 if ([self mode:candidateMode matchesMode:mode])
709 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
712 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
713 CGDisplayModeRef originalMode;
715 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
717 if (originalMode && [self mode:mode matchesMode:originalMode])
719 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
721 CGRestorePermanentDisplayConfiguration();
722 if (!displaysCapturedForFullscreen)
723 CGReleaseAllDisplays();
724 [originalDisplayModes removeAllObjects];
727 else // ... otherwise, try to restore just the one display
729 mode = [self modeMatchingMode:mode forDisplay:displayID];
730 if (mode && CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
732 [originalDisplayModes removeObjectForKey:displayIDKey];
739 BOOL active = [NSApp isActive];
740 CGDisplayModeRef currentMode;
742 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
744 currentMode = CGDisplayCopyDisplayMode(displayID);
745 if (!currentMode) // Invalid display ID
748 if ([self mode:mode matchesMode:currentMode]) // Already there!
750 CGDisplayModeRelease(currentMode);
754 CGDisplayModeRelease(currentMode);
757 mode = [self modeMatchingMode:mode forDisplay:displayID];
761 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
762 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
766 // If we get here, we have the displays captured. If we don't
767 // know the original mode of the display, the current mode must
768 // be the original. We should re-query the current mode since
769 // another process could have changed it between when we last
770 // checked and when we captured the displays.
772 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
775 ret = (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr);
776 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
777 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
778 else if (![originalDisplayModes count])
780 CGRestorePermanentDisplayConfiguration();
781 if (!displaysCapturedForFullscreen)
782 CGReleaseAllDisplays();
786 CGDisplayModeRelease(currentMode);
790 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
797 [self adjustWindowLevels];
802 - (BOOL) areDisplaysCaptured
804 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
807 - (void) updateCursor:(BOOL)force
809 if (force || lastTargetWindow)
811 if (clientWantsCursorHidden && !cursorHidden)
817 if (!cursorIsCurrent)
820 cursorIsCurrent = TRUE;
823 if (!clientWantsCursorHidden && cursorHidden)
826 cursorHidden = FALSE;
833 [[NSCursor arrowCursor] set];
834 cursorIsCurrent = FALSE;
839 cursorHidden = FALSE;
846 if (!clientWantsCursorHidden)
848 clientWantsCursorHidden = TRUE;
849 [self updateCursor:TRUE];
853 - (void) unhideCursor
855 if (clientWantsCursorHidden)
857 clientWantsCursorHidden = FALSE;
858 [self updateCursor:FALSE];
862 - (void) setCursor:(NSCursor*)newCursor
864 if (newCursor != cursor)
867 cursor = [newCursor retain];
868 cursorIsCurrent = FALSE;
869 [self updateCursor:FALSE];
875 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
876 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
877 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
878 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
881 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
882 hotSpot = CGPointZero;
883 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
888 - (void) nextCursorFrame:(NSTimer*)theTimer
891 NSTimeInterval duration;
895 if (cursorFrame >= [cursorFrames count])
899 frame = [cursorFrames objectAtIndex:cursorFrame];
900 duration = [[frame objectForKey:@"duration"] doubleValue];
901 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
902 [cursorTimer setFireDate:date];
905 - (void) setCursorWithFrames:(NSArray*)frames
907 if (self.cursorFrames == frames)
910 self.cursorFrames = frames;
912 [cursorTimer invalidate];
913 self.cursorTimer = nil;
917 if ([frames count] > 1)
919 NSDictionary* frame = [frames objectAtIndex:0];
920 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
921 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
922 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
925 selector:@selector(nextCursorFrame:)
927 repeats:YES] autorelease];
928 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
935 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
937 NSImage* nsimage = nil;
941 NSSize bestSize = NSZeroSize;
944 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
946 for (image in images)
948 CGImageRef cgimage = (CGImageRef)image;
949 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
952 NSSize size = [imageRep size];
954 [nsimage addRepresentation:imageRep];
957 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
962 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
963 [nsimage setSize:bestSize];
968 self.applicationIcon = nsimage;
971 - (void) handleCommandTab
973 if ([NSApp isActive])
975 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
976 NSRunningApplication* app;
977 NSRunningApplication* otherValidApp = nil;
979 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
982 for (displayID in originalDisplayModes)
984 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
985 [latentDisplayModes setObject:(id)mode forKey:displayID];
986 CGDisplayModeRelease(mode);
989 CGRestorePermanentDisplayConfiguration();
990 CGReleaseAllDisplays();
991 [originalDisplayModes removeAllObjects];
992 displaysCapturedForFullscreen = FALSE;
995 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
997 if (![app isEqual:thisApp] && !app.terminated &&
998 app.activationPolicy == NSApplicationActivationPolicyRegular)
1002 // There's another visible app. Just hide ourselves and let
1003 // the system activate the other app.
1009 otherValidApp = app;
1013 // Didn't find a visible GUI app. Try the Finder or, if that's not
1014 // running, the first hidden GUI app. If even that doesn't work, we
1015 // just fail to switch and remain the active app.
1016 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1017 if (!app) app = otherValidApp;
1019 [app activateWithOptions:0];
1024 * ---------- Cursor clipping methods ----------
1026 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1027 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1028 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1029 * general case, we leverage that. We disassociate mouse movements from
1030 * the cursor position and then move the cursor manually, keeping it within
1031 * the clipping rectangle.
1033 * Moving the cursor manually isn't enough. We need to modify the event
1034 * stream so that the events have the new location, too. We need to do
1035 * this at a point before the events enter Cocoa, so that Cocoa will assign
1036 * the correct window to the event. So, we install a Quartz event tap to
1039 * Also, there's a complication when we move the cursor. We use
1040 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1041 * events, but the change of cursor position is incorporated into the
1042 * deltas of the next mouse move event. When the mouse is disassociated
1043 * from the cursor position, we need the deltas to only reflect actual
1044 * device movement, not programmatic changes. So, the event tap cancels
1045 * out the change caused by our calls to CGWarpMouseCursorPosition().
1047 - (void) clipCursorLocation:(CGPoint*)location
1049 if (location->x < CGRectGetMinX(cursorClipRect))
1050 location->x = CGRectGetMinX(cursorClipRect);
1051 if (location->y < CGRectGetMinY(cursorClipRect))
1052 location->y = CGRectGetMinY(cursorClipRect);
1053 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1054 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1055 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1056 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1059 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1061 CGPoint oldLocation;
1063 if (currentLocation)
1064 oldLocation = *currentLocation;
1066 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1068 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1070 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1073 warpRecord.from = oldLocation;
1074 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1076 /* Actually move the cursor. */
1077 err = CGWarpMouseCursorPosition(*newLocation);
1078 if (err != kCGErrorSuccess)
1081 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1082 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1084 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1086 warpRecord.to = *newLocation;
1087 [warpRecords addObject:warpRecord];
1094 - (BOOL) isMouseMoveEventType:(CGEventType)type
1098 case kCGEventMouseMoved:
1099 case kCGEventLeftMouseDragged:
1100 case kCGEventRightMouseDragged:
1101 case kCGEventOtherMouseDragged:
1108 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1110 int warpsFinished = 0;
1111 for (WarpRecord* warpRecord in warpRecords)
1113 if (warpRecord.timeAfter < eventTime ||
1114 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1120 return warpsFinished;
1123 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1124 type:(CGEventType)type
1125 event:(CGEventRef)event
1127 CGEventTimestamp eventTime;
1128 CGPoint eventLocation, cursorLocation;
1130 if (type == kCGEventTapDisabledByUserInput)
1132 if (type == kCGEventTapDisabledByTimeout)
1134 CGEventTapEnable(cursorClippingEventTap, TRUE);
1138 if (!clippingCursor)
1141 eventTime = CGEventGetTimestamp(event);
1142 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1144 eventLocation = CGEventGetLocation(event);
1146 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1148 if ([self isMouseMoveEventType:type])
1150 double deltaX, deltaY;
1151 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1154 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1155 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1157 for (i = 0; i < warpsFinished; i++)
1159 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1160 deltaX -= warpRecord.to.x - warpRecord.from.x;
1161 deltaY -= warpRecord.to.y - warpRecord.from.y;
1162 [warpRecords removeObjectAtIndex:0];
1167 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1168 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1171 synthesizedLocation.x += deltaX;
1172 synthesizedLocation.y += deltaY;
1175 // If the event is destined for another process, don't clip it. This may
1176 // happen if the user activates Exposé or Mission Control. In that case,
1177 // our app does not resign active status, so clipping is still in effect,
1178 // but the cursor should not actually be clipped.
1180 // In addition, the fact that mouse moves may have been delivered to a
1181 // different process means we have to treat the next one we receive as
1182 // absolute rather than relative.
1183 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1184 [self clipCursorLocation:&synthesizedLocation];
1186 lastSetCursorPositionTime = lastEventTapEventTime;
1188 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1189 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1190 CGEventSetLocation(event, synthesizedLocation);
1195 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1196 CGEventRef event, void *refcon)
1198 WineApplicationController* controller = refcon;
1199 return [controller eventTapWithProxy:proxy type:type event:event];
1202 - (BOOL) installEventTap
1204 ProcessSerialNumber psn;
1206 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1207 CGEventMaskBit(kCGEventLeftMouseUp) |
1208 CGEventMaskBit(kCGEventRightMouseDown) |
1209 CGEventMaskBit(kCGEventRightMouseUp) |
1210 CGEventMaskBit(kCGEventMouseMoved) |
1211 CGEventMaskBit(kCGEventLeftMouseDragged) |
1212 CGEventMaskBit(kCGEventRightMouseDragged) |
1213 CGEventMaskBit(kCGEventOtherMouseDown) |
1214 CGEventMaskBit(kCGEventOtherMouseUp) |
1215 CGEventMaskBit(kCGEventOtherMouseDragged) |
1216 CGEventMaskBit(kCGEventScrollWheel);
1217 CFRunLoopSourceRef source;
1219 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1221 if (cursorClippingEventTap)
1224 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1225 // framework with dlsym() because the Win32 function of the same name
1227 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1231 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1232 if (!pGetCurrentProcess)
1234 dlclose(appServices);
1238 err = pGetCurrentProcess(&psn);
1239 dlclose(appServices);
1243 // We create an annotated session event tap rather than a process-specific
1244 // event tap because we need to programmatically move the cursor even when
1245 // mouse moves are directed to other processes. We disable our tap when
1246 // other processes are active, but things like Exposé are handled by other
1247 // processes even when we remain active.
1248 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1249 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1250 if (!cursorClippingEventTap)
1253 CGEventTapEnable(cursorClippingEventTap, FALSE);
1255 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1258 CFRelease(cursorClippingEventTap);
1259 cursorClippingEventTap = NULL;
1263 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1268 - (BOOL) setCursorPosition:(CGPoint)pos
1272 if ([windowsBeingDragged count])
1274 else if (clippingCursor)
1276 [self clipCursorLocation:&pos];
1278 ret = [self warpCursorTo:&pos from:NULL];
1279 synthesizedLocation = pos;
1282 // We want to discard mouse-move events that have already been
1283 // through the event tap, because it's too late to account for
1284 // the setting of the cursor position with them. However, the
1285 // events that may be queued with times after that but before
1286 // the above warp can still be used. So, use the last event
1287 // tap event time so that -sendEvent: doesn't discard them.
1288 lastSetCursorPositionTime = lastEventTapEventTime;
1293 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1296 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1298 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1299 // the mouse from the cursor position for 0.25 seconds. This means
1300 // that mouse movement during that interval doesn't move the cursor
1301 // and events carry a constant location (the warped-to position)
1302 // even though they have delta values. This screws us up because
1303 // the accumulated deltas we send to Wine don't match any eventual
1304 // absolute position we send (like with a button press). We can
1305 // work around this by simply forcibly reassociating the mouse and
1307 CGAssociateMouseAndMouseCursorPosition(true);
1313 WineEventQueue* queue;
1315 // Discard all pending mouse move events.
1316 [eventQueuesLock lock];
1317 for (queue in eventQueues)
1319 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1320 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1322 [queue resetMouseEventPositions:pos];
1324 [eventQueuesLock unlock];
1330 - (void) activateCursorClipping
1332 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1334 CGEventTapEnable(cursorClippingEventTap, TRUE);
1335 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1339 - (void) deactivateCursorClipping
1341 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1343 CGEventTapEnable(cursorClippingEventTap, FALSE);
1344 [warpRecords removeAllObjects];
1345 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1349 - (void) updateCursorClippingState
1351 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1352 [self activateCursorClipping];
1354 [self deactivateCursorClipping];
1357 - (BOOL) startClippingCursor:(CGRect)rect
1361 if (!cursorClippingEventTap && ![self installEventTap])
1364 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1365 CGEventTapIsEnabled(cursorClippingEventTap))
1368 err = CGAssociateMouseAndMouseCursorPosition(false);
1369 if (err != kCGErrorSuccess)
1372 clippingCursor = TRUE;
1373 cursorClipRect = rect;
1374 [self updateCursorClippingState];
1379 - (BOOL) stopClippingCursor
1381 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1382 if (err != kCGErrorSuccess)
1385 clippingCursor = FALSE;
1386 [self updateCursorClippingState];
1391 - (BOOL) isKeyPressed:(uint16_t)keyCode
1393 int bits = sizeof(pressedKeyCodes[0]) * 8;
1394 int index = keyCode / bits;
1395 uint32_t mask = 1 << (keyCode % bits);
1396 return (pressedKeyCodes[index] & mask) != 0;
1399 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1401 int bits = sizeof(pressedKeyCodes[0]) * 8;
1402 int index = keyCode / bits;
1403 uint32_t mask = 1 << (keyCode % bits);
1405 pressedKeyCodes[index] |= mask;
1407 pressedKeyCodes[index] &= ~mask;
1410 - (void) handleMouseMove:(NSEvent*)anEvent
1412 WineWindow* targetWindow;
1413 BOOL drag = [anEvent type] != NSMouseMoved;
1415 if ([windowsBeingDragged count])
1417 else if (mouseCaptureWindow)
1418 targetWindow = mouseCaptureWindow;
1420 targetWindow = (WineWindow*)[anEvent window];
1423 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1424 event indicates its window is the main window, even if the cursor is
1425 over a different window. Find the actual WineWindow that is under the
1426 cursor and post the event as being for that window. */
1427 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1428 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1429 NSInteger windowUnderNumber;
1431 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1432 belowWindowWithWindowNumber:0];
1433 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1434 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1438 if ([targetWindow isKindOfClass:[WineWindow class]])
1440 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1441 macdrv_event* event;
1444 // If we recently warped the cursor (other than in our cursor-clipping
1445 // event tap), discard mouse move events until we see an event which is
1446 // later than that time.
1447 if (lastSetCursorPositionTime)
1449 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1452 lastSetCursorPositionTime = 0;
1453 forceNextMouseMoveAbsolute = TRUE;
1456 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1459 forceNextMouseMoveAbsolute = FALSE;
1463 // Send absolute move events if the cursor is in the interior of
1464 // its range. Only send relative moves if the cursor is pinned to
1465 // the boundaries of where it can go. We compute the position
1466 // that's one additional point in the direction of movement. If
1467 // that is outside of the clipping rect or desktop region (the
1468 // union of the screen frames), then we figure the cursor would
1469 // have moved outside if it could but it was pinned.
1470 CGPoint computedPoint = point;
1471 CGFloat deltaX = [anEvent deltaX];
1472 CGFloat deltaY = [anEvent deltaY];
1476 else if (deltaX < -0.001)
1481 else if (deltaY < -0.001)
1484 // Assume cursor is pinned for now
1486 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1488 const CGRect* rects;
1489 NSUInteger count, i;
1491 // Caches screenFrameCGRects if necessary
1492 [self primaryScreenHeight];
1494 rects = [screenFrameCGRects bytes];
1495 count = [screenFrameCGRects length] / sizeof(rects[0]);
1497 for (i = 0; i < count; i++)
1499 if (CGRectContainsPoint(rects[i], computedPoint))
1511 [self clipCursorLocation:&point];
1513 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1514 event->mouse_moved.x = point.x;
1515 event->mouse_moved.y = point.y;
1517 mouseMoveDeltaX = 0;
1518 mouseMoveDeltaY = 0;
1522 /* Add event delta to accumulated delta error */
1523 /* deltaY is already flipped */
1524 mouseMoveDeltaX += [anEvent deltaX];
1525 mouseMoveDeltaY += [anEvent deltaY];
1527 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1528 event->mouse_moved.x = mouseMoveDeltaX;
1529 event->mouse_moved.y = mouseMoveDeltaY;
1531 /* Keep the remainder after integer truncation. */
1532 mouseMoveDeltaX -= event->mouse_moved.x;
1533 mouseMoveDeltaY -= event->mouse_moved.y;
1536 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1538 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1539 event->mouse_moved.drag = drag;
1541 [targetWindow.queue postEvent:event];
1544 macdrv_release_event(event);
1546 lastTargetWindow = targetWindow;
1549 lastTargetWindow = nil;
1551 [self updateCursor:FALSE];
1554 - (void) handleMouseButton:(NSEvent*)theEvent
1556 WineWindow* window = (WineWindow*)[theEvent window];
1557 NSEventType type = [theEvent type];
1558 BOOL broughtWindowForward = FALSE;
1560 if ([window isKindOfClass:[WineWindow class]] &&
1561 !window.disabled && !window.noActivate &&
1562 type == NSLeftMouseDown &&
1563 (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1565 NSWindowButton windowButton;
1567 broughtWindowForward = TRUE;
1569 /* Any left-click on our window anyplace other than the close or
1570 minimize buttons will bring it forward. */
1571 for (windowButton = NSWindowCloseButton;
1572 windowButton <= NSWindowMiniaturizeButton;
1575 NSButton* button = [window standardWindowButton:windowButton];
1578 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1579 if ([button mouse:point inRect:[button bounds]])
1581 broughtWindowForward = FALSE;
1587 if (broughtWindowForward)
1589 // Clicking on a child window does not normally reorder it with
1590 // respect to its siblings, but we want it to. We have to do it
1592 NSWindow* parent = [window parentWindow];
1593 NSInteger level = [window level];
1594 __block BOOL needReorder = FALSE;
1595 NSMutableArray* higherLevelSiblings = [NSMutableArray array];
1597 // If the window is already the last child or if it's only below
1598 // children with higher window level, then no need to reorder it.
1599 [[parent childWindows] enumerateObjectsWithOptions:NSEnumerationReverse
1600 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1601 WineWindow* child = obj;
1602 if (child == window)
1604 else if ([child level] <= level)
1610 [higherLevelSiblings insertObject:child atIndex:0];
1615 WineWindow* sibling;
1617 NSDisableScreenUpdates();
1619 [parent removeChildWindow:window];
1620 for (sibling in higherLevelSiblings)
1621 [parent removeChildWindow:sibling];
1623 [parent addChildWindow:window ordered:NSWindowAbove];
1624 for (sibling in higherLevelSiblings)
1626 // Setting a window as a child can reset its level to be
1627 // the same as the parent, so save it and restore it.
1628 // The call to -setLevel: puts the window at the front
1629 // of its level but testing shows that that's what Cocoa
1630 // does when you click on any window in an ownership
1631 // hierarchy, anyway.
1632 level = [sibling level];
1633 [parent addChildWindow:sibling ordered:NSWindowAbove];
1634 [sibling setLevel:level];
1637 NSEnableScreenUpdates();
1642 if ([windowsBeingDragged count])
1644 else if (mouseCaptureWindow)
1645 window = mouseCaptureWindow;
1647 if ([window isKindOfClass:[WineWindow class]])
1649 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1650 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1654 [self clipCursorLocation:&pt];
1658 if (mouseCaptureWindow)
1662 // Test if the click was in the window's content area.
1663 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1664 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1665 process = NSMouseInRect(nspoint, contentRect, NO);
1666 if (process && [window styleMask] & NSResizableWindowMask)
1668 // Ignore clicks in the grow box (resize widget).
1669 HIPoint origin = { 0, 0 };
1670 HIThemeGrowBoxDrawInfo info = { 0 };
1674 info.kind = kHIThemeGrowBoxKindNormal;
1675 info.direction = kThemeGrowRight | kThemeGrowDown;
1676 if ([window styleMask] & NSUtilityWindowMask)
1677 info.size = kHIThemeGrowBoxSizeSmall;
1679 info.size = kHIThemeGrowBoxSizeNormal;
1681 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1682 if (status == noErr)
1684 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1685 NSMinY(contentRect),
1687 bounds.size.height);
1688 process = !NSMouseInRect(nspoint, growBox, NO);
1693 unmatchedMouseDowns |= NSEventMaskFromType(type);
1697 NSEventType downType = type - 1;
1698 NSUInteger downMask = NSEventMaskFromType(downType);
1699 process = (unmatchedMouseDowns & downMask) != 0;
1700 unmatchedMouseDowns &= ~downMask;
1705 macdrv_event* event;
1707 event = macdrv_create_event(MOUSE_BUTTON, window);
1708 event->mouse_button.button = [theEvent buttonNumber];
1709 event->mouse_button.pressed = pressed;
1710 event->mouse_button.x = pt.x;
1711 event->mouse_button.y = pt.y;
1712 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1714 [window.queue postEvent:event];
1716 macdrv_release_event(event);
1718 else if (broughtWindowForward)
1720 [[window ancestorWineWindow] postBroughtForwardEvent];
1721 if (![window isKeyWindow])
1722 [self windowGotFocus:window];
1726 // Since mouse button events deliver absolute cursor position, the
1727 // accumulating delta from move events is invalidated. Make sure
1728 // next mouse move event starts over from an absolute baseline.
1729 // Also, it's at least possible that the title bar widgets (e.g. close
1730 // button, etc.) could enter an internal event loop on a mouse down that
1731 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1732 // dragged events and, after that, any notion of the cursor position
1733 // computed from accumulating deltas would be wrong.
1734 forceNextMouseMoveAbsolute = TRUE;
1737 - (void) handleScrollWheel:(NSEvent*)theEvent
1741 if (mouseCaptureWindow)
1742 window = mouseCaptureWindow;
1744 window = (WineWindow*)[theEvent window];
1746 if ([window isKindOfClass:[WineWindow class]])
1748 CGEventRef cgevent = [theEvent CGEvent];
1749 CGPoint pt = CGEventGetLocation(cgevent);
1753 [self clipCursorLocation:&pt];
1755 if (mouseCaptureWindow)
1759 // Only process the event if it was in the window's content area.
1760 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1761 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1762 process = NSMouseInRect(nspoint, contentRect, NO);
1767 macdrv_event* event;
1769 BOOL continuous = FALSE;
1771 event = macdrv_create_event(MOUSE_SCROLL, window);
1772 event->mouse_scroll.x = pt.x;
1773 event->mouse_scroll.y = pt.y;
1774 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1776 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1780 /* Continuous scroll wheel events come from high-precision scrolling
1781 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1782 For these, we can get more precise data from the CGEvent API. */
1783 /* Axis 1 is vertical, axis 2 is horizontal. */
1784 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1785 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1789 double pixelsPerLine = 10;
1790 CGEventSourceRef source;
1792 /* The non-continuous values are in units of "lines", not pixels. */
1793 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1795 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1799 x = pixelsPerLine * [theEvent deltaX];
1800 y = pixelsPerLine * [theEvent deltaY];
1803 /* Mac: negative is right or down, positive is left or up.
1804 Win32: negative is left or down, positive is right or up.
1805 So, negate the X scroll value to translate. */
1808 /* The x,y values so far are in pixels. Win32 expects to receive some
1809 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1810 6 times the pixel value. */
1811 event->mouse_scroll.x_scroll = 6 * x;
1812 event->mouse_scroll.y_scroll = 6 * y;
1816 /* For non-continuous "clicky" wheels, if there was any motion, make
1817 sure there was at least WHEEL_DELTA motion. This is so, at slow
1818 speeds where the system's acceleration curve is actually reducing the
1819 scroll distance, the user is sure to get some action out of each click.
1820 For example, this is important for rotating though weapons in a
1821 first-person shooter. */
1822 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1823 event->mouse_scroll.x_scroll = 120;
1824 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1825 event->mouse_scroll.x_scroll = -120;
1827 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1828 event->mouse_scroll.y_scroll = 120;
1829 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1830 event->mouse_scroll.y_scroll = -120;
1833 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1834 [window.queue postEvent:event];
1836 macdrv_release_event(event);
1838 // Since scroll wheel events deliver absolute cursor position, the
1839 // accumulating delta from move events is invalidated. Make sure next
1840 // mouse move event starts over from an absolute baseline.
1841 forceNextMouseMoveAbsolute = TRUE;
1846 // Returns TRUE if the event was handled and caller should do nothing more
1847 // with it. Returns FALSE if the caller should process it as normal and
1848 // then call -didSendEvent:.
1849 - (BOOL) handleEvent:(NSEvent*)anEvent
1852 NSEventType type = [anEvent type];
1854 if (type == NSFlagsChanged)
1855 self.lastFlagsChanged = anEvent;
1856 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1857 type == NSRightMouseDragged || type == NSOtherMouseDragged)
1859 [self handleMouseMove:anEvent];
1860 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1862 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1863 type == NSRightMouseDown || type == NSRightMouseUp ||
1864 type == NSOtherMouseDown || type == NSOtherMouseUp)
1866 [self handleMouseButton:anEvent];
1867 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1869 else if (type == NSScrollWheel)
1871 [self handleScrollWheel:anEvent];
1872 ret = mouseCaptureWindow != nil;
1874 else if (type == NSKeyUp)
1876 uint16_t keyCode = [anEvent keyCode];
1877 if ([self isKeyPressed:keyCode])
1879 WineWindow* window = (WineWindow*)[anEvent window];
1880 [self noteKey:keyCode pressed:FALSE];
1881 if ([window isKindOfClass:[WineWindow class]])
1882 [window postKeyEvent:anEvent];
1885 else if (type == NSAppKitDefined)
1887 short subtype = [anEvent subtype];
1889 // These subtypes are not documented but they appear to mean
1890 // "a window is being dragged" and "a window is no longer being
1891 // dragged", respectively.
1892 if (subtype == 20 || subtype == 21)
1894 WineWindow* window = (WineWindow*)[anEvent window];
1895 if ([window isKindOfClass:[WineWindow class]])
1897 macdrv_event* event;
1902 [windowsBeingDragged addObject:window];
1903 eventType = WINDOW_DRAG_BEGIN;
1907 [windowsBeingDragged removeObject:window];
1908 eventType = WINDOW_DRAG_END;
1910 [self updateCursorClippingState];
1912 event = macdrv_create_event(eventType, window);
1913 [window.queue postEvent:event];
1914 macdrv_release_event(event);
1922 - (void) didSendEvent:(NSEvent*)anEvent
1924 NSEventType type = [anEvent type];
1926 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1928 NSUInteger modifiers = [anEvent modifierFlags];
1929 if ((modifiers & NSCommandKeyMask) &&
1930 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1932 // Command-Tab and Command-Shift-Tab would normally be intercepted
1933 // by the system to switch applications. If we're seeing it, it's
1934 // presumably because we've captured the displays, preventing
1935 // normal application switching. Do it manually.
1936 [self handleCommandTab];
1941 - (void) setupObservations
1943 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1944 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1945 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1947 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1950 usingBlock:^(NSNotification *note){
1951 NSWindow* window = [note object];
1952 [keyWindows removeObjectIdenticalTo:window];
1953 [keyWindows insertObject:window atIndex:0];
1956 [nc addObserverForName:NSWindowWillCloseNotification
1958 queue:[NSOperationQueue mainQueue]
1959 usingBlock:^(NSNotification *note){
1960 NSWindow* window = [note object];
1961 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1963 [keyWindows removeObjectIdenticalTo:window];
1964 if (window == lastTargetWindow)
1965 lastTargetWindow = nil;
1966 if (window == self.mouseCaptureWindow)
1967 self.mouseCaptureWindow = nil;
1968 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1970 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1971 [self updateFullscreenWindows];
1974 [windowsBeingDragged removeObject:window];
1975 [self updateCursorClippingState];
1978 [nc addObserver:self
1979 selector:@selector(keyboardSelectionDidChange)
1980 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1983 /* The above notification isn't sent unless the NSTextInputContext
1984 class has initialized itself. Poke it. */
1985 [NSTextInputContext self];
1987 [wsnc addObserver:self
1988 selector:@selector(activeSpaceDidChange)
1989 name:NSWorkspaceActiveSpaceDidChangeNotification
1992 [nc addObserver:self
1993 selector:@selector(releaseMouseCapture)
1994 name:NSMenuDidBeginTrackingNotification
1997 [dnc addObserver:self
1998 selector:@selector(releaseMouseCapture)
1999 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2001 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2003 [dnc addObserver:self
2004 selector:@selector(enabledKeyboardInputSourcesChanged)
2005 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2009 - (BOOL) inputSourceIsInputMethod
2011 if (!inputSourceIsInputMethodValid)
2013 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2016 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2017 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2018 CFRelease(inputSource);
2021 inputSourceIsInputMethod = FALSE;
2022 inputSourceIsInputMethodValid = TRUE;
2025 return inputSourceIsInputMethod;
2028 - (void) releaseMouseCapture
2030 // This might be invoked on a background thread by the distributed
2031 // notification center. Shunt it to the main thread.
2032 if (![NSThread isMainThread])
2034 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2038 if (mouseCaptureWindow)
2040 macdrv_event* event;
2042 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2043 [mouseCaptureWindow.queue postEvent:event];
2044 macdrv_release_event(event);
2048 - (void) unminimizeWindowIfNoneVisible
2050 if (![self frontWineWindow])
2052 for (WineWindow* window in [NSApp windows])
2054 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2056 [window deminiaturize:self];
2065 * ---------- NSApplicationDelegate methods ----------
2067 - (void)applicationDidBecomeActive:(NSNotification *)notification
2069 NSNumber* displayID;
2070 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2072 latentDisplayModes = [[NSMutableDictionary alloc] init];
2073 for (displayID in modesToRealize)
2075 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2076 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2079 [self updateCursorClippingState];
2081 [self updateFullscreenWindows];
2082 [self adjustWindowLevels:YES];
2085 [self unminimizeWindowIfNoneVisible];
2088 // If a Wine process terminates abruptly while it has the display captured
2089 // and switched to a different resolution, Mac OS X will uncapture the
2090 // displays and switch their resolutions back. However, the other Wine
2091 // processes won't have their notion of the desktop rect changed back.
2092 // This can lead them to refuse to draw or acknowledge clicks in certain
2093 // portions of their windows.
2095 // To solve this, we synthesize a displays-changed event whenever we're
2096 // activated. This will provoke a re-synchronization of Wine's notion of
2097 // the desktop rect with the actual state.
2098 [self sendDisplaysChanged:TRUE];
2100 // The cursor probably moved while we were inactive. Accumulated mouse
2101 // movement deltas are invalidated. Make sure the next mouse move event
2102 // starts over from an absolute baseline.
2103 forceNextMouseMoveAbsolute = TRUE;
2106 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2108 primaryScreenHeightValid = FALSE;
2109 [self sendDisplaysChanged:FALSE];
2110 [self adjustWindowLevels];
2112 // When the display configuration changes, the cursor position may jump.
2113 // Accumulated mouse movement deltas are invalidated. Make sure the next
2114 // mouse move event starts over from an absolute baseline.
2115 forceNextMouseMoveAbsolute = TRUE;
2118 - (void)applicationDidResignActive:(NSNotification *)notification
2120 macdrv_event* event;
2121 WineEventQueue* queue;
2123 [self updateCursorClippingState];
2125 [self invalidateGotFocusEvents];
2127 event = macdrv_create_event(APP_DEACTIVATED, nil);
2129 [eventQueuesLock lock];
2130 for (queue in eventQueues)
2131 [queue postEvent:event];
2132 [eventQueuesLock unlock];
2134 macdrv_release_event(event);
2136 [self releaseMouseCapture];
2139 - (void) applicationDidUnhide:(NSNotification*)aNotification
2141 [self adjustWindowLevels];
2144 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2146 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2147 // don't count as "visible windows" for this purpose.
2148 [self unminimizeWindowIfNoneVisible];
2152 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2154 NSApplicationTerminateReply ret = NSTerminateNow;
2155 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2156 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2157 macdrv_event* event;
2158 WineEventQueue* queue;
2160 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2162 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2165 case kAEReallyLogOut:
2166 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2168 case kAEShowRestartDialog:
2169 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2171 case kAEShowShutdownDialog:
2172 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2175 event->app_quit_requested.reason = QUIT_REASON_NONE;
2179 [eventQueuesLock lock];
2181 if ([eventQueues count])
2183 for (queue in eventQueues)
2184 [queue postEvent:event];
2185 ret = NSTerminateLater;
2188 [eventQueuesLock unlock];
2190 macdrv_release_event(event);
2195 - (void)applicationWillResignActive:(NSNotification *)notification
2197 [self adjustWindowLevels:NO];
2200 /***********************************************************************
2203 * Run-loop-source perform callback. Pull request blocks from the
2204 * array of queued requests and invoke them.
2206 static void PerformRequest(void *info)
2208 WineApplicationController* controller = [WineApplicationController sharedController];
2212 __block dispatch_block_t block;
2214 dispatch_sync(controller->requestsManipQueue, ^{
2215 if ([controller->requests count])
2217 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2218 [controller->requests removeObjectAtIndex:0];
2232 /***********************************************************************
2235 * Run a block on the main thread asynchronously.
2237 void OnMainThreadAsync(dispatch_block_t block)
2239 WineApplicationController* controller = [WineApplicationController sharedController];
2241 block = [block copy];
2242 dispatch_sync(controller->requestsManipQueue, ^{
2243 [controller->requests addObject:block];
2246 CFRunLoopSourceSignal(controller->requestSource);
2247 CFRunLoopWakeUp(CFRunLoopGetMain());
2252 /***********************************************************************
2255 void LogError(const char* func, NSString* format, ...)
2258 va_start(args, format);
2259 LogErrorv(func, format, args);
2263 /***********************************************************************
2266 void LogErrorv(const char* func, NSString* format, va_list args)
2268 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2270 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2271 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2277 /***********************************************************************
2278 * macdrv_window_rejected_focus
2280 * Pass focus to the next window that hasn't already rejected this same
2281 * WINDOW_GOT_FOCUS event.
2283 void macdrv_window_rejected_focus(const macdrv_event *event)
2286 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2290 /***********************************************************************
2291 * macdrv_get_input_source_info
2293 * Returns the keyboard layout uchr data, keyboard type and input source.
2295 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2298 TISInputSourceRef inputSourceLayout;
2300 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2301 if (inputSourceLayout)
2303 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2304 kTISPropertyUnicodeKeyLayoutData);
2305 *uchr = CFDataCreateCopy(NULL, data);
2306 CFRelease(inputSourceLayout);
2308 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2309 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2310 *input_source = TISCopyCurrentKeyboardInputSource();
2315 /***********************************************************************
2318 * Play the beep sound configured by the user in System Preferences.
2320 void macdrv_beep(void)
2322 OnMainThreadAsync(^{
2327 /***********************************************************************
2328 * macdrv_set_display_mode
2330 int macdrv_set_display_mode(const struct macdrv_display* display,
2331 CGDisplayModeRef display_mode)
2336 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2342 /***********************************************************************
2347 * If name is non-NULL, it is a selector for a class method on NSCursor
2348 * identifying the cursor to set. In that case, frames is ignored. If
2349 * name is NULL, then frames is used.
2351 * frames is an array of dictionaries. Each dictionary is a frame of
2352 * an animated cursor. Under the key "image" is a CGImage for the
2353 * frame. Under the key "duration" is a CFNumber time interval, in
2354 * seconds, for how long that frame is presented before proceeding to
2355 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2356 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2357 * This is the hot spot, measured in pixels down and to the right of the
2358 * top-left corner of the image.
2360 * If the array has exactly 1 element, the cursor is static, not
2361 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2363 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2367 sel = NSSelectorFromString((NSString*)name);
2370 OnMainThreadAsync(^{
2371 WineApplicationController* controller = [WineApplicationController sharedController];
2372 [controller setCursorWithFrames:nil];
2373 controller.cursor = [NSCursor performSelector:sel];
2374 [controller unhideCursor];
2379 NSArray* nsframes = (NSArray*)frames;
2380 if ([nsframes count])
2382 OnMainThreadAsync(^{
2383 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2388 OnMainThreadAsync(^{
2389 WineApplicationController* controller = [WineApplicationController sharedController];
2390 [controller setCursorWithFrames:nil];
2391 [controller hideCursor];
2397 /***********************************************************************
2398 * macdrv_get_cursor_position
2400 * Obtains the current cursor position. Returns zero on failure,
2401 * non-zero on success.
2403 int macdrv_get_cursor_position(CGPoint *pos)
2406 NSPoint location = [NSEvent mouseLocation];
2407 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2408 *pos = NSPointToCGPoint(location);
2414 /***********************************************************************
2415 * macdrv_set_cursor_position
2417 * Sets the cursor position without generating events. Returns zero on
2418 * failure, non-zero on success.
2420 int macdrv_set_cursor_position(CGPoint pos)
2425 ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2431 /***********************************************************************
2432 * macdrv_clip_cursor
2434 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2435 * to or larger than the whole desktop region, the cursor is unclipped.
2436 * Returns zero on failure, non-zero on success.
2438 int macdrv_clip_cursor(CGRect rect)
2443 WineApplicationController* controller = [WineApplicationController sharedController];
2444 BOOL clipping = FALSE;
2446 if (!CGRectIsInfinite(rect))
2448 NSRect nsrect = NSRectFromCGRect(rect);
2451 /* Convert the rectangle from top-down coords to bottom-up. */
2452 [controller flipRect:&nsrect];
2455 for (screen in [NSScreen screens])
2457 if (!NSContainsRect(nsrect, [screen frame]))
2466 ret = [controller startClippingCursor:rect];
2468 ret = [controller stopClippingCursor];
2474 /***********************************************************************
2475 * macdrv_set_application_icon
2477 * Set the application icon. The images array contains CGImages. If
2478 * there are more than one, then they represent different sizes or
2479 * color depths from the icon resource. If images is NULL or empty,
2480 * restores the default application image.
2482 void macdrv_set_application_icon(CFArrayRef images)
2484 NSArray* imageArray = (NSArray*)images;
2486 OnMainThreadAsync(^{
2487 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2491 /***********************************************************************
2494 void macdrv_quit_reply(int reply)
2497 [NSApp replyToApplicationShouldTerminate:reply];
2501 /***********************************************************************
2502 * macdrv_using_input_method
2504 int macdrv_using_input_method(void)
2509 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2515 /***********************************************************************
2516 * macdrv_set_mouse_capture_window
2518 void macdrv_set_mouse_capture_window(macdrv_window window)
2520 WineWindow* w = (WineWindow*)window;
2522 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2525 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2529 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2530 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2531 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2533 /***********************************************************************
2534 * macdrv_create_input_source_list
2536 CFArrayRef macdrv_create_input_source_list(void)
2538 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2541 CFArrayRef input_list;
2542 CFDictionaryRef filter_dict;
2543 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2544 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2547 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2548 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2549 input_list = TISCreateInputSourceList(filter_dict, false);
2551 for (i = 0; i < CFArrayGetCount(input_list); i++)
2553 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2554 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2555 CFDictionaryRef entry;
2556 const void *input_keys[3] = { macdrv_input_source_input_key,
2557 macdrv_input_source_type_key,
2558 macdrv_input_source_lang_key };
2559 const void *input_values[3];
2561 input_values[0] = input;
2562 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2563 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2565 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2566 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2568 CFArrayAppendValue(ret, entry);
2571 CFRelease(input_list);
2572 CFRelease(filter_dict);
2578 int macdrv_select_input_source(TISInputSourceRef input_source)
2580 __block int ret = FALSE;
2583 ret = (TISSelectInputSource(input_source) == noErr);