/* * MACDRV Cocoa window code * * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #import #import #import "cocoa_window.h" #include "macdrv_cocoa.h" #import "cocoa_app.h" #import "cocoa_event.h" #import "cocoa_opengl.h" #if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 enum { NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7, NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8, NSWindowFullScreenButton = 7, NSFullScreenWindowMask = 1 << 14, }; @interface NSWindow (WineFullScreenExtensions) - (void) toggleFullScreen:(id)sender; @end #endif /* Additional Mac virtual keycode, to complement those in Carbon's . */ enum { kVK_RightCommand = 0x36, /* Invented for Wine; was unused */ }; static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf) { NSUInteger style_mask; if (wf->title_bar) { style_mask = NSTitledWindowMask; if (wf->close_button) style_mask |= NSClosableWindowMask; if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask; if (wf->resizable || wf->maximize_button) style_mask |= NSResizableWindowMask; if (wf->utility) style_mask |= NSUtilityWindowMask; } else style_mask = NSBorderlessWindowMask; return style_mask; } static BOOL frame_intersects_screens(NSRect frame, NSArray* screens) { NSScreen* screen; for (screen in screens) { if (NSIntersectsRect(frame, [screen frame])) return TRUE; } return FALSE; } static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens) { for (NSScreen* screen in screens) { if (NSContainsRect(rect, [screen frame])) return screen; } return nil; } /* We rely on the supposedly device-dependent modifier flags to distinguish the keys on the left side of the keyboard from those on the right. Some event sources don't set those device-depdendent flags. If we see a device-independent flag for a modifier without either corresponding device-dependent flag, assume the left one. */ static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers) { if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK) *modifiers |= NX_DEVICELCMDKEYMASK; if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK) *modifiers |= NX_DEVICELSHIFTKEYMASK; if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK) *modifiers |= NX_DEVICELCTLKEYMASK; if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK) *modifiers |= NX_DEVICELALTKEYMASK; } /* As we manipulate individual bits of a modifier mask, we can end up with inconsistent sets of flags. In particular, we might set or clear one of the left/right-specific bits, but not the corresponding non-side-specific bit. Fix that. If either side-specific bit is set, set the non-side-specific bit, otherwise clear it. */ static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers) { if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) *modifiers |= NX_COMMANDMASK; else *modifiers &= ~NX_COMMANDMASK; if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) *modifiers |= NX_SHIFTMASK; else *modifiers &= ~NX_SHIFTMASK; if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) *modifiers |= NX_CONTROLMASK; else *modifiers &= ~NX_CONTROLMASK; if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) *modifiers |= NX_ALTERNATEMASK; else *modifiers &= ~NX_ALTERNATEMASK; } static inline NSUInteger adjusted_modifiers_for_option_behavior(NSUInteger modifiers) { fix_device_modifiers_by_generic(&modifiers); if (left_option_is_alt && (modifiers & NX_DEVICELALTKEYMASK)) { modifiers |= NX_DEVICELCMDKEYMASK; modifiers &= ~NX_DEVICELALTKEYMASK; } if (right_option_is_alt && (modifiers & NX_DEVICERALTKEYMASK)) { modifiers |= NX_DEVICERCMDKEYMASK; modifiers &= ~NX_DEVICERALTKEYMASK; } fix_generic_modifiers_by_device(&modifiers); return modifiers; } @interface NSWindow (WineAccessPrivateMethods) - (id) _displayChanged; @end @interface WineDisplayLink : NSObject { CGDirectDisplayID _displayID; CVDisplayLinkRef _link; NSMutableSet* _windows; NSTimeInterval _actualRefreshPeriod; NSTimeInterval _nominalRefreshPeriod; } - (id) initWithDisplayID:(CGDirectDisplayID)displayID; - (void) addWindow:(WineWindow*)window; - (void) removeWindow:(WineWindow*)window; - (NSTimeInterval) refreshPeriod; - (void) start; @end @implementation WineDisplayLink static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext); - (id) initWithDisplayID:(CGDirectDisplayID)displayID { self = [super init]; if (self) { CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &_link); if (status == kCVReturnSuccess && !_link) status = kCVReturnError; if (status == kCVReturnSuccess) status = CVDisplayLinkSetOutputCallback(_link, WineDisplayLinkCallback, self); if (status != kCVReturnSuccess) { [self release]; return nil; } _displayID = displayID; _windows = [[NSMutableSet alloc] init]; } return self; } - (void) dealloc { if (_link) { CVDisplayLinkStop(_link); CVDisplayLinkRelease(_link); } [_windows release]; [super dealloc]; } - (void) addWindow:(WineWindow*)window { @synchronized(self) { BOOL needsStart = !_windows.count; [_windows addObject:window]; if (needsStart) CVDisplayLinkStart(_link); } } - (void) removeWindow:(WineWindow*)window { @synchronized(self) { BOOL wasRunning = _windows.count > 0; [_windows removeObject:window]; if (wasRunning && !_windows.count) CVDisplayLinkStop(_link); } } - (void) fire { NSSet* windows; @synchronized(self) { windows = [_windows copy]; } dispatch_async(dispatch_get_main_queue(), ^{ BOOL anyDisplayed = FALSE; for (WineWindow* window in windows) { if ([window viewsNeedDisplay]) { [window displayIfNeeded]; anyDisplayed = YES; } } if (!anyDisplayed) CVDisplayLinkStop(_link); }); [windows release]; } - (NSTimeInterval) refreshPeriod { if (_actualRefreshPeriod || (_actualRefreshPeriod = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_link))) return _actualRefreshPeriod; if (_nominalRefreshPeriod) return _nominalRefreshPeriod; CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(_link); if (time.flags & kCVTimeIsIndefinite) return 1.0 / 60.0; _nominalRefreshPeriod = time.timeValue / (double)time.timeScale; return _nominalRefreshPeriod; } - (void) start { CVDisplayLinkStart(_link); } static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { WineDisplayLink* link = displayLinkContext; [link fire]; return kCVReturnSuccess; } @end @interface WineContentView : NSView { NSMutableArray* glContexts; NSMutableArray* pendingGlContexts; BOOL clearedGlSurface; NSMutableAttributedString* markedText; NSRange markedTextSelection; } - (void) addGLContext:(WineOpenGLContext*)context; - (void) removeGLContext:(WineOpenGLContext*)context; - (void) updateGLContexts; @end @interface WineWindow () @property (readwrite, nonatomic) BOOL disabled; @property (readwrite, nonatomic) BOOL noActivate; @property (readwrite, nonatomic) BOOL floating; @property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose; @property (retain, nonatomic) NSWindow* latentParentWindow; @property (nonatomic) void* hwnd; @property (retain, readwrite, nonatomic) WineEventQueue* queue; @property (nonatomic) void* surface; @property (nonatomic) pthread_mutex_t* surface_mutex; @property (copy, nonatomic) NSBezierPath* shape; @property (copy, nonatomic) NSData* shapeData; @property (nonatomic) BOOL shapeChangedSinceLastDraw; @property (readonly, nonatomic) BOOL needsTransparency; @property (nonatomic) BOOL colorKeyed; @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue; @property (nonatomic) BOOL usePerPixelAlpha; @property (assign, nonatomic) void* imeData; @property (nonatomic) BOOL commandDone; @property (readonly, copy, nonatomic) NSArray* childWineWindows; - (void) updateColorSpace; - (void) updateForGLSubviews; - (BOOL) becameEligibleParentOrChild; - (void) becameIneligibleChild; @end @implementation WineContentView - (void) dealloc { [markedText release]; [glContexts release]; [pendingGlContexts release]; [super dealloc]; } - (BOOL) isFlipped { return YES; } - (void) drawRect:(NSRect)rect { WineWindow* window = (WineWindow*)[self window]; for (WineOpenGLContext* context in pendingGlContexts) { if (!clearedGlSurface) { context.shouldClearToBlack = TRUE; clearedGlSurface = TRUE; } context.needsUpdate = TRUE; } [glContexts addObjectsFromArray:pendingGlContexts]; [pendingGlContexts removeAllObjects]; if ([window contentView] != self) return; if (window.shapeChangedSinceLastDraw && window.shape && !window.colorKeyed && !window.usePerPixelAlpha) { [[NSColor clearColor] setFill]; NSRectFill(rect); [window.shape addClip]; [[NSColor windowBackgroundColor] setFill]; NSRectFill(rect); } if (window.surface && window.surface_mutex && !pthread_mutex_lock(window.surface_mutex)) { const CGRect* rects; int count; if (get_surface_blit_rects(window.surface, &rects, &count) && count) { CGContextRef context; int i; [window.shape addClip]; context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextSetInterpolationQuality(context, kCGInterpolationNone); for (i = 0; i < count; i++) { CGRect imageRect; CGImageRef image; imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect)); image = create_surface_image(window.surface, &imageRect, FALSE); if (image) { if (window.colorKeyed) { CGImageRef maskedImage; CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5, window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5, window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 }; maskedImage = CGImageCreateWithMaskingColors(image, components); if (maskedImage) { CGImageRelease(image); image = maskedImage; } } CGContextDrawImage(context, imageRect, image); CGImageRelease(image); } } } pthread_mutex_unlock(window.surface_mutex); } // If the window may be transparent, then we have to invalidate the // shadow every time we draw. Also, if this is the first time we've // drawn since changing from transparent to opaque. if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw) { window.shapeChangedSinceLastDraw = FALSE; [window invalidateShadow]; } } - (void) addGLContext:(WineOpenGLContext*)context { if (!glContexts) glContexts = [[NSMutableArray alloc] init]; if (!pendingGlContexts) pendingGlContexts = [[NSMutableArray alloc] init]; if ([[self window] windowNumber] > 0 && !NSIsEmptyRect([self visibleRect])) { [glContexts addObject:context]; if (!clearedGlSurface) { context.shouldClearToBlack = TRUE; clearedGlSurface = TRUE; } context.needsUpdate = TRUE; } else { [pendingGlContexts addObject:context]; [self setNeedsDisplay:YES]; } [(WineWindow*)[self window] updateForGLSubviews]; } - (void) removeGLContext:(WineOpenGLContext*)context { [glContexts removeObjectIdenticalTo:context]; [pendingGlContexts removeObjectIdenticalTo:context]; [(WineWindow*)[self window] updateForGLSubviews]; } - (void) updateGLContexts { for (WineOpenGLContext* context in glContexts) context.needsUpdate = TRUE; } - (BOOL) hasGLContext { return [glContexts count] || [pendingGlContexts count]; } - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent { return YES; } - (BOOL) preservesContentDuringLiveResize { // Returning YES from this tells Cocoa to keep our view's content during // a Cocoa-driven resize. In theory, we're also supposed to override // -setFrameSize: to mark exposed sections as needing redisplay, but // user32 will take care of that in a roundabout way. This way, we don't // redraw until the window surface is flushed. // // This doesn't do anything when we resize the window ourselves. return YES; } - (BOOL)acceptsFirstResponder { return [[self window] contentView] == self; } - (BOOL) mouseDownCanMoveWindow { return NO; } - (void) completeText:(NSString*)text { macdrv_event* event; WineWindow* window = (WineWindow*)[self window]; event = macdrv_create_event(IM_SET_TEXT, window); event->im_set_text.data = [window imeData]; event->im_set_text.text = (CFStringRef)[text copy]; event->im_set_text.complete = TRUE; [[window queue] postEvent:event]; macdrv_release_event(event); [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])]; markedTextSelection = NSMakeRange(0, 0); [[self inputContext] discardMarkedText]; } - (NSFocusRingType) focusRingType { return NSFocusRingTypeNone; } /* * ---------- NSTextInputClient methods ---------- */ - (NSTextInputContext*) inputContext { if (!markedText) markedText = [[NSMutableAttributedString alloc] init]; return [super inputContext]; } - (void) insertText:(id)string replacementRange:(NSRange)replacementRange { if ([string isKindOfClass:[NSAttributedString class]]) string = [string string]; if ([string isKindOfClass:[NSString class]]) [self completeText:string]; } - (void) doCommandBySelector:(SEL)aSelector { [(WineWindow*)[self window] setCommandDone:TRUE]; } - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { if ([string isKindOfClass:[NSAttributedString class]]) string = [string string]; if ([string isKindOfClass:[NSString class]]) { macdrv_event* event; WineWindow* window = (WineWindow*)[self window]; if (replacementRange.location == NSNotFound) replacementRange = NSMakeRange(0, [markedText length]); [markedText replaceCharactersInRange:replacementRange withString:string]; markedTextSelection = selectedRange; markedTextSelection.location += replacementRange.location; event = macdrv_create_event(IM_SET_TEXT, window); event->im_set_text.data = [window imeData]; event->im_set_text.text = (CFStringRef)[[markedText string] copy]; event->im_set_text.complete = FALSE; event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length; [[window queue] postEvent:event]; macdrv_release_event(event); [[self inputContext] invalidateCharacterCoordinates]; } } - (void) unmarkText { [self completeText:nil]; } - (NSRange) selectedRange { return markedTextSelection; } - (NSRange) markedRange { NSRange range = NSMakeRange(0, [markedText length]); if (!range.length) range.location = NSNotFound; return range; } - (BOOL) hasMarkedText { return [markedText length] > 0; } - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { if (aRange.location >= [markedText length]) return nil; aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length])); if (actualRange) *actualRange = aRange; return [markedText attributedSubstringFromRange:aRange]; } - (NSArray*) validAttributesForMarkedText { return [NSArray array]; } - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { macdrv_query* query; WineWindow* window = (WineWindow*)[self window]; NSRect ret; aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length])); query = macdrv_create_query(); query->type = QUERY_IME_CHAR_RECT; query->window = (macdrv_window)[window retain]; query->ime_char_rect.data = [window imeData]; query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length); if ([window.queue query:query timeout:0.3 flags:WineQueryNoPreemptWait]) { aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length); ret = NSRectFromCGRect(query->ime_char_rect.rect); [[WineApplicationController sharedController] flipRect:&ret]; } else ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12); macdrv_release_query(query); if (actualRange) *actualRange = aRange; return ret; } - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint { return NSNotFound; } - (NSInteger) windowLevel { return [[self window] level]; } @end @implementation WineWindow static WineWindow* causing_becomeKeyWindow; @synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue; @synthesize surface, surface_mutex; @synthesize shape, shapeData, shapeChangedSinceLastDraw; @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue; @synthesize usePerPixelAlpha; @synthesize imeData, commandDone; + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf windowFrame:(NSRect)window_frame hwnd:(void*)hwnd queue:(WineEventQueue*)queue { WineWindow* window; WineContentView* contentView; NSTrackingArea* trackingArea; NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; [[WineApplicationController sharedController] flipRect:&window_frame]; window = [[[self alloc] initWithContentRect:window_frame styleMask:style_mask_for_features(wf) backing:NSBackingStoreBuffered defer:YES] autorelease]; if (!window) return nil; /* Standardize windows to eliminate differences between titled and borderless windows and between NSWindow and NSPanel. */ [window setHidesOnDeactivate:NO]; [window setReleasedWhenClosed:NO]; [window setOneShot:YES]; [window disableCursorRects]; [window setShowsResizeIndicator:NO]; [window setHasShadow:wf->shadow]; [window setAcceptsMouseMovedEvents:YES]; [window setColorSpace:[NSColorSpace genericRGBColorSpace]]; [window setDelegate:window]; [window setAutodisplay:NO]; window.hwnd = hwnd; window.queue = queue; window->savedContentMinSize = NSZeroSize; window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX); window->resizable = wf->resizable; window->_lastDisplayTime = [[NSDate distantPast] timeIntervalSinceReferenceDate]; [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData, (NSString*)kUTTypeContent, nil]]; contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease]; if (!contentView) return nil; [contentView setAutoresizesSubviews:NO]; /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES because they give us mouse moves in the background. */ trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds] options:(NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect) owner:window userInfo:nil] autorelease]; if (!trackingArea) return nil; [contentView addTrackingArea:trackingArea]; [window setContentView:contentView]; [window setInitialFirstResponder:contentView]; [nc addObserver:window selector:@selector(updateFullscreen) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; [window updateFullscreen]; [nc addObserver:window selector:@selector(applicationWillHide) name:NSApplicationWillHideNotification object:NSApp]; [nc addObserver:window selector:@selector(applicationDidUnhide) name:NSApplicationDidUnhideNotification object:NSApp]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:window selector:@selector(checkWineDisplayLink) name:NSWorkspaceActiveSpaceDidChangeNotification object:[NSWorkspace sharedWorkspace]]; return window; } - (void) dealloc { [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [queue release]; [latentChildWindows release]; [latentParentWindow release]; [shape release]; [shapeData release]; [super dealloc]; } - (BOOL) preventResizing { BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor]; return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping); } - (BOOL) allowsMovingWithMaximized:(BOOL)inMaximized { if (allow_immovable_windows && (disabled || inMaximized)) return NO; else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor]) return NO; else return YES; } - (void) adjustFeaturesForState { NSUInteger style = [self styleMask]; if (style & NSClosableWindowMask) [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled]; if (style & NSMiniaturizableWindowMask) [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled]; if (style & NSResizableWindowMask) [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled]; if ([self respondsToSelector:@selector(toggleFullScreen:)]) { if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary) [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled]; } if ([self preventResizing]) { NSSize size = [self contentRectForFrameRect:[self frame]].size; [self setContentMinSize:size]; [self setContentMaxSize:size]; } else { [self setContentMaxSize:savedContentMaxSize]; [self setContentMinSize:savedContentMinSize]; } if (allow_immovable_windows || cursor_clipping_locks_windows) [self setMovable:[self allowsMovingWithMaximized:maximized]]; } - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior { if ([self respondsToSelector:@selector(toggleFullScreen:)]) { NSUInteger style = [self styleMask]; if (behavior & NSWindowCollectionBehaviorParticipatesInCycle && style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized) { behavior |= NSWindowCollectionBehaviorFullScreenPrimary; behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; } else { behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; if (style & NSFullScreenWindowMask) [super toggleFullScreen:nil]; } } if (behavior != [self collectionBehavior]) { [self setCollectionBehavior:behavior]; [self adjustFeaturesForState]; } } - (void) setWindowFeatures:(const struct macdrv_window_features*)wf { static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask; NSUInteger currentStyle = [self styleMask]; NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles); if (newStyle != currentStyle) { NSString* title = [[[self title] copy] autorelease]; BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0; BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0; if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask)) { // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask. // If transitioning from NSTitledWindowMask | NSResizableWindowMask to // just NSTitledWindowMask, the window buttons should disappear rather // than just being disabled. But they don't. Similarly in reverse. // The workaround is to also toggle NSClosableWindowMask at the same time. [self setStyleMask:newStyle ^ NSClosableWindowMask]; } [self setStyleMask:newStyle]; // -setStyleMask: resets the firstResponder to the window. Set it // back to the content view. if ([[self contentView] acceptsFirstResponder]) [self makeFirstResponder:[self contentView]]; [self adjustFullScreenBehavior:[self collectionBehavior]]; if ([[self title] length] == 0 && [title length] > 0) [self setTitle:title]; } resizable = wf->resizable; [self adjustFeaturesForState]; [self setHasShadow:wf->shadow]; } // Indicates if the window would be visible if the app were not hidden. - (BOOL) wouldBeVisible { return [NSApp isHidden] ? savedVisibleState : [self isVisible]; } - (BOOL) isOrderedIn { return [self wouldBeVisible] || [self isMiniaturized]; } - (NSInteger) minimumLevelForActive:(BOOL)active { NSInteger level; if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL || (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen))) level = NSFloatingWindowLevel; else level = NSNormalWindowLevel; if (active) { BOOL captured; captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured]; if (captured || fullscreen) { if (captured) level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */ else level = NSStatusWindowLevel + 1; if (self.floating) level++; } } return level; } - (void) postDidUnminimizeEvent { macdrv_event* event; /* Coalesce events by discarding any previous ones still in the queue. */ [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE) forWindow:self]; event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self); [queue postEvent:event]; macdrv_release_event(event); } - (void) sendResizeStartQuery { macdrv_query* query = macdrv_create_query(); query->type = QUERY_RESIZE_START; query->window = (macdrv_window)[self retain]; [self.queue query:query timeout:0.3]; macdrv_release_query(query); } - (void) setMacDrvState:(const struct macdrv_window_state*)state { NSWindowCollectionBehavior behavior; self.disabled = state->disabled; self.noActivate = state->no_activate; if (self.floating != state->floating) { self.floating = state->floating; if (state->floating) { // Became floating. If child of non-floating window, make that // relationship latent. WineWindow* parent = (WineWindow*)[self parentWindow]; if (parent && !parent.floating) [self becameIneligibleChild]; } else { // Became non-floating. If parent of floating children, make that // relationship latent. WineWindow* child; for (child in [self childWineWindows]) { if (child.floating) [child becameIneligibleChild]; } } // Check our latent relationships. If floating status was the only // reason they were latent, then make them active. if ([self isVisible]) [self becameEligibleParentOrChild]; [[WineApplicationController sharedController] adjustWindowLevels]; } if (state->minimized_valid) { macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE); pendingMinimize = FALSE; if (state->minimized && ![self isMiniaturized]) { if ([self wouldBeVisible]) { if ([self styleMask] & NSFullScreenWindowMask) { [self postDidUnminimizeEvent]; discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE); } else { [super miniaturize:nil]; discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) | event_mask_for_type(WINDOW_GOT_FOCUS) | event_mask_for_type(WINDOW_LOST_FOCUS); } } else pendingMinimize = TRUE; } else if (!state->minimized && [self isMiniaturized]) { ignore_windowDeminiaturize = TRUE; [self deminiaturize:nil]; discard |= event_mask_for_type(WINDOW_LOST_FOCUS); } if (discard) [queue discardEventsMatchingMask:discard forWindow:self]; } if (state->maximized != maximized) { maximized = state->maximized; [self adjustFeaturesForState]; if (!maximized && [self inLiveResize]) [self sendResizeStartQuery]; } behavior = NSWindowCollectionBehaviorDefault; if (state->excluded_by_expose) behavior |= NSWindowCollectionBehaviorTransient; else behavior |= NSWindowCollectionBehaviorManaged; if (state->excluded_by_cycle) { behavior |= NSWindowCollectionBehaviorIgnoresCycle; if ([self isOrderedIn]) [NSApp removeWindowsItem:self]; } else { behavior |= NSWindowCollectionBehaviorParticipatesInCycle; if ([self isOrderedIn]) [NSApp addWindowsItem:self title:[self title] filename:NO]; } [self adjustFullScreenBehavior:behavior]; } - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible { BOOL reordered = FALSE; if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating)) { if ([self level] > [child level]) [child setLevel:[self level]]; [self addChildWindow:child ordered:NSWindowAbove]; [child checkWineDisplayLink]; [latentChildWindows removeObjectIdenticalTo:child]; child.latentParentWindow = nil; reordered = TRUE; } else { if (!latentChildWindows) latentChildWindows = [[NSMutableArray alloc] init]; if (![latentChildWindows containsObject:child]) [latentChildWindows addObject:child]; child.latentParentWindow = self; } return reordered; } - (BOOL) addChildWineWindow:(WineWindow*)child { return [self addChildWineWindow:child assumeVisible:FALSE]; } - (void) removeChildWineWindow:(WineWindow*)child { [self removeChildWindow:child]; if (child.latentParentWindow == self) child.latentParentWindow = nil; [latentChildWindows removeObjectIdenticalTo:child]; } - (BOOL) becameEligibleParentOrChild { BOOL reordered = FALSE; NSUInteger count; if (latentParentWindow.floating || !self.floating) { // If we aren't visible currently, we assume that we should be and soon // will be. So, if the latent parent is visible that's enough to assume // we can establish the parent-child relationship in Cocoa. That will // actually make us visible, which is fine. if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE]) reordered = TRUE; } // Here, though, we may not actually be visible yet and adding a child // won't make us visible. The caller will have to call this method // again after actually making us visible. if ([self isVisible] && (count = [latentChildWindows count])) { NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet]; NSUInteger i; for (i = 0; i < count; i++) { WineWindow* child = [latentChildWindows objectAtIndex:i]; if ([child isVisible] && (self.floating || !child.floating)) { if (child.latentParentWindow == self) { if ([self level] > [child level]) [child setLevel:[self level]]; [self addChildWindow:child ordered:NSWindowAbove]; child.latentParentWindow = nil; reordered = TRUE; } else ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child); [indexesToRemove addIndex:i]; } } [latentChildWindows removeObjectsAtIndexes:indexesToRemove]; } return reordered; } - (void) becameIneligibleChild { WineWindow* parent = (WineWindow*)[self parentWindow]; if (parent) { if (!parent->latentChildWindows) parent->latentChildWindows = [[NSMutableArray alloc] init]; [parent->latentChildWindows insertObject:self atIndex:0]; self.latentParentWindow = parent; [parent removeChildWindow:self]; } } - (void) becameIneligibleParentOrChild { NSArray* childWindows = [self childWineWindows]; [self becameIneligibleChild]; if ([childWindows count]) { WineWindow* child; for (child in childWindows) { child.latentParentWindow = self; [self removeChildWindow:child]; } if (latentChildWindows) [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows]; else latentChildWindows = [childWindows mutableCopy]; } } // Determine if, among Wine windows, this window is directly above or below // a given other Wine window with no other Wine window intervening. // Intervening non-Wine windows are ignored. - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow { NSNumber* windowNumber; NSNumber* otherWindowNumber; NSArray* windowNumbers; NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i; if (![self isVisible] || ![otherWindow isVisible]) return FALSE; windowNumber = [NSNumber numberWithInteger:[self windowNumber]]; otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]]; windowNumbers = [[self class] windowNumbersWithOptions:0]; windowIndex = [windowNumbers indexOfObject:windowNumber]; otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber]; if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound) return FALSE; if (orderingMode == NSWindowAbove) { lowIndex = windowIndex; highIndex = otherWindowIndex; } else if (orderingMode == NSWindowBelow) { lowIndex = otherWindowIndex; highIndex = windowIndex; } else return FALSE; if (highIndex <= lowIndex) return FALSE; for (i = lowIndex + 1; i < highIndex; i++) { NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue]; NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber]; if ([interveningWindow isKindOfClass:[WineWindow class]]) return FALSE; } return TRUE; } - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other { NSMutableArray* windowNumbers; NSNumber* childWindowNumber; NSUInteger otherIndex, limit; NSArray* origChildren; NSMutableArray* children; // Get the z-order from the window server and modify it to reflect the // requested window ordering. windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease]; childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]]; [windowNumbers removeObject:childWindowNumber]; otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]]; [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)]; // Get our child windows and sort them in the reverse of the desired // z-order (back-to-front). origChildren = [self childWineWindows]; children = [[origChildren mutableCopy] autorelease]; [children sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(id obj1, id obj2){ NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]]; NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]]; NSUInteger index1 = [windowNumbers indexOfObject:window1Number]; NSUInteger index2 = [windowNumbers indexOfObject:window2Number]; if (index1 == NSNotFound) { if (index2 == NSNotFound) return NSOrderedSame; else return NSOrderedAscending; } else if (index2 == NSNotFound) return NSOrderedDescending; else if (index1 < index2) return NSOrderedDescending; else if (index2 < index1) return NSOrderedAscending; return NSOrderedSame; }]; // If the current and desired children arrays match up to a point, leave // those matching children alone. limit = MIN([origChildren count], [children count]); for (otherIndex = 0; otherIndex < limit; otherIndex++) { if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex]) break; } [children removeObjectsInRange:NSMakeRange(0, otherIndex)]; // Remove all of the child windows and re-add them back-to-front so they // are in the desired order. for (other in children) [self removeChildWindow:other]; for (other in children) [self addChildWindow:other ordered:NSWindowAbove]; } /* Returns whether or not the window was ordered in, which depends on if its frame intersects any screen. */ - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate { WineApplicationController* controller = [WineApplicationController sharedController]; if (![self isMiniaturized]) { BOOL needAdjustWindowLevels = FALSE; BOOL wasVisible; [controller transformProcessToForeground]; [NSApp unhide:nil]; wasVisible = [self isVisible]; if (activate) [NSApp activateIgnoringOtherApps:YES]; NSDisableScreenUpdates(); if ([self becameEligibleParentOrChild]) needAdjustWindowLevels = TRUE; if (prev || next) { WineWindow* other = [prev isVisible] ? prev : next; NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove; if (![self isOrdered:orderingMode relativeTo:other]) { WineWindow* parent = (WineWindow*)[self parentWindow]; WineWindow* otherParent = (WineWindow*)[other parentWindow]; // This window level may not be right for this window based // on floating-ness, fullscreen-ness, etc. But we set it // temporarily to allow us to order the windows properly. // Then the levels get fixed by -adjustWindowLevels. if ([self level] != [other level]) [self setLevel:[other level]]; [self orderWindow:orderingMode relativeTo:[other windowNumber]]; [self checkWineDisplayLink]; // The above call to -[NSWindow orderWindow:relativeTo:] won't // reorder windows which are both children of the same parent // relative to each other, so do that separately. if (parent && parent == otherParent) [parent order:orderingMode childWindow:self relativeTo:other]; needAdjustWindowLevels = TRUE; } } else { // Again, temporarily set level to make sure we can order to // the right place. next = [controller frontWineWindow]; if (next && [self level] < [next level]) [self setLevel:[next level]]; [self orderFront:nil]; [self checkWineDisplayLink]; needAdjustWindowLevels = TRUE; } if ([self becameEligibleParentOrChild]) needAdjustWindowLevels = TRUE; if (needAdjustWindowLevels) { if (!wasVisible && fullscreen && [self isOnActiveSpace]) [controller updateFullscreenWindows]; [controller adjustWindowLevels]; } if (pendingMinimize) { [super miniaturize:nil]; pendingMinimize = FALSE; } NSEnableScreenUpdates(); /* Cocoa may adjust the frame when the window is ordered onto the screen. Generate a frame-changed event just in case. The back end will ignore it if nothing actually changed. */ [self windowDidResize:nil]; if (![self isExcludedFromWindowsMenu]) [NSApp addWindowsItem:self title:[self title] filename:NO]; } } - (void) doOrderOut { WineApplicationController* controller = [WineApplicationController sharedController]; BOOL wasVisible = [self isVisible]; BOOL wasOnActiveSpace = [self isOnActiveSpace]; if ([self isMiniaturized]) pendingMinimize = TRUE; WineWindow* parent = (WineWindow*)self.parentWindow; if ([parent isKindOfClass:[WineWindow class]]) [parent grabDockIconSnapshotFromWindow:self force:NO]; [self becameIneligibleParentOrChild]; if ([self isMiniaturized]) { fakingClose = TRUE; [self close]; fakingClose = FALSE; } else [self orderOut:nil]; [self checkWineDisplayLink]; savedVisibleState = FALSE; if (wasVisible && wasOnActiveSpace && fullscreen) [controller updateFullscreenWindows]; [controller adjustWindowLevels]; [NSApp removeWindowsItem:self]; [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) | event_mask_for_type(WINDOW_GOT_FOCUS) | event_mask_for_type(WINDOW_LOST_FOCUS) | event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) | event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) | event_mask_for_type(WINDOW_RESTORE_REQUESTED) forWindow:self]; } - (void) updateFullscreen { NSRect contentRect = [self contentRectForFrameRect:[self frame]]; BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]); if (nowFullscreen != fullscreen) { WineApplicationController* controller = [WineApplicationController sharedController]; fullscreen = nowFullscreen; if ([self isVisible] && [self isOnActiveSpace]) [controller updateFullscreenWindows]; [controller adjustWindowLevels]; } } - (void) setFrameFromWine:(NSRect)contentRect { /* Origin is (left, top) in a top-down space. Need to convert it to (left, bottom) in a bottom-up space. */ [[WineApplicationController sharedController] flipRect:&contentRect]; /* The back end is establishing a new window size and position. It's not interested in any stale events regarding those that may be sitting in the queue. */ [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED) forWindow:self]; if (!NSIsEmptyRect(contentRect)) { NSRect frame, oldFrame; oldFrame = [self frame]; frame = [self frameRectForContentRect:contentRect]; if (!NSEqualRects(frame, oldFrame)) { BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size); BOOL needEnableScreenUpdates = FALSE; if ([self preventResizing]) { // Allow the following calls to -setFrame:display: to work even // if they would violate the content size constraints. This // shouldn't be necessary since the content size constraints are // documented to not constrain that method, but it seems to be. [self setContentMinSize:NSZeroSize]; [self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; } if (equalSizes && [[self childWineWindows] count]) { // If we change the window frame such that the origin moves // but the size doesn't change, then Cocoa moves child // windows with the parent. We don't want that so we fake // a change of the size and then change it back. NSRect bogusFrame = frame; bogusFrame.size.width++; NSDisableScreenUpdates(); needEnableScreenUpdates = TRUE; ignore_windowResize = TRUE; [self setFrame:bogusFrame display:NO]; ignore_windowResize = FALSE; } [self setFrame:frame display:YES]; if ([self preventResizing]) { [self setContentMinSize:contentRect.size]; [self setContentMaxSize:contentRect.size]; } if (needEnableScreenUpdates) NSEnableScreenUpdates(); if (!equalSizes) [self updateColorSpace]; if (!enteringFullScreen && [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0) nonFullscreenFrame = frame; [self updateFullscreen]; if ([self isOrderedIn]) { /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed event. The back end will ignore it if nothing actually changed. */ [self windowDidResize:nil]; } } } } - (void) setMacDrvParentWindow:(WineWindow*)parent { WineWindow* oldParent = (WineWindow*)[self parentWindow]; if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent)) { [oldParent removeChildWineWindow:self]; [latentParentWindow removeChildWineWindow:self]; if ([parent addChildWineWindow:self]) [[WineApplicationController sharedController] adjustWindowLevels]; } } - (void) setDisabled:(BOOL)newValue { if (disabled != newValue) { disabled = newValue; [self adjustFeaturesForState]; } } - (BOOL) needsTransparency { return self.shape || self.colorKeyed || self.usePerPixelAlpha || (gl_surface_mode == GL_SURFACE_BEHIND && [[self.contentView valueForKeyPath:@"subviews.@max.hasGLContext"] boolValue]); } - (void) checkTransparency { if (![self isOpaque] && !self.needsTransparency) { self.shapeChangedSinceLastDraw = TRUE; [[self contentView] setNeedsDisplay:YES]; [self setBackgroundColor:[NSColor windowBackgroundColor]]; [self setOpaque:YES]; } else if ([self isOpaque] && self.needsTransparency) { self.shapeChangedSinceLastDraw = TRUE; [[self contentView] setNeedsDisplay:YES]; [self setBackgroundColor:[NSColor clearColor]]; [self setOpaque:NO]; } } - (void) setShape:(NSBezierPath*)newShape { if (shape == newShape) return; if (shape) { [[self contentView] setNeedsDisplayInRect:[shape bounds]]; [shape release]; } if (newShape) [[self contentView] setNeedsDisplayInRect:[newShape bounds]]; shape = [newShape copy]; self.shapeChangedSinceLastDraw = TRUE; [self checkTransparency]; } - (void) makeFocused:(BOOL)activate { if (activate) { [[WineApplicationController sharedController] transformProcessToForeground]; [NSApp activateIgnoringOtherApps:YES]; } causing_becomeKeyWindow = self; [self makeKeyWindow]; causing_becomeKeyWindow = nil; [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) | event_mask_for_type(WINDOW_LOST_FOCUS) forWindow:self]; } - (void) postKey:(uint16_t)keyCode pressed:(BOOL)pressed modifiers:(NSUInteger)modifiers event:(NSEvent*)theEvent { macdrv_event* event; CGEventRef cgevent; WineApplicationController* controller = [WineApplicationController sharedController]; event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self); event->key.keycode = keyCode; event->key.modifiers = modifiers; event->key.time_ms = [controller ticksForEventTime:[theEvent timestamp]]; if ((cgevent = [theEvent CGEvent])) { CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent, kCGKeyboardEventKeyboardType); if (keyboardType != controller.keyboardType) { controller.keyboardType = keyboardType; [controller keyboardSelectionDidChange]; } } [queue postEvent:event]; macdrv_release_event(event); [controller noteKey:keyCode pressed:pressed]; } - (void) postKeyEvent:(NSEvent *)theEvent { [self flagsChanged:theEvent]; [self postKey:[theEvent keyCode] pressed:[theEvent type] == NSKeyDown modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags]) event:theEvent]; } - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize { savedContentMinSize = minSize; savedContentMaxSize = maxSize; if (![self preventResizing]) { [self setContentMinSize:minSize]; [self setContentMaxSize:maxSize]; } } - (WineWindow*) ancestorWineWindow { WineWindow* ancestor = self; for (;;) { WineWindow* parent = (WineWindow*)[ancestor parentWindow]; if ([parent isKindOfClass:[WineWindow class]]) ancestor = parent; else break; } return ancestor; } - (void) postBroughtForwardEvent { macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self); [queue postEvent:event]; macdrv_release_event(event); } - (void) updateForCursorClipping { [self adjustFeaturesForState]; } - (void) endWindowDragging { if (draggingPhase) { if (draggingPhase == 3) { macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, self); [queue postEvent:event]; macdrv_release_event(event); } draggingPhase = 0; [[WineApplicationController sharedController] window:self isBeingDragged:NO]; } } - (NSMutableDictionary*) displayIDToDisplayLinkMap { static NSMutableDictionary* displayIDToDisplayLinkMap; if (!displayIDToDisplayLinkMap) { displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init]; [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification object:NSApp queue:nil usingBlock:^(NSNotification *note){ NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys]; NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]]; [badDisplayIDs minusSet:validDisplayIDs]; [displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]]; }]; } return displayIDToDisplayLinkMap; } - (WineDisplayLink*) wineDisplayLink { if (!_lastDisplayID) return nil; NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap]; return [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]]; } - (void) checkWineDisplayLink { NSScreen* screen = self.screen; if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped]) screen = nil; #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9 if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible)) screen = nil; #endif NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"]; CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue]; if (displayID == _lastDisplayID) return; NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap]; if (_lastDisplayID) { WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]]; [link removeWindow:self]; } if (displayID) { WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber]; if (!link) { link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease]; [displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber]; } [link addWindow:self]; [self displayIfNeeded]; } _lastDisplayID = displayID; } - (BOOL) isEmptyShaped { return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero))); } - (BOOL) canProvideSnapshot { return (self.windowNumber > 0 && ![self isEmptyShaped]); } - (void) grabDockIconSnapshotFromWindow:(WineWindow*)window force:(BOOL)force { if (![self isEmptyShaped]) return; NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; if (!force && now < lastDockIconSnapshot + 1) return; if (window) { if (![window canProvideSnapshot]) return; } else { CGFloat bestArea; for (WineWindow* childWindow in self.childWindows) { if (![childWindow isKindOfClass:[WineWindow class]] || ![childWindow canProvideSnapshot]) continue; NSSize size = childWindow.frame.size; CGFloat area = size.width * size.height; if (!window || area > bestArea) { window = childWindow; bestArea = area; } } if (!window) return; } const void* windowID = (const void*)(CGWindowID)window.windowNumber; CFArrayRef windowIDs = CFArrayCreate(NULL, &windowID, 1, NULL); CGImageRef windowImage = CGWindowListCreateImageFromArray(CGRectNull, windowIDs, kCGWindowImageBoundsIgnoreFraming); CFRelease(windowIDs); if (!windowImage) return; NSImage* appImage = [NSApp applicationIconImage]; if (!appImage) appImage = [NSImage imageNamed:NSImageNameApplicationIcon]; NSImage* dockIcon = [[[NSImage alloc] initWithSize:NSMakeSize(256, 256)] autorelease]; [dockIcon lockFocus]; CGContextRef cgcontext = [[NSGraphicsContext currentContext] graphicsPort]; CGRect rect = CGRectMake(8, 8, 240, 240); size_t width = CGImageGetWidth(windowImage); size_t height = CGImageGetHeight(windowImage); if (width > height) { rect.size.height *= height / (double)width; rect.origin.y += (CGRectGetWidth(rect) - CGRectGetHeight(rect)) / 2; } else if (width != height) { rect.size.width *= width / (double)height; rect.origin.x += (CGRectGetHeight(rect) - CGRectGetWidth(rect)) / 2; } CGContextDrawImage(cgcontext, rect, windowImage); [appImage drawInRect:NSMakeRect(156, 4, 96, 96) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1 respectFlipped:YES hints:nil]; [dockIcon unlockFocus]; CGImageRelease(windowImage); NSImageView* imageView = (NSImageView*)self.dockTile.contentView; if (![imageView isKindOfClass:[NSImageView class]]) { imageView = [[[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)] autorelease]; imageView.imageScaling = NSImageScaleProportionallyUpOrDown; self.dockTile.contentView = imageView; } imageView.image = dockIcon; [self.dockTile display]; lastDockIconSnapshot = now; } - (void) checkEmptyShaped { if (self.dockTile.contentView && ![self isEmptyShaped]) { self.dockTile.contentView = nil; lastDockIconSnapshot = 0; } [self checkWineDisplayLink]; } /* * ---------- NSWindow method overrides ---------- */ - (BOOL) canBecomeKeyWindow { if (causing_becomeKeyWindow == self) return YES; if (self.disabled || self.noActivate) return NO; return [self isKeyWindow]; } - (BOOL) canBecomeMainWindow { return [self canBecomeKeyWindow]; } - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen { // If a window is sized to completely cover a screen, then it's in // full-screen mode. In that case, we don't allow NSWindow to constrain // it. NSArray* screens = [NSScreen screens]; NSRect contentRect = [self contentRectForFrameRect:frameRect]; if (!screen_covered_by_rect(contentRect, screens) && frame_intersects_screens(frameRect, screens)) frameRect = [super constrainFrameRect:frameRect toScreen:screen]; return frameRect; } // This private method of NSWindow is called as Cocoa reacts to the display // configuration changing. Among other things, it adjusts the window's // frame based on how the screen(s) changed size. That tells Wine that the // window has been moved. We don't want that. Rather, we want to make // sure that the WinAPI notion of the window position is maintained/ // restored, possibly undoing or overriding Cocoa's adjustment. // // So, we queue a REASSERT_WINDOW_POSITION event to the back end before // Cocoa has a chance to adjust the frame, thus preceding any resulting // WINDOW_FRAME_CHANGED event that may get queued. The back end will // reassert its notion of the position. That call won't get processed // until after this method returns, so it will override whatever this // method does to the window position. It will also discard any pending // WINDOW_FRAME_CHANGED events. // // Unfortunately, the only way I've found to know when Cocoa is _about to_ // adjust the window's position due to a display change is to hook into // this private method. This private method has remained stable from 10.6 // through 10.11. If it does change, the most likely thing is that it // will be removed and no longer called and this fix will simply stop // working. The only real danger would be if Apple changed the return type // to a struct or floating-point type, which would change the calling // convention. - (id) _displayChanged { macdrv_event* event = macdrv_create_event(REASSERT_WINDOW_POSITION, self); [queue postEvent:event]; macdrv_release_event(event); return [super _displayChanged]; } - (BOOL) isExcludedFromWindowsMenu { return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle); } - (BOOL) validateMenuItem:(NSMenuItem *)menuItem { BOOL ret = [super validateMenuItem:menuItem]; if ([menuItem action] == @selector(makeKeyAndOrderFront:)) ret = [self isKeyWindow] || (!self.disabled && !self.noActivate); if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized)) ret = NO; return ret; } /* We don't call this. It's the action method of the items in the Window menu. */ - (void) makeKeyAndOrderFront:(id)sender { if ([self isMiniaturized]) [self deminiaturize:nil]; [self orderBelow:nil orAbove:nil activate:NO]; [[self ancestorWineWindow] postBroughtForwardEvent]; if (![self isKeyWindow] && !self.disabled && !self.noActivate) [[WineApplicationController sharedController] windowGotFocus:self]; } - (void) sendEvent:(NSEvent*)event { NSEventType type = event.type; /* NSWindow consumes certain key-down events as part of Cocoa's keyboard interface control. For example, Control-Tab switches focus among views. We want to bypass that feature, so directly route key-down events to -keyDown:. */ if (type == NSKeyDown) [[self firstResponder] keyDown:event]; else { if (!draggingPhase && maximized && ![self isMovable] && ![self allowsMovingWithMaximized:YES] && [self allowsMovingWithMaximized:NO] && type == NSLeftMouseDown && (self.styleMask & NSTitledWindowMask)) { NSRect titleBar = self.frame; NSRect contentRect = [self contentRectForFrameRect:titleBar]; titleBar.size.height = NSMaxY(titleBar) - NSMaxY(contentRect); titleBar.origin.y = NSMaxY(contentRect); dragStartPosition = [self convertBaseToScreen:event.locationInWindow]; if (NSMouseInRect(dragStartPosition, titleBar, NO)) { static const NSWindowButton buttons[] = { NSWindowCloseButton, NSWindowMiniaturizeButton, NSWindowZoomButton, NSWindowFullScreenButton, }; BOOL hitButton = NO; int i; for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++) { NSButton* button; if (buttons[i] == NSWindowFullScreenButton && ![self respondsToSelector:@selector(toggleFullScreen:)]) continue; button = [self standardWindowButton:buttons[i]]; if ([button hitTest:[button.superview convertPoint:event.locationInWindow fromView:nil]]) { hitButton = YES; break; } } if (!hitButton) { draggingPhase = 1; dragWindowStartPosition = NSMakePoint(NSMinX(titleBar), NSMaxY(titleBar)); [[WineApplicationController sharedController] window:self isBeingDragged:YES]; } } } else if (draggingPhase && (type == NSLeftMouseDragged || type == NSLeftMouseUp)) { if ([self isMovable]) { NSPoint point = [self convertBaseToScreen:event.locationInWindow]; NSPoint newTopLeft = dragWindowStartPosition; newTopLeft.x += point.x - dragStartPosition.x; newTopLeft.y += point.y - dragStartPosition.y; if (draggingPhase == 2) { macdrv_event* event = macdrv_create_event(WINDOW_DRAG_BEGIN, self); [queue postEvent:event]; macdrv_release_event(event); draggingPhase = 3; } [self setFrameTopLeftPoint:newTopLeft]; } else if (draggingPhase == 1 && type == NSLeftMouseDragged) { macdrv_event* event; NSRect frame = [self contentRectForFrameRect:self.frame]; [[WineApplicationController sharedController] flipRect:&frame]; event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self); event->window_restore_requested.keep_frame = TRUE; event->window_restore_requested.frame = NSRectToCGRect(frame); [queue postEvent:event]; macdrv_release_event(event); draggingPhase = 2; } if (type == NSLeftMouseUp) [self endWindowDragging]; } [super sendEvent:event]; } } - (void) miniaturize:(id)sender { macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self); [queue postEvent:event]; macdrv_release_event(event); WineWindow* parent = (WineWindow*)self.parentWindow; if ([parent isKindOfClass:[WineWindow class]]) [parent grabDockIconSnapshotFromWindow:self force:YES]; } - (void) toggleFullScreen:(id)sender { if (!self.disabled && !maximized) [super toggleFullScreen:sender]; } - (void) setViewsNeedDisplay:(BOOL)value { if (value && ![self viewsNeedDisplay]) { WineDisplayLink* link = [self wineDisplayLink]; if (link) { NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; if (_lastDisplayTime + [link refreshPeriod] < now) [self setAutodisplay:YES]; else { [link start]; _lastDisplayTime = now; } } } [super setViewsNeedDisplay:value]; } - (void) display { _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime]; [super display]; [self setAutodisplay:NO]; } - (void) displayIfNeeded { _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime]; [super displayIfNeeded]; [self setAutodisplay:NO]; } - (NSArray*) childWineWindows { NSArray* childWindows = self.childWindows; NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){ return [child isKindOfClass:[WineWindow class]]; }]; return [childWindows objectsAtIndexes:indexes]; } // We normally use the generic/calibrated RGB color space for the window, // rather than the device color space, to avoid expensive color conversion // which slows down drawing. However, for windows displaying OpenGL, having // a different color space than the screen greatly reduces frame rates, often // limiting it to the display refresh rate. // // To avoid this, we switch back to the screen color space whenever the // window is covered by a view with an attached OpenGL context. - (void) updateColorSpace { NSRect contentRect = [[self contentView] frame]; BOOL coveredByGLView = FALSE; for (WineContentView* view in [[self contentView] subviews]) { if ([view hasGLContext]) { NSRect frame = [view convertRect:[view bounds] toView:nil]; if (NSContainsRect(frame, contentRect)) { coveredByGLView = TRUE; break; } } } if (coveredByGLView) [self setColorSpace:nil]; else [self setColorSpace:[NSColorSpace genericRGBColorSpace]]; } - (void) updateForGLSubviews { [self updateColorSpace]; if (gl_surface_mode == GL_SURFACE_BEHIND) [self checkTransparency]; } /* * ---------- NSResponder method overrides ---------- */ - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; } - (void) flagsChanged:(NSEvent *)theEvent { static const struct { NSUInteger mask; uint16_t keycode; } modifiers[] = { { NX_ALPHASHIFTMASK, kVK_CapsLock }, { NX_DEVICELSHIFTKEYMASK, kVK_Shift }, { NX_DEVICERSHIFTKEYMASK, kVK_RightShift }, { NX_DEVICELCTLKEYMASK, kVK_Control }, { NX_DEVICERCTLKEYMASK, kVK_RightControl }, { NX_DEVICELALTKEYMASK, kVK_Option }, { NX_DEVICERALTKEYMASK, kVK_RightOption }, { NX_DEVICELCMDKEYMASK, kVK_Command }, { NX_DEVICERCMDKEYMASK, kVK_RightCommand }, }; NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]); NSUInteger changed; int i, last_changed; fix_device_modifiers_by_generic(&modifierFlags); changed = modifierFlags ^ lastModifierFlags; last_changed = -1; for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++) if (changed & modifiers[i].mask) last_changed = i; for (i = 0; i <= last_changed; i++) { if (changed & modifiers[i].mask) { BOOL pressed = (modifierFlags & modifiers[i].mask) != 0; if (i == last_changed) lastModifierFlags = modifierFlags; else { lastModifierFlags ^= modifiers[i].mask; fix_generic_modifiers_by_device(&lastModifierFlags); } // Caps lock generates one event for each press-release action. // We need to simulate a pair of events for each actual event. if (modifiers[i].mask == NX_ALPHASHIFTMASK) { [self postKey:modifiers[i].keycode pressed:TRUE modifiers:lastModifierFlags event:(NSEvent*)theEvent]; pressed = FALSE; } [self postKey:modifiers[i].keycode pressed:pressed modifiers:lastModifierFlags event:(NSEvent*)theEvent]; } } } - (void) applicationWillHide { savedVisibleState = [self isVisible]; } - (void) applicationDidUnhide { if ([self isVisible]) [self becameEligibleParentOrChild]; } /* * ---------- NSWindowDelegate methods ---------- */ - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize { macdrv_query* query; NSSize size; query = macdrv_create_query(); query->type = QUERY_MIN_MAX_INFO; query->window = (macdrv_window)[self retain]; [self.queue query:query timeout:0.5]; macdrv_release_query(query); size = [self contentMaxSize]; if (proposedSize.width < size.width) size.width = proposedSize.width; if (proposedSize.height < size.height) size.height = proposedSize.height; return size; } - (void)windowDidBecomeKey:(NSNotification *)notification { WineApplicationController* controller = [WineApplicationController sharedController]; NSEvent* event = [controller lastFlagsChanged]; if (event) [self flagsChanged:event]; if (causing_becomeKeyWindow == self) return; [controller windowGotFocus:self]; } - (void) windowDidChangeOcclusionState:(NSNotification*)notification { [self checkWineDisplayLink]; } - (void) windowDidChangeScreen:(NSNotification*)notification { [self checkWineDisplayLink]; } - (void)windowDidDeminiaturize:(NSNotification *)notification { WineApplicationController* controller = [WineApplicationController sharedController]; if (!ignore_windowDeminiaturize) [self postDidUnminimizeEvent]; ignore_windowDeminiaturize = FALSE; [self becameEligibleParentOrChild]; if (fullscreen && [self isOnActiveSpace]) [controller updateFullscreenWindows]; [controller adjustWindowLevels]; if (![self parentWindow]) [self postBroughtForwardEvent]; if (!self.disabled && !self.noActivate) { causing_becomeKeyWindow = self; [self makeKeyWindow]; causing_becomeKeyWindow = nil; [controller windowGotFocus:self]; } [self windowDidResize:notification]; [self checkWineDisplayLink]; } - (void) windowDidEndLiveResize:(NSNotification *)notification { if (!maximized) { macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self); [queue postEvent:event]; macdrv_release_event(event); } } - (void) windowDidEnterFullScreen:(NSNotification*)notification { enteringFullScreen = FALSE; enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime]; } - (void) windowDidExitFullScreen:(NSNotification*)notification { exitingFullScreen = FALSE; [self setFrame:nonFullscreenFrame display:YES animate:NO]; [self windowDidResize:nil]; } - (void) windowDidFailToEnterFullScreen:(NSWindow*)window { enteringFullScreen = FALSE; enteredFullScreenTime = 0; } - (void) windowDidFailToExitFullScreen:(NSWindow*)window { exitingFullScreen = FALSE; [self windowDidResize:nil]; } - (void)windowDidMiniaturize:(NSNotification *)notification { if (fullscreen && [self isOnActiveSpace]) [[WineApplicationController sharedController] updateFullscreenWindows]; [self checkWineDisplayLink]; } - (void)windowDidMove:(NSNotification *)notification { [self windowDidResize:notification]; } - (void)windowDidResignKey:(NSNotification *)notification { macdrv_event* event; if (causing_becomeKeyWindow) return; event = macdrv_create_event(WINDOW_LOST_FOCUS, self); [queue postEvent:event]; macdrv_release_event(event); } - (void)windowDidResize:(NSNotification *)notification { macdrv_event* event; NSRect frame = [self frame]; if ([self inLiveResize]) { if (NSMinX(frame) != NSMinX(frameAtResizeStart)) resizingFromLeft = TRUE; if (NSMaxY(frame) != NSMaxY(frameAtResizeStart)) resizingFromTop = TRUE; } frame = [self contentRectForFrameRect:frame]; if (ignore_windowResize || exitingFullScreen) return; if ([self preventResizing]) { [self setContentMinSize:frame.size]; [self setContentMaxSize:frame.size]; } [[WineApplicationController sharedController] flipRect:&frame]; /* Coalesce events by discarding any previous ones still in the queue. */ [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED) forWindow:self]; event = macdrv_create_event(WINDOW_FRAME_CHANGED, self); event->window_frame_changed.frame = NSRectToCGRect(frame); event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0; event->window_frame_changed.in_resize = [self inLiveResize]; [queue postEvent:event]; macdrv_release_event(event); [[[self contentView] inputContext] invalidateCharacterCoordinates]; [self updateFullscreen]; } - (BOOL)windowShouldClose:(id)sender { macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self); [queue postEvent:event]; macdrv_release_event(event); return NO; } - (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { if (maximized) { macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self); [queue postEvent:event]; macdrv_release_event(event); return NO; } else if (!resizable) { macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self); [queue postEvent:event]; macdrv_release_event(event); return NO; } return YES; } - (void) windowWillClose:(NSNotification*)notification { WineWindow* child; if (fakingClose) return; if (latentParentWindow) { [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self]; self.latentParentWindow = nil; } for (child in latentChildWindows) { if (child.latentParentWindow == self) child.latentParentWindow = nil; } [latentChildWindows removeAllObjects]; } - (void) windowWillEnterFullScreen:(NSNotification*)notification { enteringFullScreen = TRUE; nonFullscreenFrame = [self frame]; } - (void) windowWillExitFullScreen:(NSNotification*)notification { exitingFullScreen = TRUE; } - (void)windowWillMiniaturize:(NSNotification *)notification { [self becameIneligibleParentOrChild]; [self grabDockIconSnapshotFromWindow:nil force:NO]; } - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { if ([self inLiveResize]) { if (maximized) return self.frame.size; NSRect rect; macdrv_query* query; rect = [self frame]; if (resizingFromLeft) rect.origin.x = NSMaxX(rect) - frameSize.width; if (!resizingFromTop) rect.origin.y = NSMaxY(rect) - frameSize.height; rect.size = frameSize; rect = [self contentRectForFrameRect:rect]; [[WineApplicationController sharedController] flipRect:&rect]; query = macdrv_create_query(); query->type = QUERY_RESIZE_SIZE; query->window = (macdrv_window)[self retain]; query->resize_size.rect = NSRectToCGRect(rect); query->resize_size.from_left = resizingFromLeft; query->resize_size.from_top = resizingFromTop; if ([self.queue query:query timeout:0.1]) { rect = NSRectFromCGRect(query->resize_size.rect); rect = [self frameRectForContentRect:rect]; frameSize = rect.size; } macdrv_release_query(query); } return frameSize; } - (void) windowWillStartLiveResize:(NSNotification *)notification { [self endWindowDragging]; if (maximized) { macdrv_event* event; NSRect frame = [self contentRectForFrameRect:self.frame]; [[WineApplicationController sharedController] flipRect:&frame]; event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self); event->window_restore_requested.keep_frame = TRUE; event->window_restore_requested.frame = NSRectToCGRect(frame); [queue postEvent:event]; macdrv_release_event(event); } else [self sendResizeStartQuery]; frameAtResizeStart = [self frame]; resizingFromLeft = resizingFromTop = FALSE; } - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame { macdrv_query* query; NSRect currentContentRect, proposedContentRect, newContentRect, screenRect; NSSize maxSize; query = macdrv_create_query(); query->type = QUERY_MIN_MAX_INFO; query->window = (macdrv_window)[self retain]; [self.queue query:query timeout:0.5]; macdrv_release_query(query); currentContentRect = [self contentRectForFrameRect:[self frame]]; proposedContentRect = [self contentRectForFrameRect:proposedFrame]; maxSize = [self contentMaxSize]; newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width); newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height); // Try to keep the top-left corner where it is. newContentRect.origin.x = NSMinX(currentContentRect); newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect); // If that pushes the bottom or right off the screen, pull it up and to the left. screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]]; if (NSMaxX(newContentRect) > NSMaxX(screenRect)) newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect); if (NSMinY(newContentRect) < NSMinY(screenRect)) newContentRect.origin.y = NSMinY(screenRect); // If that pushes the top or left off the screen, push it down and the right // again. Do this last because the top-left corner is more important than the // bottom-right. if (NSMinX(newContentRect) < NSMinX(screenRect)) newContentRect.origin.x = NSMinX(screenRect); if (NSMaxY(newContentRect) > NSMaxY(screenRect)) newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect); return [self frameRectForContentRect:newContentRect]; } /* * ---------- NSPasteboardOwner methods ---------- */ - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type { macdrv_query* query = macdrv_create_query(); query->type = QUERY_PASTEBOARD_DATA; query->window = (macdrv_window)[self retain]; query->pasteboard_data.type = (CFStringRef)[type copy]; [self.queue query:query timeout:3]; macdrv_release_query(query); } /* * ---------- NSDraggingDestination methods ---------- */ - (NSDragOperation) draggingEntered:(id )sender { return [self draggingUpdated:sender]; } - (void) draggingExited:(id )sender { // This isn't really a query. We don't need any response. However, it // has to be processed in a similar manner as the other drag-and-drop // queries in order to maintain the proper order of operations. macdrv_query* query = macdrv_create_query(); query->type = QUERY_DRAG_EXITED; query->window = (macdrv_window)[self retain]; [self.queue query:query timeout:0.1]; macdrv_release_query(query); } - (NSDragOperation) draggingUpdated:(id )sender { NSDragOperation ret; NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil]; NSPasteboard* pb = [sender draggingPasteboard]; macdrv_query* query = macdrv_create_query(); query->type = QUERY_DRAG_OPERATION; query->window = (macdrv_window)[self retain]; query->drag_operation.x = pt.x; query->drag_operation.y = pt.y; query->drag_operation.offered_ops = [sender draggingSourceOperationMask]; query->drag_operation.accepted_op = NSDragOperationNone; query->drag_operation.pasteboard = (CFTypeRef)[pb retain]; [self.queue query:query timeout:3]; ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone; macdrv_release_query(query); return ret; } - (BOOL) performDragOperation:(id )sender { BOOL ret; NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil]; NSPasteboard* pb = [sender draggingPasteboard]; macdrv_query* query = macdrv_create_query(); query->type = QUERY_DRAG_DROP; query->window = (macdrv_window)[self retain]; query->drag_drop.x = pt.x; query->drag_drop.y = pt.y; query->drag_drop.op = [sender draggingSourceOperationMask]; query->drag_drop.pasteboard = (CFTypeRef)[pb retain]; [self.queue query:query timeout:3 * 60 flags:WineQueryProcessEvents]; ret = query->status; macdrv_release_query(query); return ret; } - (BOOL) wantsPeriodicDraggingUpdates { return NO; } @end /*********************************************************************** * macdrv_create_cocoa_window * * Create a Cocoa window with the given content frame and features (e.g. * title bar, close box, etc.). */ macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf, CGRect frame, void* hwnd, macdrv_event_queue queue) { __block WineWindow* window; OnMainThread(^{ window = [[WineWindow createWindowWithFeatures:wf windowFrame:NSRectFromCGRect(frame) hwnd:hwnd queue:(WineEventQueue*)queue] retain]; }); return (macdrv_window)window; } /*********************************************************************** * macdrv_destroy_cocoa_window * * Destroy a Cocoa window. */ void macdrv_destroy_cocoa_window(macdrv_window w) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window doOrderOut]; [window close]; }); [window.queue discardEventsMatchingMask:-1 forWindow:window]; [window release]; [pool release]; } /*********************************************************************** * macdrv_get_window_hwnd * * Get the hwnd that was set for the window at creation. */ void* macdrv_get_window_hwnd(macdrv_window w) { WineWindow* window = (WineWindow*)w; return window.hwnd; } /*********************************************************************** * macdrv_set_cocoa_window_features * * Update a Cocoa window's features. */ void macdrv_set_cocoa_window_features(macdrv_window w, const struct macdrv_window_features* wf) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window setWindowFeatures:wf]; }); } /*********************************************************************** * macdrv_set_cocoa_window_state * * Update a Cocoa window's state. */ void macdrv_set_cocoa_window_state(macdrv_window w, const struct macdrv_window_state* state) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window setMacDrvState:state]; }); } /*********************************************************************** * macdrv_set_cocoa_window_title * * Set a Cocoa window's title. */ void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title, size_t length) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; NSString* titleString; if (title) titleString = [NSString stringWithCharacters:title length:length]; else titleString = @""; OnMainThreadAsync(^{ [window setTitle:titleString]; if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu]) [NSApp changeWindowsItem:window title:titleString filename:NO]; }); [pool release]; } /*********************************************************************** * macdrv_order_cocoa_window * * Reorder a Cocoa window relative to other windows. If prev is * non-NULL, it is ordered below that window. Else, if next is non-NULL, * it is ordered above that window. Otherwise, it is ordered to the * front. */ void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p, macdrv_window n, int activate) { WineWindow* window = (WineWindow*)w; WineWindow* prev = (WineWindow*)p; WineWindow* next = (WineWindow*)n; OnMainThreadAsync(^{ [window orderBelow:prev orAbove:next activate:activate]; }); [window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) forWindow:window]; [next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) forWindow:next]; } /*********************************************************************** * macdrv_hide_cocoa_window * * Hides a Cocoa window. */ void macdrv_hide_cocoa_window(macdrv_window w) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window doOrderOut]; }); } /*********************************************************************** * macdrv_set_cocoa_window_frame * * Move a Cocoa window. */ void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window setFrameFromWine:NSRectFromCGRect(*new_frame)]; }); } /*********************************************************************** * macdrv_get_cocoa_window_frame * * Gets the frame of a Cocoa window. */ void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ NSRect frame; frame = [window contentRectForFrameRect:[window frame]]; [[WineApplicationController sharedController] flipRect:&frame]; *out_frame = NSRectToCGRect(frame); }); } /*********************************************************************** * macdrv_set_cocoa_parent_window * * Sets the parent window for a Cocoa window. If parent is NULL, clears * the parent window. */ void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window setMacDrvParentWindow:(WineWindow*)parent]; }); } /*********************************************************************** * macdrv_set_window_surface */ void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ window.surface = surface; window.surface_mutex = mutex; }); [pool release]; } /*********************************************************************** * macdrv_window_needs_display * * Mark a window as needing display in a specified rect (in non-client * area coordinates). */ void macdrv_window_needs_display(macdrv_window w, CGRect rect) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThreadAsync(^{ [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)]; }); [pool release]; } /*********************************************************************** * macdrv_set_window_shape * * Sets the shape of a Cocoa window from an array of rectangles. If * rects is NULL, resets the window's shape to its frame. */ void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ if (!rects || !count) { window.shape = nil; window.shapeData = nil; [window checkEmptyShaped]; } else { size_t length = sizeof(*rects) * count; if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length)) { NSBezierPath* path; unsigned int i; path = [NSBezierPath bezierPath]; for (i = 0; i < count; i++) [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])]; window.shape = path; window.shapeData = [NSData dataWithBytes:rects length:length]; [window checkEmptyShaped]; } } }); [pool release]; } /*********************************************************************** * macdrv_set_window_alpha */ void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; [window setAlphaValue:alpha]; [pool release]; } /*********************************************************************** * macdrv_set_window_color_key */ void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen, CGFloat keyBlue) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ window.colorKeyed = TRUE; window.colorKeyRed = keyRed; window.colorKeyGreen = keyGreen; window.colorKeyBlue = keyBlue; [window checkTransparency]; }); [pool release]; } /*********************************************************************** * macdrv_clear_window_color_key */ void macdrv_clear_window_color_key(macdrv_window w) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ window.colorKeyed = FALSE; [window checkTransparency]; }); [pool release]; } /*********************************************************************** * macdrv_window_use_per_pixel_alpha */ void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; OnMainThread(^{ window.usePerPixelAlpha = use_per_pixel_alpha; [window checkTransparency]; }); [pool release]; } /*********************************************************************** * macdrv_give_cocoa_window_focus * * Makes the Cocoa window "key" (gives it keyboard focus). This also * orders it front and, if its frame was not within the desktop bounds, * Cocoa will typically move it on-screen. */ void macdrv_give_cocoa_window_focus(macdrv_window w, int activate) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window makeFocused:activate]; }); } /*********************************************************************** * macdrv_set_window_min_max_sizes * * Sets the window's minimum and maximum content sizes. */ void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size) { WineWindow* window = (WineWindow*)w; OnMainThread(^{ [window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)]; }); } /*********************************************************************** * macdrv_create_view * * Creates and returns a view in the specified rect of the window. The * caller is responsible for calling macdrv_dispose_view() on the view * when it is done with it. */ macdrv_view macdrv_create_view(macdrv_window w, CGRect rect) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineWindow* window = (WineWindow*)w; __block WineContentView* view; if (CGRectIsNull(rect)) rect = CGRectZero; OnMainThread(^{ NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)]; [view setAutoresizesSubviews:NO]; [nc addObserver:view selector:@selector(updateGLContexts) name:NSViewGlobalFrameDidChangeNotification object:view]; [nc addObserver:view selector:@selector(updateGLContexts) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; [[window contentView] addSubview:view]; [window updateForGLSubviews]; }); [pool release]; return (macdrv_view)view; } /*********************************************************************** * macdrv_dispose_view * * Destroys a view previously returned by macdrv_create_view. */ void macdrv_dispose_view(macdrv_view v) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineContentView* view = (WineContentView*)v; OnMainThread(^{ NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; WineWindow* window = (WineWindow*)[view window]; [nc removeObserver:view name:NSViewGlobalFrameDidChangeNotification object:view]; [nc removeObserver:view name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; [view removeFromSuperview]; [view release]; [window updateForGLSubviews]; }); [pool release]; } /*********************************************************************** * macdrv_set_view_window_and_frame * * Move a view to a new window and/or position within its window. If w * is NULL, leave the view in its current window and just change its * frame. */ void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineContentView* view = (WineContentView*)v; WineWindow* window = (WineWindow*)w; if (CGRectIsNull(rect)) rect = CGRectZero; OnMainThread(^{ BOOL changedWindow = (window && window != [view window]); NSRect newFrame = NSRectFromCGRect(rect); NSRect oldFrame = [view frame]; BOOL needUpdateWindowForGLSubviews = FALSE; if (changedWindow) { WineWindow* oldWindow = (WineWindow*)[view window]; [view removeFromSuperview]; [oldWindow updateForGLSubviews]; [[window contentView] addSubview:view]; needUpdateWindowForGLSubviews = TRUE; } if (!NSEqualRects(oldFrame, newFrame)) { if (!changedWindow) [[view superview] setNeedsDisplayInRect:oldFrame]; if (NSEqualPoints(oldFrame.origin, newFrame.origin)) [view setFrameSize:newFrame.size]; else if (NSEqualSizes(oldFrame.size, newFrame.size)) [view setFrameOrigin:newFrame.origin]; else [view setFrame:newFrame]; [view setNeedsDisplay:YES]; needUpdateWindowForGLSubviews = TRUE; } if (needUpdateWindowForGLSubviews) [(WineWindow*)[view window] updateForGLSubviews]; }); [pool release]; } /*********************************************************************** * macdrv_add_view_opengl_context * * Add an OpenGL context to the list being tracked for each view. */ void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineContentView* view = (WineContentView*)v; WineOpenGLContext *context = (WineOpenGLContext*)c; OnMainThread(^{ [view addGLContext:context]; }); [pool release]; } /*********************************************************************** * macdrv_remove_view_opengl_context * * Add an OpenGL context to the list being tracked for each view. */ void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineContentView* view = (WineContentView*)v; WineOpenGLContext *context = (WineOpenGLContext*)c; OnMainThreadAsync(^{ [view removeGLContext:context]; }); [pool release]; } /*********************************************************************** * macdrv_window_background_color * * Returns the standard Mac window background color as a 32-bit value of * the form 0x00rrggbb. */ uint32_t macdrv_window_background_color(void) { static uint32_t result; static dispatch_once_t once; // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other // color spaces (RGB or grayscale). So, the only way to get RGB values out // of it is to draw with it. dispatch_once(&once, ^{ OnMainThread(^{ unsigned char rgbx[4]; unsigned char *planes = rgbx; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes pixelsWide:1 pixelsHigh:1 bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:0 bytesPerRow:4 bitsPerPixel:32]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]]; [[NSColor windowBackgroundColor] set]; NSRectFill(NSMakeRect(0, 0, 1, 1)); [NSGraphicsContext restoreGraphicsState]; [bitmap release]; result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2]; }); }); return result; } /*********************************************************************** * macdrv_send_text_input_event */ void macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data, int* done) { OnMainThreadAsync(^{ BOOL ret; macdrv_event* event; WineWindow* window = (WineWindow*)[NSApp keyWindow]; if (![window isKindOfClass:[WineWindow class]]) { window = (WineWindow*)[NSApp mainWindow]; if (![window isKindOfClass:[WineWindow class]]) window = [[WineApplicationController sharedController] frontWineWindow]; } if (window) { NSUInteger localFlags = flags; CGEventRef c; NSEvent* event; window.imeData = data; fix_device_modifiers_by_generic(&localFlags); // An NSEvent created with +keyEventWithType:... is internally marked // as synthetic and doesn't get sent through input methods. But one // created from a CGEvent doesn't have that problem. c = CGEventCreateKeyboardEvent(NULL, keyc, pressed); CGEventSetFlags(c, localFlags); CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat); event = [NSEvent eventWithCGEvent:c]; CFRelease(c); window.commandDone = FALSE; ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone; } else ret = FALSE; event = macdrv_create_event(SENT_TEXT_INPUT, window); event->sent_text_input.handled = ret; event->sent_text_input.done = done; [[window queue] postEvent:event]; macdrv_release_event(event); }); }