2 * MACDRV Cocoa window code
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>
23 #import "cocoa_window.h"
25 #include "macdrv_cocoa.h"
27 #import "cocoa_event.h"
28 #import "cocoa_opengl.h"
31 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
33 kVK_RightCommand = 0x36, /* Invented for Wine; was unused */
37 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
39 NSUInteger style_mask;
43 style_mask = NSTitledWindowMask;
44 if (wf->close_button) style_mask |= NSClosableWindowMask;
45 if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
46 if (wf->resizable) style_mask |= NSResizableWindowMask;
47 if (wf->utility) style_mask |= NSUtilityWindowMask;
49 else style_mask = NSBorderlessWindowMask;
55 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
58 for (screen in screens)
60 if (NSIntersectsRect(frame, [screen frame]))
67 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
69 for (NSScreen* screen in screens)
71 if (NSContainsRect(rect, [screen frame]))
78 /* We rely on the supposedly device-dependent modifier flags to distinguish the
79 keys on the left side of the keyboard from those on the right. Some event
80 sources don't set those device-depdendent flags. If we see a device-independent
81 flag for a modifier without either corresponding device-dependent flag, assume
83 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
85 if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
86 *modifiers |= NX_DEVICELCMDKEYMASK;
87 if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
88 *modifiers |= NX_DEVICELSHIFTKEYMASK;
89 if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
90 *modifiers |= NX_DEVICELCTLKEYMASK;
91 if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
92 *modifiers |= NX_DEVICELALTKEYMASK;
95 /* As we manipulate individual bits of a modifier mask, we can end up with
96 inconsistent sets of flags. In particular, we might set or clear one of the
97 left/right-specific bits, but not the corresponding non-side-specific bit.
98 Fix that. If either side-specific bit is set, set the non-side-specific bit,
99 otherwise clear it. */
100 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
102 if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
103 *modifiers |= NX_COMMANDMASK;
105 *modifiers &= ~NX_COMMANDMASK;
106 if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
107 *modifiers |= NX_SHIFTMASK;
109 *modifiers &= ~NX_SHIFTMASK;
110 if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
111 *modifiers |= NX_CONTROLMASK;
113 *modifiers &= ~NX_CONTROLMASK;
114 if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
115 *modifiers |= NX_ALTERNATEMASK;
117 *modifiers &= ~NX_ALTERNATEMASK;
120 static inline NSUInteger adjusted_modifiers_for_option_behavior(NSUInteger modifiers)
122 fix_device_modifiers_by_generic(&modifiers);
123 if (left_option_is_alt && (modifiers & NX_DEVICELALTKEYMASK))
125 modifiers |= NX_DEVICELCMDKEYMASK;
126 modifiers &= ~NX_DEVICELALTKEYMASK;
128 if (right_option_is_alt && (modifiers & NX_DEVICERALTKEYMASK))
130 modifiers |= NX_DEVICERCMDKEYMASK;
131 modifiers &= ~NX_DEVICERALTKEYMASK;
133 fix_generic_modifiers_by_device(&modifiers);
139 @interface WineContentView : NSView <NSTextInputClient>
141 NSMutableArray* glContexts;
142 NSMutableArray* pendingGlContexts;
144 NSMutableAttributedString* markedText;
145 NSRange markedTextSelection;
148 - (void) addGLContext:(WineOpenGLContext*)context;
149 - (void) removeGLContext:(WineOpenGLContext*)context;
150 - (void) updateGLContexts;
155 @interface WineWindow ()
157 @property (readwrite, nonatomic) BOOL disabled;
158 @property (readwrite, nonatomic) BOOL noActivate;
159 @property (readwrite, nonatomic) BOOL floating;
160 @property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose;
161 @property (retain, nonatomic) NSWindow* latentParentWindow;
163 @property (nonatomic) void* hwnd;
164 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
166 @property (nonatomic) void* surface;
167 @property (nonatomic) pthread_mutex_t* surface_mutex;
169 @property (copy, nonatomic) NSBezierPath* shape;
170 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
171 @property (readonly, nonatomic) BOOL needsTransparency;
173 @property (nonatomic) BOOL colorKeyed;
174 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
175 @property (nonatomic) BOOL usePerPixelAlpha;
177 @property (assign, nonatomic) void* imeData;
178 @property (nonatomic) BOOL commandDone;
180 @property (retain, nonatomic) NSTimer* liveResizeDisplayTimer;
182 - (void) updateColorSpace;
184 - (BOOL) becameEligibleParentOrChild;
185 - (void) becameIneligibleChild;
190 @implementation WineContentView
194 [markedText release];
195 [glContexts release];
196 [pendingGlContexts release];
205 - (void) drawRect:(NSRect)rect
207 WineWindow* window = (WineWindow*)[self window];
209 for (WineOpenGLContext* context in pendingGlContexts)
210 context.needsUpdate = TRUE;
211 [glContexts addObjectsFromArray:pendingGlContexts];
212 [pendingGlContexts removeAllObjects];
214 if ([window contentView] != self)
217 if (window.surface && window.surface_mutex &&
218 !pthread_mutex_lock(window.surface_mutex))
223 if (get_surface_blit_rects(window.surface, &rects, &count) && count)
225 CGContextRef context;
228 [window.shape addClip];
230 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
231 CGContextSetBlendMode(context, kCGBlendModeCopy);
233 for (i = 0; i < count; i++)
238 imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
239 image = create_surface_image(window.surface, &imageRect, FALSE);
243 if (window.colorKeyed)
245 CGImageRef maskedImage;
246 CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
247 window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
248 window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
249 maskedImage = CGImageCreateWithMaskingColors(image, components);
252 CGImageRelease(image);
257 CGContextDrawImage(context, imageRect, image);
259 CGImageRelease(image);
264 pthread_mutex_unlock(window.surface_mutex);
267 // If the window may be transparent, then we have to invalidate the
268 // shadow every time we draw. Also, if this is the first time we've
269 // drawn since changing from transparent to opaque.
270 if (![window isOpaque] || window.shapeChangedSinceLastDraw)
272 window.shapeChangedSinceLastDraw = FALSE;
273 [window invalidateShadow];
277 - (void) addGLContext:(WineOpenGLContext*)context
280 glContexts = [[NSMutableArray alloc] init];
281 if (!pendingGlContexts)
282 pendingGlContexts = [[NSMutableArray alloc] init];
283 [pendingGlContexts addObject:context];
284 [self setNeedsDisplay:YES];
285 [(WineWindow*)[self window] updateColorSpace];
288 - (void) removeGLContext:(WineOpenGLContext*)context
290 [glContexts removeObjectIdenticalTo:context];
291 [pendingGlContexts removeObjectIdenticalTo:context];
292 [(WineWindow*)[self window] updateColorSpace];
295 - (void) updateGLContexts
297 for (WineOpenGLContext* context in glContexts)
298 context.needsUpdate = TRUE;
301 - (BOOL) hasGLContext
303 return [glContexts count] || [pendingGlContexts count];
306 - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
311 - (BOOL) preservesContentDuringLiveResize
313 // Returning YES from this tells Cocoa to keep our view's content during
314 // a Cocoa-driven resize. In theory, we're also supposed to override
315 // -setFrameSize: to mark exposed sections as needing redisplay, but
316 // user32 will take care of that in a roundabout way. This way, we don't
317 // redraw until the window surface is flushed.
319 // This doesn't do anything when we resize the window ourselves.
323 - (BOOL)acceptsFirstResponder
325 return [[self window] contentView] == self;
328 - (void) completeText:(NSString*)text
331 WineWindow* window = (WineWindow*)[self window];
333 event = macdrv_create_event(IM_SET_TEXT, window);
334 event->im_set_text.data = [window imeData];
335 event->im_set_text.text = (CFStringRef)[text copy];
336 event->im_set_text.complete = TRUE;
338 [[window queue] postEvent:event];
340 macdrv_release_event(event);
342 [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
343 markedTextSelection = NSMakeRange(0, 0);
344 [[self inputContext] discardMarkedText];
348 * ---------- NSTextInputClient methods ----------
350 - (NSTextInputContext*) inputContext
353 markedText = [[NSMutableAttributedString alloc] init];
354 return [super inputContext];
357 - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
359 if ([string isKindOfClass:[NSAttributedString class]])
360 string = [string string];
362 if ([string isKindOfClass:[NSString class]])
363 [self completeText:string];
366 - (void) doCommandBySelector:(SEL)aSelector
368 [(WineWindow*)[self window] setCommandDone:TRUE];
371 - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
373 if ([string isKindOfClass:[NSAttributedString class]])
374 string = [string string];
376 if ([string isKindOfClass:[NSString class]])
379 WineWindow* window = (WineWindow*)[self window];
381 if (replacementRange.location == NSNotFound)
382 replacementRange = NSMakeRange(0, [markedText length]);
384 [markedText replaceCharactersInRange:replacementRange withString:string];
385 markedTextSelection = selectedRange;
386 markedTextSelection.location += replacementRange.location;
388 event = macdrv_create_event(IM_SET_TEXT, window);
389 event->im_set_text.data = [window imeData];
390 event->im_set_text.text = (CFStringRef)[[markedText string] copy];
391 event->im_set_text.complete = FALSE;
392 event->im_set_text.cursor_pos = markedTextSelection.location;
394 [[window queue] postEvent:event];
396 macdrv_release_event(event);
398 [[self inputContext] invalidateCharacterCoordinates];
404 [self completeText:nil];
407 - (NSRange) selectedRange
409 return markedTextSelection;
412 - (NSRange) markedRange
414 NSRange range = NSMakeRange(0, [markedText length]);
416 range.location = NSNotFound;
420 - (BOOL) hasMarkedText
422 return [markedText length] > 0;
425 - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
427 if (aRange.location >= [markedText length])
430 aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
432 *actualRange = aRange;
433 return [markedText attributedSubstringFromRange:aRange];
436 - (NSArray*) validAttributesForMarkedText
438 return [NSArray array];
441 - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
444 WineWindow* window = (WineWindow*)[self window];
447 aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
449 query = macdrv_create_query();
450 query->type = QUERY_IME_CHAR_RECT;
451 query->window = (macdrv_window)[window retain];
452 query->ime_char_rect.data = [window imeData];
453 query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
455 if ([window.queue query:query timeout:1])
457 aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
458 ret = NSRectFromCGRect(query->ime_char_rect.rect);
459 [[WineApplicationController sharedController] flipRect:&ret];
462 ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
464 macdrv_release_query(query);
467 *actualRange = aRange;
471 - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
476 - (NSInteger) windowLevel
478 return [[self window] level];
484 @implementation WineWindow
486 static WineWindow* causing_becomeKeyWindow;
488 @synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue;
489 @synthesize surface, surface_mutex;
490 @synthesize shape, shapeChangedSinceLastDraw;
491 @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
492 @synthesize usePerPixelAlpha;
493 @synthesize imeData, commandDone;
494 @synthesize liveResizeDisplayTimer;
496 + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
497 windowFrame:(NSRect)window_frame
499 queue:(WineEventQueue*)queue
502 WineContentView* contentView;
503 NSTrackingArea* trackingArea;
505 [[WineApplicationController sharedController] flipRect:&window_frame];
507 window = [[[self alloc] initWithContentRect:window_frame
508 styleMask:style_mask_for_features(wf)
509 backing:NSBackingStoreBuffered
510 defer:YES] autorelease];
512 if (!window) return nil;
514 /* Standardize windows to eliminate differences between titled and
515 borderless windows and between NSWindow and NSPanel. */
516 [window setHidesOnDeactivate:NO];
517 [window setReleasedWhenClosed:NO];
519 [window setOneShot:YES];
520 [window disableCursorRects];
521 [window setShowsResizeIndicator:NO];
522 [window setHasShadow:wf->shadow];
523 [window setAcceptsMouseMovedEvents:YES];
524 [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
525 [window setDelegate:window];
527 window.queue = queue;
528 window->savedContentMinSize = NSZeroSize;
529 window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
531 [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
532 (NSString*)kUTTypeContent,
535 contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
538 [contentView setAutoresizesSubviews:NO];
540 /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
541 because they give us mouse moves in the background. */
542 trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
543 options:(NSTrackingMouseMoved |
544 NSTrackingActiveAlways |
545 NSTrackingInVisibleRect)
547 userInfo:nil] autorelease];
550 [contentView addTrackingArea:trackingArea];
552 [window setContentView:contentView];
553 [window setInitialFirstResponder:contentView];
555 [[NSNotificationCenter defaultCenter] addObserver:window
556 selector:@selector(updateFullscreen)
557 name:NSApplicationDidChangeScreenParametersNotification
559 [window updateFullscreen];
566 [[NSNotificationCenter defaultCenter] removeObserver:self];
567 [liveResizeDisplayTimer invalidate];
568 [liveResizeDisplayTimer release];
570 [latentChildWindows release];
571 [latentParentWindow release];
576 - (void) adjustFeaturesForState
578 NSUInteger style = [self styleMask];
580 if (style & NSClosableWindowMask)
581 [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
582 if (style & NSMiniaturizableWindowMask)
583 [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
584 if (style & NSResizableWindowMask)
585 [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
588 - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
590 static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
591 NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
592 NSUInteger currentStyle = [self styleMask];
593 NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
595 if (newStyle != currentStyle)
597 BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
598 BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
599 if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
601 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
602 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
603 // just NSTitledWindowMask, the window buttons should disappear rather
604 // than just being disabled. But they don't. Similarly in reverse.
605 // The workaround is to also toggle NSClosableWindowMask at the same time.
606 [self setStyleMask:newStyle ^ NSClosableWindowMask];
608 [self setStyleMask:newStyle];
611 [self adjustFeaturesForState];
612 [self setHasShadow:wf->shadow];
617 return [self isVisible] || [self isMiniaturized];
620 - (NSInteger) minimumLevelForActive:(BOOL)active
624 if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
625 (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
626 level = NSFloatingWindowLevel;
628 level = NSNormalWindowLevel;
634 captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
636 if (captured || fullscreen)
639 level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
641 level = NSMainMenuWindowLevel + 1;
651 - (void) setMacDrvState:(const struct macdrv_window_state*)state
653 NSWindowCollectionBehavior behavior;
655 self.disabled = state->disabled;
656 self.noActivate = state->no_activate;
658 if (self.floating != state->floating)
660 self.floating = state->floating;
663 // Became floating. If child of non-floating window, make that
664 // relationship latent.
665 WineWindow* parent = (WineWindow*)[self parentWindow];
666 if (parent && !parent.floating)
667 [self becameIneligibleChild];
671 // Became non-floating. If parent of floating children, make that
672 // relationship latent.
674 for (child in [[[self childWindows] copy] autorelease])
677 [child becameIneligibleChild];
681 // Check our latent relationships. If floating status was the only
682 // reason they were latent, then make them active.
683 if ([self isVisible])
684 [self becameEligibleParentOrChild];
686 [[WineApplicationController sharedController] adjustWindowLevels];
689 behavior = NSWindowCollectionBehaviorDefault;
690 if (state->excluded_by_expose)
691 behavior |= NSWindowCollectionBehaviorTransient;
693 behavior |= NSWindowCollectionBehaviorManaged;
694 if (state->excluded_by_cycle)
696 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
697 if ([self isOrderedIn])
698 [NSApp removeWindowsItem:self];
702 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
703 if ([self isOrderedIn])
704 [NSApp addWindowsItem:self title:[self title] filename:NO];
706 [self setCollectionBehavior:behavior];
708 if (state->minimized_valid)
710 pendingMinimize = FALSE;
711 if (state->minimized && ![self isMiniaturized])
713 if ([self isVisible])
714 [super miniaturize:nil];
716 pendingMinimize = TRUE;
718 else if (!state->minimized && [self isMiniaturized])
720 ignore_windowDeminiaturize = TRUE;
721 [self deminiaturize:nil];
724 /* Whatever events regarding minimization might have been in the queue are now stale. */
725 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
730 - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
732 BOOL reordered = FALSE;
734 if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
736 if ([self level] > [child level])
737 [child setLevel:[self level]];
738 [self addChildWindow:child ordered:NSWindowAbove];
739 [latentChildWindows removeObjectIdenticalTo:child];
740 child.latentParentWindow = nil;
745 if (!latentChildWindows)
746 latentChildWindows = [[NSMutableArray alloc] init];
747 if (![latentChildWindows containsObject:child])
748 [latentChildWindows addObject:child];
749 child.latentParentWindow = self;
755 - (BOOL) addChildWineWindow:(WineWindow*)child
757 return [self addChildWineWindow:child assumeVisible:FALSE];
760 - (void) removeChildWineWindow:(WineWindow*)child
762 [self removeChildWindow:child];
763 if (child.latentParentWindow == self)
764 child.latentParentWindow = nil;
765 [latentChildWindows removeObjectIdenticalTo:child];
768 - (BOOL) becameEligibleParentOrChild
770 BOOL reordered = FALSE;
773 if (latentParentWindow.floating || !self.floating)
775 // If we aren't visible currently, we assume that we should be and soon
776 // will be. So, if the latent parent is visible that's enough to assume
777 // we can establish the parent-child relationship in Cocoa. That will
778 // actually make us visible, which is fine.
779 if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
783 // Here, though, we may not actually be visible yet and adding a child
784 // won't make us visible. The caller will have to call this method
785 // again after actually making us visible.
786 if ([self isVisible] && (count = [latentChildWindows count]))
788 NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
791 for (i = 0; i < count; i++)
793 WineWindow* child = [latentChildWindows objectAtIndex:i];
794 if ([child isVisible] && (self.floating || !child.floating))
796 if (child.latentParentWindow == self)
798 if ([self level] > [child level])
799 [child setLevel:[self level]];
800 [self addChildWindow:child ordered:NSWindowAbove];
801 child.latentParentWindow = nil;
805 ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
806 [indexesToRemove addIndex:i];
810 [latentChildWindows removeObjectsAtIndexes:indexesToRemove];
816 - (void) becameIneligibleChild
818 WineWindow* parent = (WineWindow*)[self parentWindow];
821 if (!parent->latentChildWindows)
822 parent->latentChildWindows = [[NSMutableArray alloc] init];
823 [parent->latentChildWindows insertObject:self atIndex:0];
824 self.latentParentWindow = parent;
825 [parent removeChildWindow:self];
829 - (void) becameIneligibleParentOrChild
831 NSArray* childWindows = [self childWindows];
833 [self becameIneligibleChild];
835 if ([childWindows count])
839 childWindows = [[childWindows copy] autorelease];
840 for (child in childWindows)
842 child.latentParentWindow = self;
843 [self removeChildWindow:child];
846 if (latentChildWindows)
847 [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
849 latentChildWindows = [childWindows mutableCopy];
853 // Determine if, among Wine windows, this window is directly above or below
854 // a given other Wine window with no other Wine window intervening.
855 // Intervening non-Wine windows are ignored.
856 - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
858 NSNumber* windowNumber;
859 NSNumber* otherWindowNumber;
860 NSArray* windowNumbers;
861 NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
863 if (![self isVisible] || ![otherWindow isVisible])
866 windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
867 otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
868 windowNumbers = [[self class] windowNumbersWithOptions:0];
869 windowIndex = [windowNumbers indexOfObject:windowNumber];
870 otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
872 if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
875 if (orderingMode == NSWindowAbove)
877 lowIndex = windowIndex;
878 highIndex = otherWindowIndex;
880 else if (orderingMode == NSWindowBelow)
882 lowIndex = otherWindowIndex;
883 highIndex = windowIndex;
888 if (highIndex <= lowIndex)
891 for (i = lowIndex + 1; i < highIndex; i++)
893 NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
894 NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
895 if ([interveningWindow isKindOfClass:[WineWindow class]])
902 - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
904 NSMutableArray* windowNumbers;
905 NSNumber* childWindowNumber;
906 NSUInteger otherIndex, limit;
907 NSArray* origChildren;
908 NSMutableArray* children;
910 // Get the z-order from the window server and modify it to reflect the
911 // requested window ordering.
912 windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
913 childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
914 [windowNumbers removeObject:childWindowNumber];
915 otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
916 [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
918 // Get our child windows and sort them in the reverse of the desired
919 // z-order (back-to-front).
920 origChildren = [self childWindows];
921 children = [[origChildren mutableCopy] autorelease];
922 [children sortWithOptions:NSSortStable
923 usingComparator:^NSComparisonResult(id obj1, id obj2){
924 NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]];
925 NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]];
926 NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
927 NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
928 if (index1 == NSNotFound)
930 if (index2 == NSNotFound)
931 return NSOrderedSame;
933 return NSOrderedAscending;
935 else if (index2 == NSNotFound)
936 return NSOrderedDescending;
937 else if (index1 < index2)
938 return NSOrderedDescending;
939 else if (index2 < index1)
940 return NSOrderedAscending;
942 return NSOrderedSame;
945 // If the current and desired children arrays match up to a point, leave
946 // those matching children alone.
947 limit = MIN([origChildren count], [children count]);
948 for (otherIndex = 0; otherIndex < limit; otherIndex++)
950 if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex])
953 [children removeObjectsInRange:NSMakeRange(0, otherIndex)];
955 // Remove all of the child windows and re-add them back-to-front so they
956 // are in the desired order.
957 for (other in children)
958 [self removeChildWindow:other];
959 for (other in children)
960 [self addChildWindow:other ordered:NSWindowAbove];
963 /* Returns whether or not the window was ordered in, which depends on if
964 its frame intersects any screen. */
965 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
967 WineApplicationController* controller = [WineApplicationController sharedController];
968 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
969 if (on_screen && ![self isMiniaturized])
971 BOOL needAdjustWindowLevels = FALSE;
972 BOOL wasVisible = [self isVisible];
974 [controller transformProcessToForeground];
977 [NSApp activateIgnoringOtherApps:YES];
979 NSDisableScreenUpdates();
981 if ([self becameEligibleParentOrChild])
982 needAdjustWindowLevels = TRUE;
986 WineWindow* other = [prev isVisible] ? prev : next;
987 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
989 if (![self isOrdered:orderingMode relativeTo:other])
991 WineWindow* parent = (WineWindow*)[self parentWindow];
992 WineWindow* otherParent = (WineWindow*)[other parentWindow];
994 // This window level may not be right for this window based
995 // on floating-ness, fullscreen-ness, etc. But we set it
996 // temporarily to allow us to order the windows properly.
997 // Then the levels get fixed by -adjustWindowLevels.
998 if ([self level] != [other level])
999 [self setLevel:[other level]];
1000 [self orderWindow:orderingMode relativeTo:[other windowNumber]];
1002 // The above call to -[NSWindow orderWindow:relativeTo:] won't
1003 // reorder windows which are both children of the same parent
1004 // relative to each other, so do that separately.
1005 if (parent && parent == otherParent)
1006 [parent order:orderingMode childWindow:self relativeTo:other];
1008 needAdjustWindowLevels = TRUE;
1013 // Again, temporarily set level to make sure we can order to
1015 next = [controller frontWineWindow];
1016 if (next && [self level] < [next level])
1017 [self setLevel:[next level]];
1018 [self orderFront:nil];
1019 needAdjustWindowLevels = TRUE;
1022 if ([self becameEligibleParentOrChild])
1023 needAdjustWindowLevels = TRUE;
1025 if (needAdjustWindowLevels)
1027 if (!wasVisible && fullscreen && [self isOnActiveSpace])
1028 [controller updateFullscreenWindows];
1029 [controller adjustWindowLevels];
1032 if (pendingMinimize)
1034 [super miniaturize:nil];
1035 pendingMinimize = FALSE;
1038 NSEnableScreenUpdates();
1040 /* Cocoa may adjust the frame when the window is ordered onto the screen.
1041 Generate a frame-changed event just in case. The back end will ignore
1042 it if nothing actually changed. */
1043 [self windowDidResize:nil];
1045 if (![self isExcludedFromWindowsMenu])
1046 [NSApp addWindowsItem:self title:[self title] filename:NO];
1054 WineApplicationController* controller = [WineApplicationController sharedController];
1055 BOOL wasVisible = [self isVisible];
1056 BOOL wasOnActiveSpace = [self isOnActiveSpace];
1058 if ([self isMiniaturized])
1059 pendingMinimize = TRUE;
1061 [self becameIneligibleParentOrChild];
1062 if ([self isMiniaturized])
1066 fakingClose = FALSE;
1069 [self orderOut:nil];
1070 if (wasVisible && wasOnActiveSpace && fullscreen)
1071 [controller updateFullscreenWindows];
1072 [controller adjustWindowLevels];
1073 [NSApp removeWindowsItem:self];
1076 - (void) updateFullscreen
1078 NSRect contentRect = [self contentRectForFrameRect:[self frame]];
1079 BOOL nowFullscreen = (screen_covered_by_rect(contentRect, [NSScreen screens]) != nil);
1081 if (nowFullscreen != fullscreen)
1083 WineApplicationController* controller = [WineApplicationController sharedController];
1085 fullscreen = nowFullscreen;
1086 if ([self isVisible] && [self isOnActiveSpace])
1087 [controller updateFullscreenWindows];
1089 [controller adjustWindowLevels];
1093 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
1095 NSArray* screens = [NSScreen screens];
1096 BOOL on_screen = [self isOrderedIn];
1098 if (![screens count]) return on_screen;
1100 /* Origin is (left, top) in a top-down space. Need to convert it to
1101 (left, bottom) in a bottom-up space. */
1102 [[WineApplicationController sharedController] flipRect:&contentRect];
1106 on_screen = frame_intersects_screens(contentRect, screens);
1111 /* The back end is establishing a new window size and position. It's
1112 not interested in any stale events regarding those that may be sitting
1114 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1117 if (!NSIsEmptyRect(contentRect))
1119 NSRect frame, oldFrame;
1121 oldFrame = [self frame];
1122 frame = [self frameRectForContentRect:contentRect];
1123 if (!NSEqualRects(frame, oldFrame))
1125 if (NSEqualSizes(frame.size, oldFrame.size))
1126 [self setFrameOrigin:frame.origin];
1129 [self setFrame:frame display:YES];
1130 [self updateColorSpace];
1133 [self updateFullscreen];
1137 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
1138 event. The back end will ignore it if nothing actually changed. */
1139 [self windowDidResize:nil];
1147 - (void) setMacDrvParentWindow:(WineWindow*)parent
1149 WineWindow* oldParent = (WineWindow*)[self parentWindow];
1150 if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
1152 [oldParent removeChildWineWindow:self];
1153 [latentParentWindow removeChildWineWindow:self];
1154 if ([parent addChildWineWindow:self])
1155 [[WineApplicationController sharedController] adjustWindowLevels];
1159 - (void) setDisabled:(BOOL)newValue
1161 if (disabled != newValue)
1163 disabled = newValue;
1164 [self adjustFeaturesForState];
1168 NSSize size = [self contentRectForFrameRect:[self frame]].size;
1169 [self setContentMinSize:size];
1170 [self setContentMaxSize:size];
1174 [self setContentMaxSize:savedContentMaxSize];
1175 [self setContentMinSize:savedContentMinSize];
1180 - (BOOL) needsTransparency
1182 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
1185 - (void) checkTransparency
1187 if (![self isOpaque] && !self.needsTransparency)
1189 [self setBackgroundColor:[NSColor windowBackgroundColor]];
1190 [self setOpaque:YES];
1192 else if ([self isOpaque] && self.needsTransparency)
1194 [self setBackgroundColor:[NSColor clearColor]];
1195 [self setOpaque:NO];
1199 - (void) setShape:(NSBezierPath*)newShape
1201 if (shape == newShape) return;
1202 if (shape && newShape && [shape isEqual:newShape]) return;
1206 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
1210 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
1212 shape = [newShape copy];
1213 self.shapeChangedSinceLastDraw = TRUE;
1215 [self checkTransparency];
1218 - (void) setLiveResizeDisplayTimer:(NSTimer*)newTimer
1220 if (newTimer != liveResizeDisplayTimer)
1222 [liveResizeDisplayTimer invalidate];
1223 [liveResizeDisplayTimer release];
1224 liveResizeDisplayTimer = [newTimer retain];
1228 - (void) makeFocused:(BOOL)activate
1230 [self orderBelow:nil orAbove:nil activate:activate];
1232 causing_becomeKeyWindow = self;
1233 [self makeKeyWindow];
1234 causing_becomeKeyWindow = nil;
1237 - (void) postKey:(uint16_t)keyCode
1238 pressed:(BOOL)pressed
1239 modifiers:(NSUInteger)modifiers
1240 event:(NSEvent*)theEvent
1242 macdrv_event* event;
1244 WineApplicationController* controller = [WineApplicationController sharedController];
1246 event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
1247 event->key.keycode = keyCode;
1248 event->key.modifiers = modifiers;
1249 event->key.time_ms = [controller ticksForEventTime:[theEvent timestamp]];
1251 if ((cgevent = [theEvent CGEvent]))
1253 CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
1254 kCGKeyboardEventKeyboardType);
1255 if (keyboardType != controller.keyboardType)
1257 controller.keyboardType = keyboardType;
1258 [controller keyboardSelectionDidChange];
1262 [queue postEvent:event];
1264 macdrv_release_event(event);
1266 [controller noteKey:keyCode pressed:pressed];
1269 - (void) postKeyEvent:(NSEvent *)theEvent
1271 [self flagsChanged:theEvent];
1272 [self postKey:[theEvent keyCode]
1273 pressed:[theEvent type] == NSKeyDown
1274 modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags])
1278 - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
1280 savedContentMinSize = minSize;
1281 savedContentMaxSize = maxSize;
1284 [self setContentMinSize:minSize];
1285 [self setContentMaxSize:maxSize];
1289 - (WineWindow*) ancestorWineWindow
1291 WineWindow* ancestor = self;
1294 WineWindow* parent = (WineWindow*)[ancestor parentWindow];
1295 if ([parent isKindOfClass:[WineWindow class]])
1303 - (void) postBroughtForwardEvent
1305 macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
1306 [queue postEvent:event];
1307 macdrv_release_event(event);
1312 * ---------- NSWindow method overrides ----------
1314 - (BOOL) canBecomeKeyWindow
1316 if (causing_becomeKeyWindow == self) return YES;
1317 if (self.disabled || self.noActivate) return NO;
1318 return [self isKeyWindow];
1321 - (BOOL) canBecomeMainWindow
1323 return [self canBecomeKeyWindow];
1326 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1328 // If a window is sized to completely cover a screen, then it's in
1329 // full-screen mode. In that case, we don't allow NSWindow to constrain
1331 NSRect contentRect = [self contentRectForFrameRect:frameRect];
1332 if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
1333 frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1337 - (BOOL) isExcludedFromWindowsMenu
1339 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1342 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1344 BOOL ret = [super validateMenuItem:menuItem];
1346 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1347 ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1352 /* We don't call this. It's the action method of the items in the Window menu. */
1353 - (void) makeKeyAndOrderFront:(id)sender
1355 if ([self isMiniaturized])
1356 [self deminiaturize:nil];
1357 [self orderBelow:nil orAbove:nil activate:NO];
1358 [[self ancestorWineWindow] postBroughtForwardEvent];
1360 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1361 [[WineApplicationController sharedController] windowGotFocus:self];
1364 - (void) sendEvent:(NSEvent*)event
1366 /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1367 interface control. For example, Control-Tab switches focus among
1368 views. We want to bypass that feature, so directly route key-down
1369 events to -keyDown:. */
1370 if ([event type] == NSKeyDown)
1371 [[self firstResponder] keyDown:event];
1373 [super sendEvent:event];
1376 - (void) miniaturize:(id)sender
1378 macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
1379 [queue postEvent:event];
1380 macdrv_release_event(event);
1383 // We normally use the generic/calibrated RGB color space for the window,
1384 // rather than the device color space, to avoid expensive color conversion
1385 // which slows down drawing. However, for windows displaying OpenGL, having
1386 // a different color space than the screen greatly reduces frame rates, often
1387 // limiting it to the display refresh rate.
1389 // To avoid this, we switch back to the screen color space whenever the
1390 // window is covered by a view with an attached OpenGL context.
1391 - (void) updateColorSpace
1393 NSRect contentRect = [[self contentView] frame];
1394 BOOL coveredByGLView = FALSE;
1395 for (WineContentView* view in [[self contentView] subviews])
1397 if ([view hasGLContext])
1399 NSRect frame = [view convertRect:[view bounds] toView:nil];
1400 if (NSContainsRect(frame, contentRect))
1402 coveredByGLView = TRUE;
1408 if (coveredByGLView)
1409 [self setColorSpace:nil];
1411 [self setColorSpace:[NSColorSpace genericRGBColorSpace]];
1416 * ---------- NSResponder method overrides ----------
1418 - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1420 - (void) flagsChanged:(NSEvent *)theEvent
1422 static const struct {
1426 { NX_ALPHASHIFTMASK, kVK_CapsLock },
1427 { NX_DEVICELSHIFTKEYMASK, kVK_Shift },
1428 { NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
1429 { NX_DEVICELCTLKEYMASK, kVK_Control },
1430 { NX_DEVICERCTLKEYMASK, kVK_RightControl },
1431 { NX_DEVICELALTKEYMASK, kVK_Option },
1432 { NX_DEVICERALTKEYMASK, kVK_RightOption },
1433 { NX_DEVICELCMDKEYMASK, kVK_Command },
1434 { NX_DEVICERCMDKEYMASK, kVK_RightCommand },
1437 NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]);
1439 int i, last_changed;
1441 fix_device_modifiers_by_generic(&modifierFlags);
1442 changed = modifierFlags ^ lastModifierFlags;
1445 for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
1446 if (changed & modifiers[i].mask)
1449 for (i = 0; i <= last_changed; i++)
1451 if (changed & modifiers[i].mask)
1453 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
1455 if (i == last_changed)
1456 lastModifierFlags = modifierFlags;
1459 lastModifierFlags ^= modifiers[i].mask;
1460 fix_generic_modifiers_by_device(&lastModifierFlags);
1463 // Caps lock generates one event for each press-release action.
1464 // We need to simulate a pair of events for each actual event.
1465 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1467 [self postKey:modifiers[i].keycode
1469 modifiers:lastModifierFlags
1470 event:(NSEvent*)theEvent];
1474 [self postKey:modifiers[i].keycode
1476 modifiers:lastModifierFlags
1477 event:(NSEvent*)theEvent];
1484 * ---------- NSWindowDelegate methods ----------
1486 - (void)windowDidBecomeKey:(NSNotification *)notification
1488 WineApplicationController* controller = [WineApplicationController sharedController];
1489 NSEvent* event = [controller lastFlagsChanged];
1491 [self flagsChanged:event];
1493 if (causing_becomeKeyWindow == self) return;
1495 [controller windowGotFocus:self];
1498 - (void)windowDidDeminiaturize:(NSNotification *)notification
1500 WineApplicationController* controller = [WineApplicationController sharedController];
1502 if (!ignore_windowDeminiaturize)
1504 macdrv_event* event;
1506 /* Coalesce events by discarding any previous ones still in the queue. */
1507 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1510 event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1511 [queue postEvent:event];
1512 macdrv_release_event(event);
1515 ignore_windowDeminiaturize = FALSE;
1517 [self becameEligibleParentOrChild];
1519 if (fullscreen && [self isOnActiveSpace])
1520 [controller updateFullscreenWindows];
1521 [controller adjustWindowLevels];
1523 if (![self parentWindow])
1524 [self postBroughtForwardEvent];
1526 if (!self.disabled && !self.noActivate)
1528 causing_becomeKeyWindow = self;
1529 [self makeKeyWindow];
1530 causing_becomeKeyWindow = nil;
1531 [controller windowGotFocus:self];
1534 [self windowDidResize:notification];
1537 - (void) windowDidEndLiveResize:(NSNotification *)notification
1539 macdrv_query* query = macdrv_create_query();
1540 query->type = QUERY_RESIZE_END;
1541 query->window = (macdrv_window)[self retain];
1543 [self.queue query:query timeout:0.3];
1544 macdrv_release_query(query);
1546 self.liveResizeDisplayTimer = nil;
1549 - (void)windowDidMiniaturize:(NSNotification *)notification
1551 if (fullscreen && [self isOnActiveSpace])
1552 [[WineApplicationController sharedController] updateFullscreenWindows];
1555 - (void)windowDidMove:(NSNotification *)notification
1557 [self windowDidResize:notification];
1560 - (void)windowDidResignKey:(NSNotification *)notification
1562 macdrv_event* event;
1564 if (causing_becomeKeyWindow) return;
1566 event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1567 [queue postEvent:event];
1568 macdrv_release_event(event);
1571 - (void)windowDidResize:(NSNotification *)notification
1573 macdrv_event* event;
1574 NSRect frame = [self contentRectForFrameRect:[self frame]];
1578 [self setContentMinSize:frame.size];
1579 [self setContentMaxSize:frame.size];
1582 [[WineApplicationController sharedController] flipRect:&frame];
1584 /* Coalesce events by discarding any previous ones still in the queue. */
1585 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1588 event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1589 event->window_frame_changed.frame = NSRectToCGRect(frame);
1590 [queue postEvent:event];
1591 macdrv_release_event(event);
1593 [[[self contentView] inputContext] invalidateCharacterCoordinates];
1594 [self updateFullscreen];
1597 - (BOOL)windowShouldClose:(id)sender
1599 macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1600 [queue postEvent:event];
1601 macdrv_release_event(event);
1605 - (void) windowWillClose:(NSNotification*)notification
1609 if (fakingClose) return;
1610 if (latentParentWindow)
1612 [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
1613 self.latentParentWindow = nil;
1616 for (child in latentChildWindows)
1618 if (child.latentParentWindow == self)
1619 child.latentParentWindow = nil;
1621 [latentChildWindows removeAllObjects];
1624 - (void)windowWillMiniaturize:(NSNotification *)notification
1626 [self becameIneligibleParentOrChild];
1629 - (void) windowWillStartLiveResize:(NSNotification *)notification
1631 macdrv_query* query = macdrv_create_query();
1632 query->type = QUERY_RESIZE_START;
1633 query->window = (macdrv_window)[self retain];
1635 [self.queue query:query timeout:0.3];
1636 macdrv_release_query(query);
1638 // There's a strange restriction in window redrawing during Cocoa-
1639 // managed window resizing. Only calls to -[NSView setNeedsDisplay...]
1640 // that happen synchronously when Cocoa tells us that our window size
1641 // has changed or asynchronously in a short interval thereafter provoke
1642 // the window to redraw. Calls to those methods that happen asynchronously
1643 // a half second or more after the last change of the window size aren't
1644 // heeded until the next resize-related user event (e.g. mouse movement).
1646 // Wine often has a significant delay between when it's been told that
1647 // the window has changed size and when it can flush completed drawing.
1648 // So, our windows would get stuck with incomplete drawing for as long
1649 // as the user holds the mouse button down and doesn't move it.
1651 // We address this by "manually" asking our windows to check if they need
1652 // redrawing every so often (during live resize only).
1653 self.liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
1655 selector:@selector(displayIfNeeded)
1658 [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
1659 forMode:NSRunLoopCommonModes];
1662 - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
1664 macdrv_query* query;
1665 NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
1668 query = macdrv_create_query();
1669 query->type = QUERY_MIN_MAX_INFO;
1670 query->window = (macdrv_window)[self retain];
1671 [self.queue query:query timeout:0.5];
1672 macdrv_release_query(query);
1674 currentContentRect = [self contentRectForFrameRect:[self frame]];
1675 proposedContentRect = [self contentRectForFrameRect:proposedFrame];
1677 maxSize = [self contentMaxSize];
1678 newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
1679 newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
1681 // Try to keep the top-left corner where it is.
1682 newContentRect.origin.x = NSMinX(currentContentRect);
1683 newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
1685 // If that pushes the bottom or right off the screen, pull it up and to the left.
1686 screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
1687 if (NSMaxX(newContentRect) > NSMaxX(screenRect))
1688 newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
1689 if (NSMinY(newContentRect) < NSMinY(screenRect))
1690 newContentRect.origin.y = NSMinY(screenRect);
1692 // If that pushes the top or left off the screen, push it down and the right
1693 // again. Do this last because the top-left corner is more important than the
1695 if (NSMinX(newContentRect) < NSMinX(screenRect))
1696 newContentRect.origin.x = NSMinX(screenRect);
1697 if (NSMaxY(newContentRect) > NSMaxY(screenRect))
1698 newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
1700 return [self frameRectForContentRect:newContentRect];
1705 * ---------- NSPasteboardOwner methods ----------
1707 - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
1709 macdrv_query* query = macdrv_create_query();
1710 query->type = QUERY_PASTEBOARD_DATA;
1711 query->window = (macdrv_window)[self retain];
1712 query->pasteboard_data.type = (CFStringRef)[type copy];
1714 [self.queue query:query timeout:3];
1715 macdrv_release_query(query);
1720 * ---------- NSDraggingDestination methods ----------
1722 - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
1724 return [self draggingUpdated:sender];
1727 - (void) draggingExited:(id <NSDraggingInfo>)sender
1729 // This isn't really a query. We don't need any response. However, it
1730 // has to be processed in a similar manner as the other drag-and-drop
1731 // queries in order to maintain the proper order of operations.
1732 macdrv_query* query = macdrv_create_query();
1733 query->type = QUERY_DRAG_EXITED;
1734 query->window = (macdrv_window)[self retain];
1736 [self.queue query:query timeout:0.1];
1737 macdrv_release_query(query);
1740 - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
1742 NSDragOperation ret;
1743 NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1744 NSPasteboard* pb = [sender draggingPasteboard];
1746 macdrv_query* query = macdrv_create_query();
1747 query->type = QUERY_DRAG_OPERATION;
1748 query->window = (macdrv_window)[self retain];
1749 query->drag_operation.x = pt.x;
1750 query->drag_operation.y = pt.y;
1751 query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
1752 query->drag_operation.accepted_op = NSDragOperationNone;
1753 query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
1755 [self.queue query:query timeout:3];
1756 ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
1757 macdrv_release_query(query);
1762 - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
1765 NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1766 NSPasteboard* pb = [sender draggingPasteboard];
1768 macdrv_query* query = macdrv_create_query();
1769 query->type = QUERY_DRAG_DROP;
1770 query->window = (macdrv_window)[self retain];
1771 query->drag_drop.x = pt.x;
1772 query->drag_drop.y = pt.y;
1773 query->drag_drop.op = [sender draggingSourceOperationMask];
1774 query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
1776 [self.queue query:query timeout:3 * 60 processEvents:YES];
1777 ret = query->status;
1778 macdrv_release_query(query);
1783 - (BOOL) wantsPeriodicDraggingUpdates
1791 /***********************************************************************
1792 * macdrv_create_cocoa_window
1794 * Create a Cocoa window with the given content frame and features (e.g.
1795 * title bar, close box, etc.).
1797 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1798 CGRect frame, void* hwnd, macdrv_event_queue queue)
1800 __block WineWindow* window;
1803 window = [[WineWindow createWindowWithFeatures:wf
1804 windowFrame:NSRectFromCGRect(frame)
1806 queue:(WineEventQueue*)queue] retain];
1809 return (macdrv_window)window;
1812 /***********************************************************************
1813 * macdrv_destroy_cocoa_window
1815 * Destroy a Cocoa window.
1817 void macdrv_destroy_cocoa_window(macdrv_window w)
1819 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1820 WineWindow* window = (WineWindow*)w;
1823 [window doOrderOut];
1826 [window.queue discardEventsMatchingMask:-1 forWindow:window];
1832 /***********************************************************************
1833 * macdrv_get_window_hwnd
1835 * Get the hwnd that was set for the window at creation.
1837 void* macdrv_get_window_hwnd(macdrv_window w)
1839 WineWindow* window = (WineWindow*)w;
1843 /***********************************************************************
1844 * macdrv_set_cocoa_window_features
1846 * Update a Cocoa window's features.
1848 void macdrv_set_cocoa_window_features(macdrv_window w,
1849 const struct macdrv_window_features* wf)
1851 WineWindow* window = (WineWindow*)w;
1854 [window setWindowFeatures:wf];
1858 /***********************************************************************
1859 * macdrv_set_cocoa_window_state
1861 * Update a Cocoa window's state.
1863 void macdrv_set_cocoa_window_state(macdrv_window w,
1864 const struct macdrv_window_state* state)
1866 WineWindow* window = (WineWindow*)w;
1869 [window setMacDrvState:state];
1873 /***********************************************************************
1874 * macdrv_set_cocoa_window_title
1876 * Set a Cocoa window's title.
1878 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1881 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1882 WineWindow* window = (WineWindow*)w;
1883 NSString* titleString;
1886 titleString = [NSString stringWithCharacters:title length:length];
1889 OnMainThreadAsync(^{
1890 [window setTitle:titleString];
1891 if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
1892 [NSApp changeWindowsItem:window title:titleString filename:NO];
1898 /***********************************************************************
1899 * macdrv_order_cocoa_window
1901 * Reorder a Cocoa window relative to other windows. If prev is
1902 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
1903 * it is ordered above that window. Otherwise, it is ordered to the
1906 * Returns true if the window has actually been ordered onto the screen
1907 * (i.e. if its frame intersects with a screen). Otherwise, false.
1909 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1910 macdrv_window next, int activate)
1912 WineWindow* window = (WineWindow*)w;
1913 __block BOOL on_screen;
1916 on_screen = [window orderBelow:(WineWindow*)prev
1917 orAbove:(WineWindow*)next
1924 /***********************************************************************
1925 * macdrv_hide_cocoa_window
1927 * Hides a Cocoa window.
1929 void macdrv_hide_cocoa_window(macdrv_window w)
1931 WineWindow* window = (WineWindow*)w;
1934 [window doOrderOut];
1938 /***********************************************************************
1939 * macdrv_set_cocoa_window_frame
1941 * Move a Cocoa window. If the window has been moved out of the bounds
1942 * of the desktop, it is ordered out. (This routine won't ever order a
1943 * window in, though.)
1945 * Returns true if the window is on screen; false otherwise.
1947 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1949 WineWindow* window = (WineWindow*)w;
1950 __block BOOL on_screen;
1953 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1959 /***********************************************************************
1960 * macdrv_get_cocoa_window_frame
1962 * Gets the frame of a Cocoa window.
1964 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1966 WineWindow* window = (WineWindow*)w;
1971 frame = [window contentRectForFrameRect:[window frame]];
1972 [[WineApplicationController sharedController] flipRect:&frame];
1973 *out_frame = NSRectToCGRect(frame);
1977 /***********************************************************************
1978 * macdrv_set_cocoa_parent_window
1980 * Sets the parent window for a Cocoa window. If parent is NULL, clears
1981 * the parent window.
1983 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1985 WineWindow* window = (WineWindow*)w;
1988 [window setMacDrvParentWindow:(WineWindow*)parent];
1992 /***********************************************************************
1993 * macdrv_set_window_surface
1995 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1997 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1998 WineWindow* window = (WineWindow*)w;
2001 window.surface = surface;
2002 window.surface_mutex = mutex;
2008 /***********************************************************************
2009 * macdrv_window_needs_display
2011 * Mark a window as needing display in a specified rect (in non-client
2012 * area coordinates).
2014 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
2016 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2017 WineWindow* window = (WineWindow*)w;
2019 OnMainThreadAsync(^{
2020 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
2026 /***********************************************************************
2027 * macdrv_set_window_shape
2029 * Sets the shape of a Cocoa window from an array of rectangles. If
2030 * rects is NULL, resets the window's shape to its frame.
2032 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
2034 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2035 WineWindow* window = (WineWindow*)w;
2038 if (!rects || !count)
2045 path = [NSBezierPath bezierPath];
2046 for (i = 0; i < count; i++)
2047 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
2048 window.shape = path;
2055 /***********************************************************************
2056 * macdrv_set_window_alpha
2058 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
2060 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2061 WineWindow* window = (WineWindow*)w;
2063 [window setAlphaValue:alpha];
2068 /***********************************************************************
2069 * macdrv_set_window_color_key
2071 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
2074 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2075 WineWindow* window = (WineWindow*)w;
2078 window.colorKeyed = TRUE;
2079 window.colorKeyRed = keyRed;
2080 window.colorKeyGreen = keyGreen;
2081 window.colorKeyBlue = keyBlue;
2082 [window checkTransparency];
2088 /***********************************************************************
2089 * macdrv_clear_window_color_key
2091 void macdrv_clear_window_color_key(macdrv_window w)
2093 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2094 WineWindow* window = (WineWindow*)w;
2097 window.colorKeyed = FALSE;
2098 [window checkTransparency];
2104 /***********************************************************************
2105 * macdrv_window_use_per_pixel_alpha
2107 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
2109 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2110 WineWindow* window = (WineWindow*)w;
2113 window.usePerPixelAlpha = use_per_pixel_alpha;
2114 [window checkTransparency];
2120 /***********************************************************************
2121 * macdrv_give_cocoa_window_focus
2123 * Makes the Cocoa window "key" (gives it keyboard focus). This also
2124 * orders it front and, if its frame was not within the desktop bounds,
2125 * Cocoa will typically move it on-screen.
2127 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
2129 WineWindow* window = (WineWindow*)w;
2132 [window makeFocused:activate];
2136 /***********************************************************************
2137 * macdrv_set_window_min_max_sizes
2139 * Sets the window's minimum and maximum content sizes.
2141 void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
2143 WineWindow* window = (WineWindow*)w;
2146 [window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)];
2150 /***********************************************************************
2151 * macdrv_create_view
2153 * Creates and returns a view in the specified rect of the window. The
2154 * caller is responsible for calling macdrv_dispose_view() on the view
2155 * when it is done with it.
2157 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
2159 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2160 WineWindow* window = (WineWindow*)w;
2161 __block WineContentView* view;
2163 if (CGRectIsNull(rect)) rect = CGRectZero;
2166 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2168 view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
2169 [view setAutoresizesSubviews:NO];
2170 [nc addObserver:view
2171 selector:@selector(updateGLContexts)
2172 name:NSViewGlobalFrameDidChangeNotification
2174 [nc addObserver:view
2175 selector:@selector(updateGLContexts)
2176 name:NSApplicationDidChangeScreenParametersNotification
2178 [[window contentView] addSubview:view];
2179 [window updateColorSpace];
2183 return (macdrv_view)view;
2186 /***********************************************************************
2187 * macdrv_dispose_view
2189 * Destroys a view previously returned by macdrv_create_view.
2191 void macdrv_dispose_view(macdrv_view v)
2193 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2194 WineContentView* view = (WineContentView*)v;
2197 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2198 WineWindow* window = (WineWindow*)[view window];
2200 [nc removeObserver:view
2201 name:NSViewGlobalFrameDidChangeNotification
2203 [nc removeObserver:view
2204 name:NSApplicationDidChangeScreenParametersNotification
2206 [view removeFromSuperview];
2208 [window updateColorSpace];
2214 /***********************************************************************
2215 * macdrv_set_view_window_and_frame
2217 * Move a view to a new window and/or position within its window. If w
2218 * is NULL, leave the view in its current window and just change its
2221 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
2223 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2224 WineContentView* view = (WineContentView*)v;
2225 WineWindow* window = (WineWindow*)w;
2227 if (CGRectIsNull(rect)) rect = CGRectZero;
2230 BOOL changedWindow = (window && window != [view window]);
2231 NSRect newFrame = NSRectFromCGRect(rect);
2232 NSRect oldFrame = [view frame];
2233 BOOL needUpdateWindowColorSpace = FALSE;
2237 WineWindow* oldWindow = (WineWindow*)[view window];
2238 [view removeFromSuperview];
2239 [oldWindow updateColorSpace];
2240 [[window contentView] addSubview:view];
2241 needUpdateWindowColorSpace = TRUE;
2244 if (!NSEqualRects(oldFrame, newFrame))
2247 [[view superview] setNeedsDisplayInRect:oldFrame];
2248 if (NSEqualPoints(oldFrame.origin, newFrame.origin))
2249 [view setFrameSize:newFrame.size];
2250 else if (NSEqualSizes(oldFrame.size, newFrame.size))
2251 [view setFrameOrigin:newFrame.origin];
2253 [view setFrame:newFrame];
2254 [view setNeedsDisplay:YES];
2255 needUpdateWindowColorSpace = TRUE;
2258 if (needUpdateWindowColorSpace)
2259 [(WineWindow*)[view window] updateColorSpace];
2265 /***********************************************************************
2266 * macdrv_add_view_opengl_context
2268 * Add an OpenGL context to the list being tracked for each view.
2270 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
2272 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2273 WineContentView* view = (WineContentView*)v;
2274 WineOpenGLContext *context = (WineOpenGLContext*)c;
2276 OnMainThreadAsync(^{
2277 [view addGLContext:context];
2283 /***********************************************************************
2284 * macdrv_remove_view_opengl_context
2286 * Add an OpenGL context to the list being tracked for each view.
2288 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
2290 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2291 WineContentView* view = (WineContentView*)v;
2292 WineOpenGLContext *context = (WineOpenGLContext*)c;
2294 OnMainThreadAsync(^{
2295 [view removeGLContext:context];
2301 /***********************************************************************
2302 * macdrv_window_background_color
2304 * Returns the standard Mac window background color as a 32-bit value of
2305 * the form 0x00rrggbb.
2307 uint32_t macdrv_window_background_color(void)
2309 static uint32_t result;
2310 static dispatch_once_t once;
2312 // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
2313 // color spaces (RGB or grayscale). So, the only way to get RGB values out
2314 // of it is to draw with it.
2315 dispatch_once(&once, ^{
2317 unsigned char rgbx[4];
2318 unsigned char *planes = rgbx;
2319 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
2326 colorSpaceName:NSCalibratedRGBColorSpace
2330 [NSGraphicsContext saveGraphicsState];
2331 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
2332 [[NSColor windowBackgroundColor] set];
2333 NSRectFill(NSMakeRect(0, 0, 1, 1));
2334 [NSGraphicsContext restoreGraphicsState];
2336 result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
2343 /***********************************************************************
2344 * macdrv_send_text_input_event
2346 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
2351 WineWindow* window = (WineWindow*)[NSApp keyWindow];
2352 if (![window isKindOfClass:[WineWindow class]])
2354 window = (WineWindow*)[NSApp mainWindow];
2355 if (![window isKindOfClass:[WineWindow class]])
2356 window = [[WineApplicationController sharedController] frontWineWindow];
2361 NSUInteger localFlags = flags;
2365 window.imeData = data;
2366 fix_device_modifiers_by_generic(&localFlags);
2368 // An NSEvent created with +keyEventWithType:... is internally marked
2369 // as synthetic and doesn't get sent through input methods. But one
2370 // created from a CGEvent doesn't have that problem.
2371 c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
2372 CGEventSetFlags(c, localFlags);
2373 CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
2374 event = [NSEvent eventWithCGEvent:c];
2377 window.commandDone = FALSE;
2378 ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;