winemac: Allow the user to attempt to resize a maximized window and try to restore...
[wine.git] / dlls / winemac.drv / cocoa_window.m
blob1bb99a956abea25f4a3fc7eb38bae722b52643cf
1 /*
2  * MACDRV Cocoa window code
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
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.
10  *
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.
15  *
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
19  */
21 #import <Carbon/Carbon.h>
23 #import "cocoa_window.h"
25 #include "macdrv_cocoa.h"
26 #import "cocoa_app.h"
27 #import "cocoa_event.h"
28 #import "cocoa_opengl.h"
31 #if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
32 enum {
33     NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
34     NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8,
35     NSWindowFullScreenButton = 7,
36     NSFullScreenWindowMask = 1 << 14,
39 @interface NSWindow (WineFullScreenExtensions)
40     - (void) toggleFullScreen:(id)sender;
41 @end
42 #endif
45 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
46 enum {
47     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
51 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
53     NSUInteger style_mask;
55     if (wf->title_bar)
56     {
57         style_mask = NSTitledWindowMask;
58         if (wf->close_button) style_mask |= NSClosableWindowMask;
59         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
60         if (wf->resizable || wf->maximize_button) style_mask |= NSResizableWindowMask;
61         if (wf->utility) style_mask |= NSUtilityWindowMask;
62     }
63     else style_mask = NSBorderlessWindowMask;
65     return style_mask;
69 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
71     NSScreen* screen;
72     for (screen in screens)
73     {
74         if (NSIntersectsRect(frame, [screen frame]))
75             return TRUE;
76     }
77     return FALSE;
81 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
83     for (NSScreen* screen in screens)
84     {
85         if (NSContainsRect(rect, [screen frame]))
86             return screen;
87     }
88     return nil;
92 /* We rely on the supposedly device-dependent modifier flags to distinguish the
93    keys on the left side of the keyboard from those on the right.  Some event
94    sources don't set those device-depdendent flags.  If we see a device-independent
95    flag for a modifier without either corresponding device-dependent flag, assume
96    the left one. */
97 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
99     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
100         *modifiers |= NX_DEVICELCMDKEYMASK;
101     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
102         *modifiers |= NX_DEVICELSHIFTKEYMASK;
103     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
104         *modifiers |= NX_DEVICELCTLKEYMASK;
105     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
106         *modifiers |= NX_DEVICELALTKEYMASK;
109 /* As we manipulate individual bits of a modifier mask, we can end up with
110    inconsistent sets of flags.  In particular, we might set or clear one of the
111    left/right-specific bits, but not the corresponding non-side-specific bit.
112    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
113    otherwise clear it. */
114 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
116     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
117         *modifiers |= NX_COMMANDMASK;
118     else
119         *modifiers &= ~NX_COMMANDMASK;
120     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
121         *modifiers |= NX_SHIFTMASK;
122     else
123         *modifiers &= ~NX_SHIFTMASK;
124     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
125         *modifiers |= NX_CONTROLMASK;
126     else
127         *modifiers &= ~NX_CONTROLMASK;
128     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
129         *modifiers |= NX_ALTERNATEMASK;
130     else
131         *modifiers &= ~NX_ALTERNATEMASK;
134 static inline NSUInteger adjusted_modifiers_for_option_behavior(NSUInteger modifiers)
136     fix_device_modifiers_by_generic(&modifiers);
137     if (left_option_is_alt && (modifiers & NX_DEVICELALTKEYMASK))
138     {
139         modifiers |= NX_DEVICELCMDKEYMASK;
140         modifiers &= ~NX_DEVICELALTKEYMASK;
141     }
142     if (right_option_is_alt && (modifiers & NX_DEVICERALTKEYMASK))
143     {
144         modifiers |= NX_DEVICERCMDKEYMASK;
145         modifiers &= ~NX_DEVICERALTKEYMASK;
146     }
147     fix_generic_modifiers_by_device(&modifiers);
149     return modifiers;
153 @interface WineContentView : NSView <NSTextInputClient>
155     NSMutableArray* glContexts;
156     NSMutableArray* pendingGlContexts;
157     BOOL clearedGlSurface;
159     NSMutableAttributedString* markedText;
160     NSRange markedTextSelection;
163     - (void) addGLContext:(WineOpenGLContext*)context;
164     - (void) removeGLContext:(WineOpenGLContext*)context;
165     - (void) updateGLContexts;
167 @end
170 @interface WineWindow ()
172 @property (readwrite, nonatomic) BOOL disabled;
173 @property (readwrite, nonatomic) BOOL noActivate;
174 @property (readwrite, nonatomic) BOOL floating;
175 @property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose;
176 @property (retain, nonatomic) NSWindow* latentParentWindow;
178 @property (nonatomic) void* hwnd;
179 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
181 @property (nonatomic) void* surface;
182 @property (nonatomic) pthread_mutex_t* surface_mutex;
184 @property (copy, nonatomic) NSBezierPath* shape;
185 @property (copy, nonatomic) NSData* shapeData;
186 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
187 @property (readonly, nonatomic) BOOL needsTransparency;
189 @property (nonatomic) BOOL colorKeyed;
190 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
191 @property (nonatomic) BOOL usePerPixelAlpha;
193 @property (assign, nonatomic) void* imeData;
194 @property (nonatomic) BOOL commandDone;
196 @property (retain, nonatomic) NSTimer* liveResizeDisplayTimer;
198 @property (readonly, copy, nonatomic) NSArray* childWineWindows;
200     - (void) updateColorSpace;
202     - (BOOL) becameEligibleParentOrChild;
203     - (void) becameIneligibleChild;
205 @end
208 @implementation WineContentView
210     - (void) dealloc
211     {
212         [markedText release];
213         [glContexts release];
214         [pendingGlContexts release];
215         [super dealloc];
216     }
218     - (BOOL) isFlipped
219     {
220         return YES;
221     }
223     - (void) drawRect:(NSRect)rect
224     {
225         WineWindow* window = (WineWindow*)[self window];
227         for (WineOpenGLContext* context in pendingGlContexts)
228         {
229             if (!clearedGlSurface)
230             {
231                 context.shouldClearToBlack = TRUE;
232                 clearedGlSurface = TRUE;
233             }
234             context.needsUpdate = TRUE;
235         }
236         [glContexts addObjectsFromArray:pendingGlContexts];
237         [pendingGlContexts removeAllObjects];
239         if ([window contentView] != self)
240             return;
242         if (window.shapeChangedSinceLastDraw && window.shape && !window.colorKeyed && !window.usePerPixelAlpha)
243         {
244             [[NSColor clearColor] setFill];
245             NSRectFill(rect);
247             [window.shape addClip];
249             [[NSColor windowBackgroundColor] setFill];
250             NSRectFill(rect);
251         }
253         if (window.surface && window.surface_mutex &&
254             !pthread_mutex_lock(window.surface_mutex))
255         {
256             const CGRect* rects;
257             int count;
259             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
260             {
261                 CGContextRef context;
262                 int i;
264                 [window.shape addClip];
266                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
267                 CGContextSetBlendMode(context, kCGBlendModeCopy);
268                 CGContextSetInterpolationQuality(context, kCGInterpolationNone);
270                 for (i = 0; i < count; i++)
271                 {
272                     CGRect imageRect;
273                     CGImageRef image;
275                     imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
276                     image = create_surface_image(window.surface, &imageRect, FALSE);
278                     if (image)
279                     {
280                         if (window.colorKeyed)
281                         {
282                             CGImageRef maskedImage;
283                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
284                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
285                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
286                             maskedImage = CGImageCreateWithMaskingColors(image, components);
287                             if (maskedImage)
288                             {
289                                 CGImageRelease(image);
290                                 image = maskedImage;
291                             }
292                         }
294                         CGContextDrawImage(context, imageRect, image);
296                         CGImageRelease(image);
297                     }
298                 }
299             }
301             pthread_mutex_unlock(window.surface_mutex);
302         }
304         // If the window may be transparent, then we have to invalidate the
305         // shadow every time we draw.  Also, if this is the first time we've
306         // drawn since changing from transparent to opaque.
307         if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw)
308         {
309             window.shapeChangedSinceLastDraw = FALSE;
310             [window invalidateShadow];
311         }
312     }
314     - (void) addGLContext:(WineOpenGLContext*)context
315     {
316         if (!glContexts)
317             glContexts = [[NSMutableArray alloc] init];
318         if (!pendingGlContexts)
319             pendingGlContexts = [[NSMutableArray alloc] init];
321         if ([[self window] windowNumber] > 0 && !NSIsEmptyRect([self visibleRect]))
322         {
323             [glContexts addObject:context];
324             if (!clearedGlSurface)
325             {
326                 context.shouldClearToBlack = TRUE;
327                 clearedGlSurface = TRUE;
328             }
329             context.needsUpdate = TRUE;
330         }
331         else
332         {
333             [pendingGlContexts addObject:context];
334             [self setNeedsDisplay:YES];
335         }
337         [(WineWindow*)[self window] updateColorSpace];
338     }
340     - (void) removeGLContext:(WineOpenGLContext*)context
341     {
342         [glContexts removeObjectIdenticalTo:context];
343         [pendingGlContexts removeObjectIdenticalTo:context];
344         [(WineWindow*)[self window] updateColorSpace];
345     }
347     - (void) updateGLContexts
348     {
349         for (WineOpenGLContext* context in glContexts)
350             context.needsUpdate = TRUE;
351     }
353     - (BOOL) hasGLContext
354     {
355         return [glContexts count] || [pendingGlContexts count];
356     }
358     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
359     {
360         return YES;
361     }
363     - (BOOL) preservesContentDuringLiveResize
364     {
365         // Returning YES from this tells Cocoa to keep our view's content during
366         // a Cocoa-driven resize.  In theory, we're also supposed to override
367         // -setFrameSize: to mark exposed sections as needing redisplay, but
368         // user32 will take care of that in a roundabout way.  This way, we don't
369         // redraw until the window surface is flushed.
370         //
371         // This doesn't do anything when we resize the window ourselves.
372         return YES;
373     }
375     - (BOOL)acceptsFirstResponder
376     {
377         return [[self window] contentView] == self;
378     }
380     - (BOOL) mouseDownCanMoveWindow
381     {
382         return NO;
383     }
385     - (void) completeText:(NSString*)text
386     {
387         macdrv_event* event;
388         WineWindow* window = (WineWindow*)[self window];
390         event = macdrv_create_event(IM_SET_TEXT, window);
391         event->im_set_text.data = [window imeData];
392         event->im_set_text.text = (CFStringRef)[text copy];
393         event->im_set_text.complete = TRUE;
395         [[window queue] postEvent:event];
397         macdrv_release_event(event);
399         [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
400         markedTextSelection = NSMakeRange(0, 0);
401         [[self inputContext] discardMarkedText];
402     }
404     - (NSFocusRingType) focusRingType
405     {
406         return NSFocusRingTypeNone;
407     }
409     /*
410      * ---------- NSTextInputClient methods ----------
411      */
412     - (NSTextInputContext*) inputContext
413     {
414         if (!markedText)
415             markedText = [[NSMutableAttributedString alloc] init];
416         return [super inputContext];
417     }
419     - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
420     {
421         if ([string isKindOfClass:[NSAttributedString class]])
422             string = [string string];
424         if ([string isKindOfClass:[NSString class]])
425             [self completeText:string];
426     }
428     - (void) doCommandBySelector:(SEL)aSelector
429     {
430         [(WineWindow*)[self window] setCommandDone:TRUE];
431     }
433     - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
434     {
435         if ([string isKindOfClass:[NSAttributedString class]])
436             string = [string string];
438         if ([string isKindOfClass:[NSString class]])
439         {
440             macdrv_event* event;
441             WineWindow* window = (WineWindow*)[self window];
443             if (replacementRange.location == NSNotFound)
444                 replacementRange = NSMakeRange(0, [markedText length]);
446             [markedText replaceCharactersInRange:replacementRange withString:string];
447             markedTextSelection = selectedRange;
448             markedTextSelection.location += replacementRange.location;
450             event = macdrv_create_event(IM_SET_TEXT, window);
451             event->im_set_text.data = [window imeData];
452             event->im_set_text.text = (CFStringRef)[[markedText string] copy];
453             event->im_set_text.complete = FALSE;
454             event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length;
456             [[window queue] postEvent:event];
458             macdrv_release_event(event);
460             [[self inputContext] invalidateCharacterCoordinates];
461         }
462     }
464     - (void) unmarkText
465     {
466         [self completeText:nil];
467     }
469     - (NSRange) selectedRange
470     {
471         return markedTextSelection;
472     }
474     - (NSRange) markedRange
475     {
476         NSRange range = NSMakeRange(0, [markedText length]);
477         if (!range.length)
478             range.location = NSNotFound;
479         return range;
480     }
482     - (BOOL) hasMarkedText
483     {
484         return [markedText length] > 0;
485     }
487     - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
488     {
489         if (aRange.location >= [markedText length])
490             return nil;
492         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
493         if (actualRange)
494             *actualRange = aRange;
495         return [markedText attributedSubstringFromRange:aRange];
496     }
498     - (NSArray*) validAttributesForMarkedText
499     {
500         return [NSArray array];
501     }
503     - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
504     {
505         macdrv_query* query;
506         WineWindow* window = (WineWindow*)[self window];
507         NSRect ret;
509         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
511         query = macdrv_create_query();
512         query->type = QUERY_IME_CHAR_RECT;
513         query->window = (macdrv_window)[window retain];
514         query->ime_char_rect.data = [window imeData];
515         query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
517         if ([window.queue query:query timeout:1])
518         {
519             aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
520             ret = NSRectFromCGRect(query->ime_char_rect.rect);
521             [[WineApplicationController sharedController] flipRect:&ret];
522         }
523         else
524             ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
526         macdrv_release_query(query);
528         if (actualRange)
529             *actualRange = aRange;
530         return ret;
531     }
533     - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
534     {
535         return NSNotFound;
536     }
538     - (NSInteger) windowLevel
539     {
540         return [[self window] level];
541     }
543 @end
546 @implementation WineWindow
548     static WineWindow* causing_becomeKeyWindow;
550     @synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue;
551     @synthesize surface, surface_mutex;
552     @synthesize shape, shapeData, shapeChangedSinceLastDraw;
553     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
554     @synthesize usePerPixelAlpha;
555     @synthesize imeData, commandDone;
556     @synthesize liveResizeDisplayTimer;
558     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
559                                  windowFrame:(NSRect)window_frame
560                                         hwnd:(void*)hwnd
561                                        queue:(WineEventQueue*)queue
562     {
563         WineWindow* window;
564         WineContentView* contentView;
565         NSTrackingArea* trackingArea;
566         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
568         [[WineApplicationController sharedController] flipRect:&window_frame];
570         window = [[[self alloc] initWithContentRect:window_frame
571                                           styleMask:style_mask_for_features(wf)
572                                             backing:NSBackingStoreBuffered
573                                               defer:YES] autorelease];
575         if (!window) return nil;
577         /* Standardize windows to eliminate differences between titled and
578            borderless windows and between NSWindow and NSPanel. */
579         [window setHidesOnDeactivate:NO];
580         [window setReleasedWhenClosed:NO];
582         [window setOneShot:YES];
583         [window disableCursorRects];
584         [window setShowsResizeIndicator:NO];
585         [window setHasShadow:wf->shadow];
586         [window setAcceptsMouseMovedEvents:YES];
587         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
588         [window setDelegate:window];
589         window.hwnd = hwnd;
590         window.queue = queue;
591         window->savedContentMinSize = NSZeroSize;
592         window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
593         window->resizable = wf->resizable;
595         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
596                                                                   (NSString*)kUTTypeContent,
597                                                                   nil]];
599         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
600         if (!contentView)
601             return nil;
602         [contentView setAutoresizesSubviews:NO];
604         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
605            because they give us mouse moves in the background. */
606         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
607                                                      options:(NSTrackingMouseMoved |
608                                                               NSTrackingActiveAlways |
609                                                               NSTrackingInVisibleRect)
610                                                        owner:window
611                                                     userInfo:nil] autorelease];
612         if (!trackingArea)
613             return nil;
614         [contentView addTrackingArea:trackingArea];
616         [window setContentView:contentView];
617         [window setInitialFirstResponder:contentView];
619         [nc addObserver:window
620                selector:@selector(updateFullscreen)
621                    name:NSApplicationDidChangeScreenParametersNotification
622                  object:NSApp];
623         [window updateFullscreen];
625         [nc addObserver:window
626                selector:@selector(applicationWillHide)
627                    name:NSApplicationWillHideNotification
628                  object:NSApp];
629         [nc addObserver:window
630                selector:@selector(applicationDidUnhide)
631                    name:NSApplicationDidUnhideNotification
632                  object:NSApp];
634         return window;
635     }
637     - (void) dealloc
638     {
639         [[NSNotificationCenter defaultCenter] removeObserver:self];
640         [liveResizeDisplayTimer invalidate];
641         [liveResizeDisplayTimer release];
642         [queue release];
643         [latentChildWindows release];
644         [latentParentWindow release];
645         [shape release];
646         [shapeData release];
647         [super dealloc];
648     }
650     - (BOOL) preventResizing
651     {
652         BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor];
653         return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping);
654     }
656     - (void) adjustFeaturesForState
657     {
658         NSUInteger style = [self styleMask];
660         if (style & NSClosableWindowMask)
661             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
662         if (style & NSMiniaturizableWindowMask)
663             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
664         if (style & NSResizableWindowMask)
665             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
666         if ([self respondsToSelector:@selector(toggleFullScreen:)])
667         {
668             if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
669                 [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
670         }
672         if ([self preventResizing])
673         {
674             NSSize size = [self contentRectForFrameRect:[self frame]].size;
675             [self setContentMinSize:size];
676             [self setContentMaxSize:size];
677         }
678         else
679         {
680             [self setContentMaxSize:savedContentMaxSize];
681             [self setContentMinSize:savedContentMinSize];
682         }
684         if (allow_immovable_windows || cursor_clipping_locks_windows)
685         {
686             if (allow_immovable_windows && (disabled || maximized))
687                 [self setMovable:NO];
688             else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor])
689                 [self setMovable:NO];
690             else
691                 [self setMovable:YES];
692         }
693     }
695     - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
696     {
697         if ([self respondsToSelector:@selector(toggleFullScreen:)])
698         {
699             NSUInteger style = [self styleMask];
701             if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
702                 style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized)
703             {
704                 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
705                 behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
706             }
707             else
708             {
709                 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
710                 behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
711                 if (style & NSFullScreenWindowMask)
712                     [super toggleFullScreen:nil];
713             }
714         }
716         if (behavior != [self collectionBehavior])
717         {
718             [self setCollectionBehavior:behavior];
719             [self adjustFeaturesForState];
720         }
721     }
723     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
724     {
725         static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
726                                              NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
727         NSUInteger currentStyle = [self styleMask];
728         NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
730         if (newStyle != currentStyle)
731         {
732             NSString* title = [[[self title] copy] autorelease];
733             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
734             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
735             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
736             {
737                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
738                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
739                 // just NSTitledWindowMask, the window buttons should disappear rather
740                 // than just being disabled.  But they don't.  Similarly in reverse.
741                 // The workaround is to also toggle NSClosableWindowMask at the same time.
742                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
743             }
744             [self setStyleMask:newStyle];
746             // -setStyleMask: resets the firstResponder to the window.  Set it
747             // back to the content view.
748             if ([[self contentView] acceptsFirstResponder])
749                 [self makeFirstResponder:[self contentView]];
751             [self adjustFullScreenBehavior:[self collectionBehavior]];
753             if ([[self title] length] == 0 && [title length] > 0)
754                 [self setTitle:title];
755         }
757         resizable = wf->resizable;
758         [self adjustFeaturesForState];
759         [self setHasShadow:wf->shadow];
760     }
762     // Indicates if the window would be visible if the app were not hidden.
763     - (BOOL) wouldBeVisible
764     {
765         return [NSApp isHidden] ? savedVisibleState : [self isVisible];
766     }
768     - (BOOL) isOrderedIn
769     {
770         return [self wouldBeVisible] || [self isMiniaturized];
771     }
773     - (NSInteger) minimumLevelForActive:(BOOL)active
774     {
775         NSInteger level;
777         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
778                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
779             level = NSFloatingWindowLevel;
780         else
781             level = NSNormalWindowLevel;
783         if (active)
784         {
785             BOOL captured;
787             captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
789             if (captured || fullscreen)
790             {
791                 if (captured)
792                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
793                 else
794                     level = NSStatusWindowLevel + 1;
796                 if (self.floating)
797                     level++;
798             }
799         }
801         return level;
802     }
804     - (void) postDidUnminimizeEvent
805     {
806         macdrv_event* event;
808         /* Coalesce events by discarding any previous ones still in the queue. */
809         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
810                                forWindow:self];
812         event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
813         [queue postEvent:event];
814         macdrv_release_event(event);
815     }
817     - (void) sendResizeStartQuery
818     {
819         macdrv_query* query = macdrv_create_query();
820         query->type = QUERY_RESIZE_START;
821         query->window = (macdrv_window)[self retain];
823         [self.queue query:query timeout:0.3];
824         macdrv_release_query(query);
825     }
827     - (void) setMacDrvState:(const struct macdrv_window_state*)state
828     {
829         NSWindowCollectionBehavior behavior;
831         self.disabled = state->disabled;
832         self.noActivate = state->no_activate;
834         if (self.floating != state->floating)
835         {
836             self.floating = state->floating;
837             if (state->floating)
838             {
839                 // Became floating.  If child of non-floating window, make that
840                 // relationship latent.
841                 WineWindow* parent = (WineWindow*)[self parentWindow];
842                 if (parent && !parent.floating)
843                     [self becameIneligibleChild];
844             }
845             else
846             {
847                 // Became non-floating.  If parent of floating children, make that
848                 // relationship latent.
849                 WineWindow* child;
850                 for (child in [self childWineWindows])
851                 {
852                     if (child.floating)
853                         [child becameIneligibleChild];
854                 }
855             }
857             // Check our latent relationships.  If floating status was the only
858             // reason they were latent, then make them active.
859             if ([self isVisible])
860                 [self becameEligibleParentOrChild];
862             [[WineApplicationController sharedController] adjustWindowLevels];
863         }
865         if (state->minimized_valid)
866         {
867             macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE);
869             pendingMinimize = FALSE;
870             if (state->minimized && ![self isMiniaturized])
871             {
872                 if ([self wouldBeVisible])
873                 {
874                     if ([self styleMask] & NSFullScreenWindowMask)
875                     {
876                         [self postDidUnminimizeEvent];
877                         discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE);
878                     }
879                     else
880                     {
881                         [super miniaturize:nil];
882                         discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
883                                    event_mask_for_type(WINDOW_GOT_FOCUS) |
884                                    event_mask_for_type(WINDOW_LOST_FOCUS);
885                     }
886                 }
887                 else
888                     pendingMinimize = TRUE;
889             }
890             else if (!state->minimized && [self isMiniaturized])
891             {
892                 ignore_windowDeminiaturize = TRUE;
893                 [self deminiaturize:nil];
894                 discard |= event_mask_for_type(WINDOW_LOST_FOCUS);
895             }
897             if (discard)
898                 [queue discardEventsMatchingMask:discard forWindow:self];
899         }
901         if (state->maximized != maximized)
902         {
903             maximized = state->maximized;
904             [self adjustFeaturesForState];
906             if (!maximized && [self inLiveResize])
907                 [self sendResizeStartQuery];
908         }
910         behavior = NSWindowCollectionBehaviorDefault;
911         if (state->excluded_by_expose)
912             behavior |= NSWindowCollectionBehaviorTransient;
913         else
914             behavior |= NSWindowCollectionBehaviorManaged;
915         if (state->excluded_by_cycle)
916         {
917             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
918             if ([self isOrderedIn])
919                 [NSApp removeWindowsItem:self];
920         }
921         else
922         {
923             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
924             if ([self isOrderedIn])
925                 [NSApp addWindowsItem:self title:[self title] filename:NO];
926         }
927         [self adjustFullScreenBehavior:behavior];
928     }
930     - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
931     {
932         BOOL reordered = FALSE;
934         if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
935         {
936             if ([self level] > [child level])
937                 [child setLevel:[self level]];
938             [self addChildWindow:child ordered:NSWindowAbove];
939             [latentChildWindows removeObjectIdenticalTo:child];
940             child.latentParentWindow = nil;
941             reordered = TRUE;
942         }
943         else
944         {
945             if (!latentChildWindows)
946                 latentChildWindows = [[NSMutableArray alloc] init];
947             if (![latentChildWindows containsObject:child])
948                 [latentChildWindows addObject:child];
949             child.latentParentWindow = self;
950         }
952         return reordered;
953     }
955     - (BOOL) addChildWineWindow:(WineWindow*)child
956     {
957         return [self addChildWineWindow:child assumeVisible:FALSE];
958     }
960     - (void) removeChildWineWindow:(WineWindow*)child
961     {
962         [self removeChildWindow:child];
963         if (child.latentParentWindow == self)
964             child.latentParentWindow = nil;
965         [latentChildWindows removeObjectIdenticalTo:child];
966     }
968     - (BOOL) becameEligibleParentOrChild
969     {
970         BOOL reordered = FALSE;
971         NSUInteger count;
973         if (latentParentWindow.floating || !self.floating)
974         {
975             // If we aren't visible currently, we assume that we should be and soon
976             // will be.  So, if the latent parent is visible that's enough to assume
977             // we can establish the parent-child relationship in Cocoa.  That will
978             // actually make us visible, which is fine.
979             if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
980                 reordered = TRUE;
981         }
983         // Here, though, we may not actually be visible yet and adding a child
984         // won't make us visible.  The caller will have to call this method
985         // again after actually making us visible.
986         if ([self isVisible] && (count = [latentChildWindows count]))
987         {
988             NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
989             NSUInteger i;
991             for (i = 0; i < count; i++)
992             {
993                 WineWindow* child = [latentChildWindows objectAtIndex:i];
994                 if ([child isVisible] && (self.floating || !child.floating))
995                 {
996                     if (child.latentParentWindow == self)
997                     {
998                         if ([self level] > [child level])
999                             [child setLevel:[self level]];
1000                         [self addChildWindow:child ordered:NSWindowAbove];
1001                         child.latentParentWindow = nil;
1002                         reordered = TRUE;
1003                     }
1004                     else
1005                         ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
1006                     [indexesToRemove addIndex:i];
1007                 }
1008             }
1010             [latentChildWindows removeObjectsAtIndexes:indexesToRemove];
1011         }
1013         return reordered;
1014     }
1016     - (void) becameIneligibleChild
1017     {
1018         WineWindow* parent = (WineWindow*)[self parentWindow];
1019         if (parent)
1020         {
1021             if (!parent->latentChildWindows)
1022                 parent->latentChildWindows = [[NSMutableArray alloc] init];
1023             [parent->latentChildWindows insertObject:self atIndex:0];
1024             self.latentParentWindow = parent;
1025             [parent removeChildWindow:self];
1026         }
1027     }
1029     - (void) becameIneligibleParentOrChild
1030     {
1031         NSArray* childWindows = [self childWineWindows];
1033         [self becameIneligibleChild];
1035         if ([childWindows count])
1036         {
1037             WineWindow* child;
1039             for (child in childWindows)
1040             {
1041                 child.latentParentWindow = self;
1042                 [self removeChildWindow:child];
1043             }
1045             if (latentChildWindows)
1046                 [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
1047             else
1048                 latentChildWindows = [childWindows mutableCopy];
1049         }
1050     }
1052     // Determine if, among Wine windows, this window is directly above or below
1053     // a given other Wine window with no other Wine window intervening.
1054     // Intervening non-Wine windows are ignored.
1055     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
1056     {
1057         NSNumber* windowNumber;
1058         NSNumber* otherWindowNumber;
1059         NSArray* windowNumbers;
1060         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
1062         if (![self isVisible] || ![otherWindow isVisible])
1063             return FALSE;
1065         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
1066         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
1067         windowNumbers = [[self class] windowNumbersWithOptions:0];
1068         windowIndex = [windowNumbers indexOfObject:windowNumber];
1069         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
1071         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
1072             return FALSE;
1074         if (orderingMode == NSWindowAbove)
1075         {
1076             lowIndex = windowIndex;
1077             highIndex = otherWindowIndex;
1078         }
1079         else if (orderingMode == NSWindowBelow)
1080         {
1081             lowIndex = otherWindowIndex;
1082             highIndex = windowIndex;
1083         }
1084         else
1085             return FALSE;
1087         if (highIndex <= lowIndex)
1088             return FALSE;
1090         for (i = lowIndex + 1; i < highIndex; i++)
1091         {
1092             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
1093             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
1094             if ([interveningWindow isKindOfClass:[WineWindow class]])
1095                 return FALSE;
1096         }
1098         return TRUE;
1099     }
1101     - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
1102     {
1103         NSMutableArray* windowNumbers;
1104         NSNumber* childWindowNumber;
1105         NSUInteger otherIndex, limit;
1106         NSArray* origChildren;
1107         NSMutableArray* children;
1109         // Get the z-order from the window server and modify it to reflect the
1110         // requested window ordering.
1111         windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
1112         childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
1113         [windowNumbers removeObject:childWindowNumber];
1114         otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
1115         [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
1117         // Get our child windows and sort them in the reverse of the desired
1118         // z-order (back-to-front).
1119         origChildren = [self childWineWindows];
1120         children = [[origChildren mutableCopy] autorelease];
1121         [children sortWithOptions:NSSortStable
1122                   usingComparator:^NSComparisonResult(id obj1, id obj2){
1123             NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]];
1124             NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]];
1125             NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
1126             NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
1127             if (index1 == NSNotFound)
1128             {
1129                 if (index2 == NSNotFound)
1130                     return NSOrderedSame;
1131                 else
1132                     return NSOrderedAscending;
1133             }
1134             else if (index2 == NSNotFound)
1135                 return NSOrderedDescending;
1136             else if (index1 < index2)
1137                 return NSOrderedDescending;
1138             else if (index2 < index1)
1139                 return NSOrderedAscending;
1141             return NSOrderedSame;
1142         }];
1144         // If the current and desired children arrays match up to a point, leave
1145         // those matching children alone.
1146         limit = MIN([origChildren count], [children count]);
1147         for (otherIndex = 0; otherIndex < limit; otherIndex++)
1148         {
1149             if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex])
1150                 break;
1151         }
1152         [children removeObjectsInRange:NSMakeRange(0, otherIndex)];
1154         // Remove all of the child windows and re-add them back-to-front so they
1155         // are in the desired order.
1156         for (other in children)
1157             [self removeChildWindow:other];
1158         for (other in children)
1159             [self addChildWindow:other ordered:NSWindowAbove];
1160     }
1162     /* Returns whether or not the window was ordered in, which depends on if
1163        its frame intersects any screen. */
1164     - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
1165     {
1166         WineApplicationController* controller = [WineApplicationController sharedController];
1167         if (![self isMiniaturized])
1168         {
1169             BOOL needAdjustWindowLevels = FALSE;
1170             BOOL wasVisible;
1172             [controller transformProcessToForeground];
1173             [NSApp unhide:nil];
1174             wasVisible = [self isVisible];
1176             if (activate)
1177                 [NSApp activateIgnoringOtherApps:YES];
1179             NSDisableScreenUpdates();
1181             if ([self becameEligibleParentOrChild])
1182                 needAdjustWindowLevels = TRUE;
1184             if (prev || next)
1185             {
1186                 WineWindow* other = [prev isVisible] ? prev : next;
1187                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
1189                 if (![self isOrdered:orderingMode relativeTo:other])
1190                 {
1191                     WineWindow* parent = (WineWindow*)[self parentWindow];
1192                     WineWindow* otherParent = (WineWindow*)[other parentWindow];
1194                     // This window level may not be right for this window based
1195                     // on floating-ness, fullscreen-ness, etc.  But we set it
1196                     // temporarily to allow us to order the windows properly.
1197                     // Then the levels get fixed by -adjustWindowLevels.
1198                     if ([self level] != [other level])
1199                         [self setLevel:[other level]];
1200                     [self orderWindow:orderingMode relativeTo:[other windowNumber]];
1202                     // The above call to -[NSWindow orderWindow:relativeTo:] won't
1203                     // reorder windows which are both children of the same parent
1204                     // relative to each other, so do that separately.
1205                     if (parent && parent == otherParent)
1206                         [parent order:orderingMode childWindow:self relativeTo:other];
1208                     needAdjustWindowLevels = TRUE;
1209                 }
1210             }
1211             else
1212             {
1213                 // Again, temporarily set level to make sure we can order to
1214                 // the right place.
1215                 next = [controller frontWineWindow];
1216                 if (next && [self level] < [next level])
1217                     [self setLevel:[next level]];
1218                 [self orderFront:nil];
1219                 needAdjustWindowLevels = TRUE;
1220             }
1222             if ([self becameEligibleParentOrChild])
1223                 needAdjustWindowLevels = TRUE;
1225             if (needAdjustWindowLevels)
1226             {
1227                 if (!wasVisible && fullscreen && [self isOnActiveSpace])
1228                     [controller updateFullscreenWindows];
1229                 [controller adjustWindowLevels];
1230             }
1232             if (pendingMinimize)
1233             {
1234                 [super miniaturize:nil];
1235                 pendingMinimize = FALSE;
1236             }
1238             NSEnableScreenUpdates();
1240             /* Cocoa may adjust the frame when the window is ordered onto the screen.
1241                Generate a frame-changed event just in case.  The back end will ignore
1242                it if nothing actually changed. */
1243             [self windowDidResize:nil];
1245             if (![self isExcludedFromWindowsMenu])
1246                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1247         }
1248     }
1250     - (void) doOrderOut
1251     {
1252         WineApplicationController* controller = [WineApplicationController sharedController];
1253         BOOL wasVisible = [self isVisible];
1254         BOOL wasOnActiveSpace = [self isOnActiveSpace];
1256         if ([self isMiniaturized])
1257             pendingMinimize = TRUE;
1259         [self becameIneligibleParentOrChild];
1260         if ([self isMiniaturized])
1261         {
1262             fakingClose = TRUE;
1263             [self close];
1264             fakingClose = FALSE;
1265         }
1266         else
1267             [self orderOut:nil];
1268         savedVisibleState = FALSE;
1269         if (wasVisible && wasOnActiveSpace && fullscreen)
1270             [controller updateFullscreenWindows];
1271         [controller adjustWindowLevels];
1272         [NSApp removeWindowsItem:self];
1274         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1275                                          event_mask_for_type(WINDOW_GOT_FOCUS) |
1276                                          event_mask_for_type(WINDOW_LOST_FOCUS) |
1277                                          event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
1278                                          event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
1279                                          event_mask_for_type(WINDOW_RESTORE_REQUESTED)
1280                                forWindow:self];
1281     }
1283     - (void) updateFullscreen
1284     {
1285         NSRect contentRect = [self contentRectForFrameRect:[self frame]];
1286         BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
1288         if (nowFullscreen != fullscreen)
1289         {
1290             WineApplicationController* controller = [WineApplicationController sharedController];
1292             fullscreen = nowFullscreen;
1293             if ([self isVisible] && [self isOnActiveSpace])
1294                 [controller updateFullscreenWindows];
1296             [controller adjustWindowLevels];
1297         }
1298     }
1300     - (void) setFrameFromWine:(NSRect)contentRect
1301     {
1302         /* Origin is (left, top) in a top-down space.  Need to convert it to
1303            (left, bottom) in a bottom-up space. */
1304         [[WineApplicationController sharedController] flipRect:&contentRect];
1306         /* The back end is establishing a new window size and position.  It's
1307            not interested in any stale events regarding those that may be sitting
1308            in the queue. */
1309         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1310                                forWindow:self];
1312         if (!NSIsEmptyRect(contentRect))
1313         {
1314             NSRect frame, oldFrame;
1316             oldFrame = [self frame];
1317             frame = [self frameRectForContentRect:contentRect];
1318             if (!NSEqualRects(frame, oldFrame))
1319             {
1320                 BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size);
1321                 BOOL needEnableScreenUpdates = FALSE;
1323                 if ([self preventResizing])
1324                 {
1325                     // Allow the following calls to -setFrame:display: to work even
1326                     // if they would violate the content size constraints. This
1327                     // shouldn't be necessary since the content size constraints are
1328                     // documented to not constrain that method, but it seems to be.
1329                     [self setContentMinSize:NSZeroSize];
1330                     [self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
1331                 }
1333                 if (equalSizes && [[self childWineWindows] count])
1334                 {
1335                     // If we change the window frame such that the origin moves
1336                     // but the size doesn't change, then Cocoa moves child
1337                     // windows with the parent.  We don't want that so we fake
1338                     // a change of the size and then change it back.
1339                     NSRect bogusFrame = frame;
1340                     bogusFrame.size.width++;
1342                     NSDisableScreenUpdates();
1343                     needEnableScreenUpdates = TRUE;
1345                     ignore_windowResize = TRUE;
1346                     [self setFrame:bogusFrame display:NO];
1347                     ignore_windowResize = FALSE;
1348                 }
1350                 [self setFrame:frame display:YES];
1351                 if ([self preventResizing])
1352                 {
1353                     [self setContentMinSize:contentRect.size];
1354                     [self setContentMaxSize:contentRect.size];
1355                 }
1357                 if (needEnableScreenUpdates)
1358                     NSEnableScreenUpdates();
1360                 if (!equalSizes)
1361                     [self updateColorSpace];
1363                 if (!enteringFullScreen &&
1364                     [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
1365                     nonFullscreenFrame = frame;
1367                 [self updateFullscreen];
1369                 if ([self isOrderedIn])
1370                 {
1371                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
1372                        event.  The back end will ignore it if nothing actually changed. */
1373                     [self windowDidResize:nil];
1374                 }
1375             }
1376         }
1377     }
1379     - (void) setMacDrvParentWindow:(WineWindow*)parent
1380     {
1381         WineWindow* oldParent = (WineWindow*)[self parentWindow];
1382         if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
1383         {
1384             [oldParent removeChildWineWindow:self];
1385             [latentParentWindow removeChildWineWindow:self];
1386             if ([parent addChildWineWindow:self])
1387                 [[WineApplicationController sharedController] adjustWindowLevels];
1388         }
1389     }
1391     - (void) setDisabled:(BOOL)newValue
1392     {
1393         if (disabled != newValue)
1394         {
1395             disabled = newValue;
1396             [self adjustFeaturesForState];
1397         }
1398     }
1400     - (BOOL) needsTransparency
1401     {
1402         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
1403     }
1405     - (void) checkTransparency
1406     {
1407         if (![self isOpaque] && !self.needsTransparency)
1408         {
1409             self.shapeChangedSinceLastDraw = TRUE;
1410             [[self contentView] setNeedsDisplay:YES];
1411             [self setBackgroundColor:[NSColor windowBackgroundColor]];
1412             [self setOpaque:YES];
1413         }
1414         else if ([self isOpaque] && self.needsTransparency)
1415         {
1416             self.shapeChangedSinceLastDraw = TRUE;
1417             [[self contentView] setNeedsDisplay:YES];
1418             [self setBackgroundColor:[NSColor clearColor]];
1419             [self setOpaque:NO];
1420         }
1421     }
1423     - (void) setShape:(NSBezierPath*)newShape
1424     {
1425         if (shape == newShape) return;
1427         if (shape)
1428         {
1429             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
1430             [shape release];
1431         }
1432         if (newShape)
1433             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
1435         shape = [newShape copy];
1436         self.shapeChangedSinceLastDraw = TRUE;
1438         [self checkTransparency];
1439     }
1441     - (void) setLiveResizeDisplayTimer:(NSTimer*)newTimer
1442     {
1443         if (newTimer != liveResizeDisplayTimer)
1444         {
1445             [liveResizeDisplayTimer invalidate];
1446             [liveResizeDisplayTimer release];
1447             liveResizeDisplayTimer = [newTimer retain];
1448         }
1449     }
1451     - (void) makeFocused:(BOOL)activate
1452     {
1453         if (activate)
1454         {
1455             [[WineApplicationController sharedController] transformProcessToForeground];
1456             [NSApp activateIgnoringOtherApps:YES];
1457         }
1459         causing_becomeKeyWindow = self;
1460         [self makeKeyWindow];
1461         causing_becomeKeyWindow = nil;
1463         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
1464                                          event_mask_for_type(WINDOW_LOST_FOCUS)
1465                                forWindow:self];
1466     }
1468     - (void) postKey:(uint16_t)keyCode
1469              pressed:(BOOL)pressed
1470            modifiers:(NSUInteger)modifiers
1471                event:(NSEvent*)theEvent
1472     {
1473         macdrv_event* event;
1474         CGEventRef cgevent;
1475         WineApplicationController* controller = [WineApplicationController sharedController];
1477         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
1478         event->key.keycode   = keyCode;
1479         event->key.modifiers = modifiers;
1480         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
1482         if ((cgevent = [theEvent CGEvent]))
1483         {
1484             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
1485                                                         kCGKeyboardEventKeyboardType);
1486             if (keyboardType != controller.keyboardType)
1487             {
1488                 controller.keyboardType = keyboardType;
1489                 [controller keyboardSelectionDidChange];
1490             }
1491         }
1493         [queue postEvent:event];
1495         macdrv_release_event(event);
1497         [controller noteKey:keyCode pressed:pressed];
1498     }
1500     - (void) postKeyEvent:(NSEvent *)theEvent
1501     {
1502         [self flagsChanged:theEvent];
1503         [self postKey:[theEvent keyCode]
1504               pressed:[theEvent type] == NSKeyDown
1505             modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags])
1506                 event:theEvent];
1507     }
1509     - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
1510     {
1511         savedContentMinSize = minSize;
1512         savedContentMaxSize = maxSize;
1513         if (![self preventResizing])
1514         {
1515             [self setContentMinSize:minSize];
1516             [self setContentMaxSize:maxSize];
1517         }
1518     }
1520     - (WineWindow*) ancestorWineWindow
1521     {
1522         WineWindow* ancestor = self;
1523         for (;;)
1524         {
1525             WineWindow* parent = (WineWindow*)[ancestor parentWindow];
1526             if ([parent isKindOfClass:[WineWindow class]])
1527                 ancestor = parent;
1528             else
1529                 break;
1530         }
1531         return ancestor;
1532     }
1534     - (void) postBroughtForwardEvent
1535     {
1536         macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
1537         [queue postEvent:event];
1538         macdrv_release_event(event);
1539     }
1541     - (void) updateForCursorClipping
1542     {
1543         [self adjustFeaturesForState];
1544     }
1547     /*
1548      * ---------- NSWindow method overrides ----------
1549      */
1550     - (BOOL) canBecomeKeyWindow
1551     {
1552         if (causing_becomeKeyWindow == self) return YES;
1553         if (self.disabled || self.noActivate) return NO;
1554         return [self isKeyWindow];
1555     }
1557     - (BOOL) canBecomeMainWindow
1558     {
1559         return [self canBecomeKeyWindow];
1560     }
1562     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1563     {
1564         // If a window is sized to completely cover a screen, then it's in
1565         // full-screen mode.  In that case, we don't allow NSWindow to constrain
1566         // it.
1567         NSArray* screens = [NSScreen screens];
1568         NSRect contentRect = [self contentRectForFrameRect:frameRect];
1569         if (!screen_covered_by_rect(contentRect, screens) &&
1570             frame_intersects_screens(frameRect, screens))
1571             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1572         return frameRect;
1573     }
1575     - (BOOL) isExcludedFromWindowsMenu
1576     {
1577         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1578     }
1580     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1581     {
1582         BOOL ret = [super validateMenuItem:menuItem];
1584         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1585             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1586         if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized))
1587             ret = NO;
1589         return ret;
1590     }
1592     /* We don't call this.  It's the action method of the items in the Window menu. */
1593     - (void) makeKeyAndOrderFront:(id)sender
1594     {
1595         if ([self isMiniaturized])
1596             [self deminiaturize:nil];
1597         [self orderBelow:nil orAbove:nil activate:NO];
1598         [[self ancestorWineWindow] postBroughtForwardEvent];
1600         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1601             [[WineApplicationController sharedController] windowGotFocus:self];
1602     }
1604     - (void) sendEvent:(NSEvent*)event
1605     {
1606         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1607            interface control.  For example, Control-Tab switches focus among
1608            views.  We want to bypass that feature, so directly route key-down
1609            events to -keyDown:. */
1610         if ([event type] == NSKeyDown)
1611             [[self firstResponder] keyDown:event];
1612         else
1613             [super sendEvent:event];
1614     }
1616     - (void) miniaturize:(id)sender
1617     {
1618         macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
1619         [queue postEvent:event];
1620         macdrv_release_event(event);
1621     }
1623     - (void) toggleFullScreen:(id)sender
1624     {
1625         if (!self.disabled && !maximized)
1626             [super toggleFullScreen:sender];
1627     }
1629     - (NSArray*) childWineWindows
1630     {
1631         NSArray* childWindows = self.childWindows;
1632         NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){
1633             return [child isKindOfClass:[WineWindow class]];
1634         }];
1635         return [childWindows objectsAtIndexes:indexes];
1636     }
1638     // We normally use the generic/calibrated RGB color space for the window,
1639     // rather than the device color space, to avoid expensive color conversion
1640     // which slows down drawing.  However, for windows displaying OpenGL, having
1641     // a different color space than the screen greatly reduces frame rates, often
1642     // limiting it to the display refresh rate.
1643     //
1644     // To avoid this, we switch back to the screen color space whenever the
1645     // window is covered by a view with an attached OpenGL context.
1646     - (void) updateColorSpace
1647     {
1648         NSRect contentRect = [[self contentView] frame];
1649         BOOL coveredByGLView = FALSE;
1650         for (WineContentView* view in [[self contentView] subviews])
1651         {
1652             if ([view hasGLContext])
1653             {
1654                 NSRect frame = [view convertRect:[view bounds] toView:nil];
1655                 if (NSContainsRect(frame, contentRect))
1656                 {
1657                     coveredByGLView = TRUE;
1658                     break;
1659                 }
1660             }
1661         }
1663         if (coveredByGLView)
1664             [self setColorSpace:nil];
1665         else
1666             [self setColorSpace:[NSColorSpace genericRGBColorSpace]];
1667     }
1670     /*
1671      * ---------- NSResponder method overrides ----------
1672      */
1673     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1675     - (void) flagsChanged:(NSEvent *)theEvent
1676     {
1677         static const struct {
1678             NSUInteger  mask;
1679             uint16_t    keycode;
1680         } modifiers[] = {
1681             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
1682             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
1683             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
1684             { NX_DEVICELCTLKEYMASK,     kVK_Control },
1685             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
1686             { NX_DEVICELALTKEYMASK,     kVK_Option },
1687             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
1688             { NX_DEVICELCMDKEYMASK,     kVK_Command },
1689             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
1690         };
1692         NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]);
1693         NSUInteger changed;
1694         int i, last_changed;
1696         fix_device_modifiers_by_generic(&modifierFlags);
1697         changed = modifierFlags ^ lastModifierFlags;
1699         last_changed = -1;
1700         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
1701             if (changed & modifiers[i].mask)
1702                 last_changed = i;
1704         for (i = 0; i <= last_changed; i++)
1705         {
1706             if (changed & modifiers[i].mask)
1707             {
1708                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
1710                 if (i == last_changed)
1711                     lastModifierFlags = modifierFlags;
1712                 else
1713                 {
1714                     lastModifierFlags ^= modifiers[i].mask;
1715                     fix_generic_modifiers_by_device(&lastModifierFlags);
1716                 }
1718                 // Caps lock generates one event for each press-release action.
1719                 // We need to simulate a pair of events for each actual event.
1720                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1721                 {
1722                     [self postKey:modifiers[i].keycode
1723                           pressed:TRUE
1724                         modifiers:lastModifierFlags
1725                             event:(NSEvent*)theEvent];
1726                     pressed = FALSE;
1727                 }
1729                 [self postKey:modifiers[i].keycode
1730                       pressed:pressed
1731                     modifiers:lastModifierFlags
1732                         event:(NSEvent*)theEvent];
1733             }
1734         }
1735     }
1737     - (void) applicationWillHide
1738     {
1739         savedVisibleState = [self isVisible];
1740     }
1742     - (void) applicationDidUnhide
1743     {
1744         if ([self isVisible])
1745             [self becameEligibleParentOrChild];
1746     }
1749     /*
1750      * ---------- NSWindowDelegate methods ----------
1751      */
1752     - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
1753     {
1754         macdrv_query* query;
1755         NSSize size;
1757         query = macdrv_create_query();
1758         query->type = QUERY_MIN_MAX_INFO;
1759         query->window = (macdrv_window)[self retain];
1760         [self.queue query:query timeout:0.5];
1761         macdrv_release_query(query);
1763         size = [self contentMaxSize];
1764         if (proposedSize.width < size.width)
1765             size.width = proposedSize.width;
1766         if (proposedSize.height < size.height)
1767             size.height = proposedSize.height;
1768         return size;
1769     }
1771     - (void)windowDidBecomeKey:(NSNotification *)notification
1772     {
1773         WineApplicationController* controller = [WineApplicationController sharedController];
1774         NSEvent* event = [controller lastFlagsChanged];
1775         if (event)
1776             [self flagsChanged:event];
1778         if (causing_becomeKeyWindow == self) return;
1780         [controller windowGotFocus:self];
1781     }
1783     - (void)windowDidDeminiaturize:(NSNotification *)notification
1784     {
1785         WineApplicationController* controller = [WineApplicationController sharedController];
1787         if (!ignore_windowDeminiaturize)
1788             [self postDidUnminimizeEvent];
1789         ignore_windowDeminiaturize = FALSE;
1791         [self becameEligibleParentOrChild];
1793         if (fullscreen && [self isOnActiveSpace])
1794             [controller updateFullscreenWindows];
1795         [controller adjustWindowLevels];
1797         if (![self parentWindow])
1798             [self postBroughtForwardEvent];
1800         if (!self.disabled && !self.noActivate)
1801         {
1802             causing_becomeKeyWindow = self;
1803             [self makeKeyWindow];
1804             causing_becomeKeyWindow = nil;
1805             [controller windowGotFocus:self];
1806         }
1808         [self windowDidResize:notification];
1809     }
1811     - (void) windowDidEndLiveResize:(NSNotification *)notification
1812     {
1813         if (!maximized)
1814         {
1815             macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
1816             [queue postEvent:event];
1817             macdrv_release_event(event);
1818         }
1820         self.liveResizeDisplayTimer = nil;
1821     }
1823     - (void) windowDidEnterFullScreen:(NSNotification*)notification
1824     {
1825         enteringFullScreen = FALSE;
1826         enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
1827     }
1829     - (void) windowDidExitFullScreen:(NSNotification*)notification
1830     {
1831         exitingFullScreen = FALSE;
1832         [self setFrame:nonFullscreenFrame display:YES animate:NO];
1833         [self windowDidResize:nil];
1834     }
1836     - (void) windowDidFailToEnterFullScreen:(NSWindow*)window
1837     {
1838         enteringFullScreen = FALSE;
1839         enteredFullScreenTime = 0;
1840     }
1842     - (void) windowDidFailToExitFullScreen:(NSWindow*)window
1843     {
1844         exitingFullScreen = FALSE;
1845         [self windowDidResize:nil];
1846     }
1848     - (void)windowDidMiniaturize:(NSNotification *)notification
1849     {
1850         if (fullscreen && [self isOnActiveSpace])
1851             [[WineApplicationController sharedController] updateFullscreenWindows];
1852     }
1854     - (void)windowDidMove:(NSNotification *)notification
1855     {
1856         [self windowDidResize:notification];
1857     }
1859     - (void)windowDidResignKey:(NSNotification *)notification
1860     {
1861         macdrv_event* event;
1863         if (causing_becomeKeyWindow) return;
1865         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1866         [queue postEvent:event];
1867         macdrv_release_event(event);
1868     }
1870     - (void)windowDidResize:(NSNotification *)notification
1871     {
1872         macdrv_event* event;
1873         NSRect frame = [self frame];
1875         if ([self inLiveResize])
1876         {
1877             if (NSMinX(frame) != NSMinX(frameAtResizeStart))
1878                 resizingFromLeft = TRUE;
1879             if (NSMaxY(frame) != NSMaxY(frameAtResizeStart))
1880                 resizingFromTop = TRUE;
1881         }
1883         frame = [self contentRectForFrameRect:frame];
1885         if (ignore_windowResize || exitingFullScreen) return;
1887         if ([self preventResizing])
1888         {
1889             [self setContentMinSize:frame.size];
1890             [self setContentMaxSize:frame.size];
1891         }
1893         [[WineApplicationController sharedController] flipRect:&frame];
1895         /* Coalesce events by discarding any previous ones still in the queue. */
1896         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1897                                forWindow:self];
1899         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1900         event->window_frame_changed.frame = NSRectToCGRect(frame);
1901         event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0;
1902         event->window_frame_changed.in_resize = [self inLiveResize];
1903         [queue postEvent:event];
1904         macdrv_release_event(event);
1906         [[[self contentView] inputContext] invalidateCharacterCoordinates];
1907         [self updateFullscreen];
1908     }
1910     - (BOOL)windowShouldClose:(id)sender
1911     {
1912         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1913         [queue postEvent:event];
1914         macdrv_release_event(event);
1915         return NO;
1916     }
1918     - (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
1919     {
1920         if (maximized)
1921         {
1922             macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
1923             [queue postEvent:event];
1924             macdrv_release_event(event);
1925             return NO;
1926         }
1927         else if (!resizable)
1928         {
1929             macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self);
1930             [queue postEvent:event];
1931             macdrv_release_event(event);
1932             return NO;
1933         }
1935         return YES;
1936     }
1938     - (void) windowWillClose:(NSNotification*)notification
1939     {
1940         WineWindow* child;
1942         if (fakingClose) return;
1943         if (latentParentWindow)
1944         {
1945             [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
1946             self.latentParentWindow = nil;
1947         }
1949         for (child in latentChildWindows)
1950         {
1951             if (child.latentParentWindow == self)
1952                 child.latentParentWindow = nil;
1953         }
1954         [latentChildWindows removeAllObjects];
1955     }
1957     - (void) windowWillEnterFullScreen:(NSNotification*)notification
1958     {
1959         enteringFullScreen = TRUE;
1960         nonFullscreenFrame = [self frame];
1961     }
1963     - (void) windowWillExitFullScreen:(NSNotification*)notification
1964     {
1965         exitingFullScreen = TRUE;
1966     }
1968     - (void)windowWillMiniaturize:(NSNotification *)notification
1969     {
1970         [self becameIneligibleParentOrChild];
1971     }
1973     - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
1974     {
1975         if ([self inLiveResize])
1976         {
1977             if (maximized)
1978                 return self.frame.size;
1980             NSRect rect;
1981             macdrv_query* query;
1983             rect = [self frame];
1984             if (resizingFromLeft)
1985                 rect.origin.x = NSMaxX(rect) - frameSize.width;
1986             if (!resizingFromTop)
1987                 rect.origin.y = NSMaxY(rect) - frameSize.height;
1988             rect.size = frameSize;
1989             rect = [self contentRectForFrameRect:rect];
1990             [[WineApplicationController sharedController] flipRect:&rect];
1992             query = macdrv_create_query();
1993             query->type = QUERY_RESIZE_SIZE;
1994             query->window = (macdrv_window)[self retain];
1995             query->resize_size.rect = NSRectToCGRect(rect);
1996             query->resize_size.from_left = resizingFromLeft;
1997             query->resize_size.from_top = resizingFromTop;
1999             if ([self.queue query:query timeout:0.1])
2000             {
2001                 rect = NSRectFromCGRect(query->resize_size.rect);
2002                 rect = [self frameRectForContentRect:rect];
2003                 frameSize = rect.size;
2004             }
2006             macdrv_release_query(query);
2007         }
2009         return frameSize;
2010     }
2012     - (void) windowWillStartLiveResize:(NSNotification *)notification
2013     {
2014         if (maximized)
2015         {
2016             macdrv_event* event;
2017             NSRect frame = [self contentRectForFrameRect:self.frame];
2019             [[WineApplicationController sharedController] flipRect:&frame];
2021             event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2022             event->window_restore_requested.keep_frame = TRUE;
2023             event->window_restore_requested.frame = NSRectToCGRect(frame);
2024             [queue postEvent:event];
2025             macdrv_release_event(event);
2026         }
2027         else
2028             [self sendResizeStartQuery];
2030         frameAtResizeStart = [self frame];
2031         resizingFromLeft = resizingFromTop = FALSE;
2033         // There's a strange restriction in window redrawing during Cocoa-
2034         // managed window resizing.  Only calls to -[NSView setNeedsDisplay...]
2035         // that happen synchronously when Cocoa tells us that our window size
2036         // has changed or asynchronously in a short interval thereafter provoke
2037         // the window to redraw.  Calls to those methods that happen asynchronously
2038         // a half second or more after the last change of the window size aren't
2039         // heeded until the next resize-related user event (e.g. mouse movement).
2040         //
2041         // Wine often has a significant delay between when it's been told that
2042         // the window has changed size and when it can flush completed drawing.
2043         // So, our windows would get stuck with incomplete drawing for as long
2044         // as the user holds the mouse button down and doesn't move it.
2045         //
2046         // We address this by "manually" asking our windows to check if they need
2047         // redrawing every so often (during live resize only).
2048         self.liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
2049                                                                        target:self
2050                                                                      selector:@selector(displayIfNeeded)
2051                                                                      userInfo:nil
2052                                                                       repeats:YES];
2053         [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
2054                                      forMode:NSRunLoopCommonModes];
2055     }
2057     - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
2058     {
2059         macdrv_query* query;
2060         NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
2061         NSSize maxSize;
2063         query = macdrv_create_query();
2064         query->type = QUERY_MIN_MAX_INFO;
2065         query->window = (macdrv_window)[self retain];
2066         [self.queue query:query timeout:0.5];
2067         macdrv_release_query(query);
2069         currentContentRect = [self contentRectForFrameRect:[self frame]];
2070         proposedContentRect = [self contentRectForFrameRect:proposedFrame];
2072         maxSize = [self contentMaxSize];
2073         newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
2074         newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
2076         // Try to keep the top-left corner where it is.
2077         newContentRect.origin.x = NSMinX(currentContentRect);
2078         newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
2080         // If that pushes the bottom or right off the screen, pull it up and to the left.
2081         screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
2082         if (NSMaxX(newContentRect) > NSMaxX(screenRect))
2083             newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
2084         if (NSMinY(newContentRect) < NSMinY(screenRect))
2085             newContentRect.origin.y = NSMinY(screenRect);
2087         // If that pushes the top or left off the screen, push it down and the right
2088         // again.  Do this last because the top-left corner is more important than the
2089         // bottom-right.
2090         if (NSMinX(newContentRect) < NSMinX(screenRect))
2091             newContentRect.origin.x = NSMinX(screenRect);
2092         if (NSMaxY(newContentRect) > NSMaxY(screenRect))
2093             newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
2095         return [self frameRectForContentRect:newContentRect];
2096     }
2099     /*
2100      * ---------- NSPasteboardOwner methods ----------
2101      */
2102     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
2103     {
2104         macdrv_query* query = macdrv_create_query();
2105         query->type = QUERY_PASTEBOARD_DATA;
2106         query->window = (macdrv_window)[self retain];
2107         query->pasteboard_data.type = (CFStringRef)[type copy];
2109         [self.queue query:query timeout:3];
2110         macdrv_release_query(query);
2111     }
2114     /*
2115      * ---------- NSDraggingDestination methods ----------
2116      */
2117     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
2118     {
2119         return [self draggingUpdated:sender];
2120     }
2122     - (void) draggingExited:(id <NSDraggingInfo>)sender
2123     {
2124         // This isn't really a query.  We don't need any response.  However, it
2125         // has to be processed in a similar manner as the other drag-and-drop
2126         // queries in order to maintain the proper order of operations.
2127         macdrv_query* query = macdrv_create_query();
2128         query->type = QUERY_DRAG_EXITED;
2129         query->window = (macdrv_window)[self retain];
2131         [self.queue query:query timeout:0.1];
2132         macdrv_release_query(query);
2133     }
2135     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
2136     {
2137         NSDragOperation ret;
2138         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2139         NSPasteboard* pb = [sender draggingPasteboard];
2141         macdrv_query* query = macdrv_create_query();
2142         query->type = QUERY_DRAG_OPERATION;
2143         query->window = (macdrv_window)[self retain];
2144         query->drag_operation.x = pt.x;
2145         query->drag_operation.y = pt.y;
2146         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
2147         query->drag_operation.accepted_op = NSDragOperationNone;
2148         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
2150         [self.queue query:query timeout:3];
2151         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
2152         macdrv_release_query(query);
2154         return ret;
2155     }
2157     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
2158     {
2159         BOOL ret;
2160         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2161         NSPasteboard* pb = [sender draggingPasteboard];
2163         macdrv_query* query = macdrv_create_query();
2164         query->type = QUERY_DRAG_DROP;
2165         query->window = (macdrv_window)[self retain];
2166         query->drag_drop.x = pt.x;
2167         query->drag_drop.y = pt.y;
2168         query->drag_drop.op = [sender draggingSourceOperationMask];
2169         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
2171         [self.queue query:query timeout:3 * 60 processEvents:YES];
2172         ret = query->status;
2173         macdrv_release_query(query);
2175         return ret;
2176     }
2178     - (BOOL) wantsPeriodicDraggingUpdates
2179     {
2180         return NO;
2181     }
2183 @end
2186 /***********************************************************************
2187  *              macdrv_create_cocoa_window
2189  * Create a Cocoa window with the given content frame and features (e.g.
2190  * title bar, close box, etc.).
2191  */
2192 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
2193         CGRect frame, void* hwnd, macdrv_event_queue queue)
2195     __block WineWindow* window;
2197     OnMainThread(^{
2198         window = [[WineWindow createWindowWithFeatures:wf
2199                                            windowFrame:NSRectFromCGRect(frame)
2200                                                   hwnd:hwnd
2201                                                  queue:(WineEventQueue*)queue] retain];
2202     });
2204     return (macdrv_window)window;
2207 /***********************************************************************
2208  *              macdrv_destroy_cocoa_window
2210  * Destroy a Cocoa window.
2211  */
2212 void macdrv_destroy_cocoa_window(macdrv_window w)
2214     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2215     WineWindow* window = (WineWindow*)w;
2217     OnMainThread(^{
2218         [window doOrderOut];
2219         [window close];
2220     });
2221     [window.queue discardEventsMatchingMask:-1 forWindow:window];
2222     [window release];
2224     [pool release];
2227 /***********************************************************************
2228  *              macdrv_get_window_hwnd
2230  * Get the hwnd that was set for the window at creation.
2231  */
2232 void* macdrv_get_window_hwnd(macdrv_window w)
2234     WineWindow* window = (WineWindow*)w;
2235     return window.hwnd;
2238 /***********************************************************************
2239  *              macdrv_set_cocoa_window_features
2241  * Update a Cocoa window's features.
2242  */
2243 void macdrv_set_cocoa_window_features(macdrv_window w,
2244         const struct macdrv_window_features* wf)
2246     WineWindow* window = (WineWindow*)w;
2248     OnMainThread(^{
2249         [window setWindowFeatures:wf];
2250     });
2253 /***********************************************************************
2254  *              macdrv_set_cocoa_window_state
2256  * Update a Cocoa window's state.
2257  */
2258 void macdrv_set_cocoa_window_state(macdrv_window w,
2259         const struct macdrv_window_state* state)
2261     WineWindow* window = (WineWindow*)w;
2263     OnMainThread(^{
2264         [window setMacDrvState:state];
2265     });
2268 /***********************************************************************
2269  *              macdrv_set_cocoa_window_title
2271  * Set a Cocoa window's title.
2272  */
2273 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
2274         size_t length)
2276     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2277     WineWindow* window = (WineWindow*)w;
2278     NSString* titleString;
2280     if (title)
2281         titleString = [NSString stringWithCharacters:title length:length];
2282     else
2283         titleString = @"";
2284     OnMainThreadAsync(^{
2285         [window setTitle:titleString];
2286         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
2287             [NSApp changeWindowsItem:window title:titleString filename:NO];
2288     });
2290     [pool release];
2293 /***********************************************************************
2294  *              macdrv_order_cocoa_window
2296  * Reorder a Cocoa window relative to other windows.  If prev is
2297  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
2298  * it is ordered above that window.  Otherwise, it is ordered to the
2299  * front.
2300  */
2301 void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p,
2302         macdrv_window n, int activate)
2304     WineWindow* window = (WineWindow*)w;
2305     WineWindow* prev = (WineWindow*)p;
2306     WineWindow* next = (WineWindow*)n;
2308     OnMainThreadAsync(^{
2309         [window orderBelow:prev
2310                    orAbove:next
2311                   activate:activate];
2312     });
2313     [window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2314                                   forWindow:window];
2315     [next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2316                                 forWindow:next];
2319 /***********************************************************************
2320  *              macdrv_hide_cocoa_window
2322  * Hides a Cocoa window.
2323  */
2324 void macdrv_hide_cocoa_window(macdrv_window w)
2326     WineWindow* window = (WineWindow*)w;
2328     OnMainThread(^{
2329         [window doOrderOut];
2330     });
2333 /***********************************************************************
2334  *              macdrv_set_cocoa_window_frame
2336  * Move a Cocoa window.
2337  */
2338 void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
2340     WineWindow* window = (WineWindow*)w;
2342     OnMainThread(^{
2343         [window setFrameFromWine:NSRectFromCGRect(*new_frame)];
2344     });
2347 /***********************************************************************
2348  *              macdrv_get_cocoa_window_frame
2350  * Gets the frame of a Cocoa window.
2351  */
2352 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
2354     WineWindow* window = (WineWindow*)w;
2356     OnMainThread(^{
2357         NSRect frame;
2359         frame = [window contentRectForFrameRect:[window frame]];
2360         [[WineApplicationController sharedController] flipRect:&frame];
2361         *out_frame = NSRectToCGRect(frame);
2362     });
2365 /***********************************************************************
2366  *              macdrv_set_cocoa_parent_window
2368  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
2369  * the parent window.
2370  */
2371 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
2373     WineWindow* window = (WineWindow*)w;
2375     OnMainThread(^{
2376         [window setMacDrvParentWindow:(WineWindow*)parent];
2377     });
2380 /***********************************************************************
2381  *              macdrv_set_window_surface
2382  */
2383 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
2385     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2386     WineWindow* window = (WineWindow*)w;
2388     OnMainThread(^{
2389         window.surface = surface;
2390         window.surface_mutex = mutex;
2391     });
2393     [pool release];
2396 /***********************************************************************
2397  *              macdrv_window_needs_display
2399  * Mark a window as needing display in a specified rect (in non-client
2400  * area coordinates).
2401  */
2402 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
2404     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2405     WineWindow* window = (WineWindow*)w;
2407     OnMainThreadAsync(^{
2408         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
2409     });
2411     [pool release];
2414 /***********************************************************************
2415  *              macdrv_set_window_shape
2417  * Sets the shape of a Cocoa window from an array of rectangles.  If
2418  * rects is NULL, resets the window's shape to its frame.
2419  */
2420 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
2422     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2423     WineWindow* window = (WineWindow*)w;
2425     OnMainThread(^{
2426         if (!rects || !count)
2427         {
2428             window.shape = nil;
2429             window.shapeData = nil;
2430         }
2431         else
2432         {
2433             size_t length = sizeof(*rects) * count;
2434             if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
2435             {
2436                 NSBezierPath* path;
2437                 unsigned int i;
2439                 path = [NSBezierPath bezierPath];
2440                 for (i = 0; i < count; i++)
2441                     [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
2442                 window.shape = path;
2443                 window.shapeData = [NSData dataWithBytes:rects length:length];
2444             }
2445         }
2446     });
2448     [pool release];
2451 /***********************************************************************
2452  *              macdrv_set_window_alpha
2453  */
2454 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
2456     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2457     WineWindow* window = (WineWindow*)w;
2459     [window setAlphaValue:alpha];
2461     [pool release];
2464 /***********************************************************************
2465  *              macdrv_set_window_color_key
2466  */
2467 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
2468                                  CGFloat keyBlue)
2470     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2471     WineWindow* window = (WineWindow*)w;
2473     OnMainThread(^{
2474         window.colorKeyed       = TRUE;
2475         window.colorKeyRed      = keyRed;
2476         window.colorKeyGreen    = keyGreen;
2477         window.colorKeyBlue     = keyBlue;
2478         [window checkTransparency];
2479     });
2481     [pool release];
2484 /***********************************************************************
2485  *              macdrv_clear_window_color_key
2486  */
2487 void macdrv_clear_window_color_key(macdrv_window w)
2489     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2490     WineWindow* window = (WineWindow*)w;
2492     OnMainThread(^{
2493         window.colorKeyed = FALSE;
2494         [window checkTransparency];
2495     });
2497     [pool release];
2500 /***********************************************************************
2501  *              macdrv_window_use_per_pixel_alpha
2502  */
2503 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
2505     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2506     WineWindow* window = (WineWindow*)w;
2508     OnMainThread(^{
2509         window.usePerPixelAlpha = use_per_pixel_alpha;
2510         [window checkTransparency];
2511     });
2513     [pool release];
2516 /***********************************************************************
2517  *              macdrv_give_cocoa_window_focus
2519  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
2520  * orders it front and, if its frame was not within the desktop bounds,
2521  * Cocoa will typically move it on-screen.
2522  */
2523 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
2525     WineWindow* window = (WineWindow*)w;
2527     OnMainThread(^{
2528         [window makeFocused:activate];
2529     });
2532 /***********************************************************************
2533  *              macdrv_set_window_min_max_sizes
2535  * Sets the window's minimum and maximum content sizes.
2536  */
2537 void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
2539     WineWindow* window = (WineWindow*)w;
2541     OnMainThread(^{
2542         [window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)];
2543     });
2546 /***********************************************************************
2547  *              macdrv_create_view
2549  * Creates and returns a view in the specified rect of the window.  The
2550  * caller is responsible for calling macdrv_dispose_view() on the view
2551  * when it is done with it.
2552  */
2553 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
2555     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2556     WineWindow* window = (WineWindow*)w;
2557     __block WineContentView* view;
2559     if (CGRectIsNull(rect)) rect = CGRectZero;
2561     OnMainThread(^{
2562         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2564         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
2565         [view setAutoresizesSubviews:NO];
2566         [nc addObserver:view
2567                selector:@selector(updateGLContexts)
2568                    name:NSViewGlobalFrameDidChangeNotification
2569                  object:view];
2570         [nc addObserver:view
2571                selector:@selector(updateGLContexts)
2572                    name:NSApplicationDidChangeScreenParametersNotification
2573                  object:NSApp];
2574         [[window contentView] addSubview:view];
2575         [window updateColorSpace];
2576     });
2578     [pool release];
2579     return (macdrv_view)view;
2582 /***********************************************************************
2583  *              macdrv_dispose_view
2585  * Destroys a view previously returned by macdrv_create_view.
2586  */
2587 void macdrv_dispose_view(macdrv_view v)
2589     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2590     WineContentView* view = (WineContentView*)v;
2592     OnMainThread(^{
2593         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2594         WineWindow* window = (WineWindow*)[view window];
2596         [nc removeObserver:view
2597                       name:NSViewGlobalFrameDidChangeNotification
2598                     object:view];
2599         [nc removeObserver:view
2600                       name:NSApplicationDidChangeScreenParametersNotification
2601                     object:NSApp];
2602         [view removeFromSuperview];
2603         [view release];
2604         [window updateColorSpace];
2605     });
2607     [pool release];
2610 /***********************************************************************
2611  *              macdrv_set_view_window_and_frame
2613  * Move a view to a new window and/or position within its window.  If w
2614  * is NULL, leave the view in its current window and just change its
2615  * frame.
2616  */
2617 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
2619     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2620     WineContentView* view = (WineContentView*)v;
2621     WineWindow* window = (WineWindow*)w;
2623     if (CGRectIsNull(rect)) rect = CGRectZero;
2625     OnMainThread(^{
2626         BOOL changedWindow = (window && window != [view window]);
2627         NSRect newFrame = NSRectFromCGRect(rect);
2628         NSRect oldFrame = [view frame];
2629         BOOL needUpdateWindowColorSpace = FALSE;
2631         if (changedWindow)
2632         {
2633             WineWindow* oldWindow = (WineWindow*)[view window];
2634             [view removeFromSuperview];
2635             [oldWindow updateColorSpace];
2636             [[window contentView] addSubview:view];
2637             needUpdateWindowColorSpace = TRUE;
2638         }
2640         if (!NSEqualRects(oldFrame, newFrame))
2641         {
2642             if (!changedWindow)
2643                 [[view superview] setNeedsDisplayInRect:oldFrame];
2644             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
2645                 [view setFrameSize:newFrame.size];
2646             else if (NSEqualSizes(oldFrame.size, newFrame.size))
2647                 [view setFrameOrigin:newFrame.origin];
2648             else
2649                 [view setFrame:newFrame];
2650             [view setNeedsDisplay:YES];
2651             needUpdateWindowColorSpace = TRUE;
2652         }
2654         if (needUpdateWindowColorSpace)
2655             [(WineWindow*)[view window] updateColorSpace];
2656     });
2658     [pool release];
2661 /***********************************************************************
2662  *              macdrv_add_view_opengl_context
2664  * Add an OpenGL context to the list being tracked for each view.
2665  */
2666 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
2668     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2669     WineContentView* view = (WineContentView*)v;
2670     WineOpenGLContext *context = (WineOpenGLContext*)c;
2672     OnMainThread(^{
2673         [view addGLContext:context];
2674     });
2676     [pool release];
2679 /***********************************************************************
2680  *              macdrv_remove_view_opengl_context
2682  * Add an OpenGL context to the list being tracked for each view.
2683  */
2684 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
2686     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2687     WineContentView* view = (WineContentView*)v;
2688     WineOpenGLContext *context = (WineOpenGLContext*)c;
2690     OnMainThreadAsync(^{
2691         [view removeGLContext:context];
2692     });
2694     [pool release];
2697 /***********************************************************************
2698  *              macdrv_window_background_color
2700  * Returns the standard Mac window background color as a 32-bit value of
2701  * the form 0x00rrggbb.
2702  */
2703 uint32_t macdrv_window_background_color(void)
2705     static uint32_t result;
2706     static dispatch_once_t once;
2708     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
2709     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
2710     // of it is to draw with it.
2711     dispatch_once(&once, ^{
2712         OnMainThread(^{
2713             unsigned char rgbx[4];
2714             unsigned char *planes = rgbx;
2715             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
2716                                                                                pixelsWide:1
2717                                                                                pixelsHigh:1
2718                                                                             bitsPerSample:8
2719                                                                           samplesPerPixel:3
2720                                                                                  hasAlpha:NO
2721                                                                                  isPlanar:NO
2722                                                                            colorSpaceName:NSCalibratedRGBColorSpace
2723                                                                              bitmapFormat:0
2724                                                                               bytesPerRow:4
2725                                                                              bitsPerPixel:32];
2726             [NSGraphicsContext saveGraphicsState];
2727             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
2728             [[NSColor windowBackgroundColor] set];
2729             NSRectFill(NSMakeRect(0, 0, 1, 1));
2730             [NSGraphicsContext restoreGraphicsState];
2731             [bitmap release];
2732             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
2733         });
2734     });
2736     return result;
2739 /***********************************************************************
2740  *              macdrv_send_text_input_event
2741  */
2742 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
2744     __block BOOL ret;
2746     OnMainThread(^{
2747         WineWindow* window = (WineWindow*)[NSApp keyWindow];
2748         if (![window isKindOfClass:[WineWindow class]])
2749         {
2750             window = (WineWindow*)[NSApp mainWindow];
2751             if (![window isKindOfClass:[WineWindow class]])
2752                 window = [[WineApplicationController sharedController] frontWineWindow];
2753         }
2755         if (window)
2756         {
2757             NSUInteger localFlags = flags;
2758             CGEventRef c;
2759             NSEvent* event;
2761             window.imeData = data;
2762             fix_device_modifiers_by_generic(&localFlags);
2764             // An NSEvent created with +keyEventWithType:... is internally marked
2765             // as synthetic and doesn't get sent through input methods.  But one
2766             // created from a CGEvent doesn't have that problem.
2767             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
2768             CGEventSetFlags(c, localFlags);
2769             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
2770             event = [NSEvent eventWithCGEvent:c];
2771             CFRelease(c);
2773             window.commandDone = FALSE;
2774             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
2775         }
2776         else
2777             ret = FALSE;
2778     });
2780     return ret;