winemac: Add an option to capture the displays for full-screen windows in addition...
[wine/multimedia.git] / dlls / winemac.drv / cocoa_window.m
blob7c2e040e7921e84b6918123cd664878c0418df36
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 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
32 enum {
33     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
37 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
39     NSUInteger style_mask;
41     if (wf->title_bar)
42     {
43         style_mask = NSTitledWindowMask;
44         if (wf->close_button) style_mask |= NSClosableWindowMask;
45         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
46         if (wf->resizable) style_mask |= NSResizableWindowMask;
47         if (wf->utility) style_mask |= NSUtilityWindowMask;
48     }
49     else style_mask = NSBorderlessWindowMask;
51     return style_mask;
55 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
57     NSScreen* screen;
58     for (screen in screens)
59     {
60         if (NSIntersectsRect(frame, [screen frame]))
61             return TRUE;
62     }
63     return FALSE;
67 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
69     for (NSScreen* screen in screens)
70     {
71         if (NSContainsRect(rect, [screen frame]))
72             return screen;
73     }
74     return nil;
78 /* We rely on the supposedly device-dependent modifier flags to distinguish the
79    keys on the left side of the keyboard from those on the right.  Some event
80    sources don't set those device-depdendent flags.  If we see a device-independent
81    flag for a modifier without either corresponding device-dependent flag, assume
82    the left one. */
83 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
85     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
86         *modifiers |= NX_DEVICELCMDKEYMASK;
87     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
88         *modifiers |= NX_DEVICELSHIFTKEYMASK;
89     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
90         *modifiers |= NX_DEVICELCTLKEYMASK;
91     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
92         *modifiers |= NX_DEVICELALTKEYMASK;
95 /* As we manipulate individual bits of a modifier mask, we can end up with
96    inconsistent sets of flags.  In particular, we might set or clear one of the
97    left/right-specific bits, but not the corresponding non-side-specific bit.
98    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
99    otherwise clear it. */
100 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
102     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
103         *modifiers |= NX_COMMANDMASK;
104     else
105         *modifiers &= ~NX_COMMANDMASK;
106     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
107         *modifiers |= NX_SHIFTMASK;
108     else
109         *modifiers &= ~NX_SHIFTMASK;
110     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
111         *modifiers |= NX_CONTROLMASK;
112     else
113         *modifiers &= ~NX_CONTROLMASK;
114     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
115         *modifiers |= NX_ALTERNATEMASK;
116     else
117         *modifiers &= ~NX_ALTERNATEMASK;
121 @interface WineContentView : NSView <NSTextInputClient>
123     NSMutableArray* glContexts;
124     NSMutableArray* pendingGlContexts;
126     NSMutableAttributedString* markedText;
127     NSRange markedTextSelection;
130     - (void) addGLContext:(WineOpenGLContext*)context;
131     - (void) removeGLContext:(WineOpenGLContext*)context;
132     - (void) updateGLContexts;
134 @end
137 @interface WineWindow ()
139 @property (nonatomic) BOOL disabled;
140 @property (nonatomic) BOOL noActivate;
141 @property (readwrite, nonatomic) BOOL floating;
142 @property (retain, nonatomic) NSWindow* latentParentWindow;
144 @property (nonatomic) void* hwnd;
145 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
147 @property (nonatomic) void* surface;
148 @property (nonatomic) pthread_mutex_t* surface_mutex;
150 @property (copy, nonatomic) NSBezierPath* shape;
151 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
152 @property (readonly, nonatomic) BOOL needsTransparency;
154 @property (nonatomic) BOOL colorKeyed;
155 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
156 @property (nonatomic) BOOL usePerPixelAlpha;
158 @property (assign, nonatomic) void* imeData;
159 @property (nonatomic) BOOL commandDone;
161 @end
164 @implementation WineContentView
166     - (void) dealloc
167     {
168         [markedText release];
169         [glContexts release];
170         [pendingGlContexts release];
171         [super dealloc];
172     }
174     - (BOOL) isFlipped
175     {
176         return YES;
177     }
179     - (void) drawRect:(NSRect)rect
180     {
181         WineWindow* window = (WineWindow*)[self window];
183         for (WineOpenGLContext* context in pendingGlContexts)
184             context.needsUpdate = TRUE;
185         [glContexts addObjectsFromArray:pendingGlContexts];
186         [pendingGlContexts removeAllObjects];
188         if ([window contentView] != self)
189             return;
191         if (window.surface && window.surface_mutex &&
192             !pthread_mutex_lock(window.surface_mutex))
193         {
194             const CGRect* rects;
195             int count;
197             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
198             {
199                 CGContextRef context;
200                 int i;
202                 [window.shape addClip];
204                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
205                 CGContextSetBlendMode(context, kCGBlendModeCopy);
207                 for (i = 0; i < count; i++)
208                 {
209                     CGRect imageRect;
210                     CGImageRef image;
212                     imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
213                     image = create_surface_image(window.surface, &imageRect, FALSE);
215                     if (image)
216                     {
217                         if (window.colorKeyed)
218                         {
219                             CGImageRef maskedImage;
220                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
221                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
222                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
223                             maskedImage = CGImageCreateWithMaskingColors(image, components);
224                             if (maskedImage)
225                             {
226                                 CGImageRelease(image);
227                                 image = maskedImage;
228                             }
229                         }
231                         CGContextDrawImage(context, imageRect, image);
233                         CGImageRelease(image);
234                     }
235                 }
236             }
238             pthread_mutex_unlock(window.surface_mutex);
239         }
241         // If the window may be transparent, then we have to invalidate the
242         // shadow every time we draw.  Also, if this is the first time we've
243         // drawn since changing from transparent to opaque.
244         if (![window isOpaque] || window.shapeChangedSinceLastDraw)
245         {
246             window.shapeChangedSinceLastDraw = FALSE;
247             [window invalidateShadow];
248         }
249     }
251     - (void) addGLContext:(WineOpenGLContext*)context
252     {
253         if (!glContexts)
254             glContexts = [[NSMutableArray alloc] init];
255         if (!pendingGlContexts)
256             pendingGlContexts = [[NSMutableArray alloc] init];
257         [pendingGlContexts addObject:context];
258         [self setNeedsDisplay:YES];
259     }
261     - (void) removeGLContext:(WineOpenGLContext*)context
262     {
263         [glContexts removeObjectIdenticalTo:context];
264         [pendingGlContexts removeObjectIdenticalTo:context];
265     }
267     - (void) updateGLContexts
268     {
269         for (WineOpenGLContext* context in glContexts)
270             context.needsUpdate = TRUE;
271     }
273     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
274     {
275         return YES;
276     }
278     - (BOOL) preservesContentDuringLiveResize
279     {
280         // Returning YES from this tells Cocoa to keep our view's content during
281         // a Cocoa-driven resize.  In theory, we're also supposed to override
282         // -setFrameSize: to mark exposed sections as needing redisplay, but
283         // user32 will take care of that in a roundabout way.  This way, we don't
284         // redraw until the window surface is flushed.
285         //
286         // This doesn't do anything when we resize the window ourselves.
287         return YES;
288     }
290     - (BOOL)acceptsFirstResponder
291     {
292         return [[self window] contentView] == self;
293     }
295     - (void) completeText:(NSString*)text
296     {
297         macdrv_event* event;
298         WineWindow* window = (WineWindow*)[self window];
300         event = macdrv_create_event(IM_SET_TEXT, window);
301         event->im_set_text.data = [window imeData];
302         event->im_set_text.text = (CFStringRef)[text copy];
303         event->im_set_text.complete = TRUE;
305         [[window queue] postEvent:event];
307         macdrv_release_event(event);
309         [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
310         markedTextSelection = NSMakeRange(0, 0);
311         [[self inputContext] discardMarkedText];
312     }
314     /*
315      * ---------- NSTextInputClient methods ----------
316      */
317     - (NSTextInputContext*) inputContext
318     {
319         if (!markedText)
320             markedText = [[NSMutableAttributedString alloc] init];
321         return [super inputContext];
322     }
324     - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
325     {
326         if ([string isKindOfClass:[NSAttributedString class]])
327             string = [string string];
329         if ([string isKindOfClass:[NSString class]])
330             [self completeText:string];
331     }
333     - (void) doCommandBySelector:(SEL)aSelector
334     {
335         [(WineWindow*)[self window] setCommandDone:TRUE];
336     }
338     - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
339     {
340         if ([string isKindOfClass:[NSAttributedString class]])
341             string = [string string];
343         if ([string isKindOfClass:[NSString class]])
344         {
345             macdrv_event* event;
346             WineWindow* window = (WineWindow*)[self window];
348             if (replacementRange.location == NSNotFound)
349                 replacementRange = NSMakeRange(0, [markedText length]);
351             [markedText replaceCharactersInRange:replacementRange withString:string];
352             markedTextSelection = selectedRange;
353             markedTextSelection.location += replacementRange.location;
355             event = macdrv_create_event(IM_SET_TEXT, window);
356             event->im_set_text.data = [window imeData];
357             event->im_set_text.text = (CFStringRef)[[markedText string] copy];
358             event->im_set_text.complete = FALSE;
359             event->im_set_text.cursor_pos = markedTextSelection.location;
361             [[window queue] postEvent:event];
363             macdrv_release_event(event);
365             [[self inputContext] invalidateCharacterCoordinates];
366         }
367     }
369     - (void) unmarkText
370     {
371         [self completeText:nil];
372     }
374     - (NSRange) selectedRange
375     {
376         return markedTextSelection;
377     }
379     - (NSRange) markedRange
380     {
381         NSRange range = NSMakeRange(0, [markedText length]);
382         if (!range.length)
383             range.location = NSNotFound;
384         return range;
385     }
387     - (BOOL) hasMarkedText
388     {
389         return [markedText length] > 0;
390     }
392     - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
393     {
394         if (aRange.location >= [markedText length])
395             return nil;
397         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
398         if (actualRange)
399             *actualRange = aRange;
400         return [markedText attributedSubstringFromRange:aRange];
401     }
403     - (NSArray*) validAttributesForMarkedText
404     {
405         return [NSArray array];
406     }
408     - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
409     {
410         macdrv_query* query;
411         WineWindow* window = (WineWindow*)[self window];
412         NSRect ret;
414         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
416         query = macdrv_create_query();
417         query->type = QUERY_IME_CHAR_RECT;
418         query->window = (macdrv_window)[window retain];
419         query->ime_char_rect.data = [window imeData];
420         query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
422         if ([window.queue query:query timeout:1])
423         {
424             aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
425             ret = NSRectFromCGRect(query->ime_char_rect.rect);
426             [[WineApplicationController sharedController] flipRect:&ret];
427         }
428         else
429             ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
431         macdrv_release_query(query);
433         if (actualRange)
434             *actualRange = aRange;
435         return ret;
436     }
438     - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
439     {
440         return NSNotFound;
441     }
443     - (NSInteger) windowLevel
444     {
445         return [[self window] level];
446     }
448 @end
451 @implementation WineWindow
453     @synthesize disabled, noActivate, floating, fullscreen, latentParentWindow, hwnd, queue;
454     @synthesize surface, surface_mutex;
455     @synthesize shape, shapeChangedSinceLastDraw;
456     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
457     @synthesize usePerPixelAlpha;
458     @synthesize imeData, commandDone;
460     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
461                                  windowFrame:(NSRect)window_frame
462                                         hwnd:(void*)hwnd
463                                        queue:(WineEventQueue*)queue
464     {
465         WineWindow* window;
466         WineContentView* contentView;
467         NSTrackingArea* trackingArea;
469         [[WineApplicationController sharedController] flipRect:&window_frame];
471         window = [[[self alloc] initWithContentRect:window_frame
472                                           styleMask:style_mask_for_features(wf)
473                                             backing:NSBackingStoreBuffered
474                                               defer:YES] autorelease];
476         if (!window) return nil;
478         /* Standardize windows to eliminate differences between titled and
479            borderless windows and between NSWindow and NSPanel. */
480         [window setHidesOnDeactivate:NO];
481         [window setReleasedWhenClosed:NO];
483         [window disableCursorRects];
484         [window setShowsResizeIndicator:NO];
485         [window setHasShadow:wf->shadow];
486         [window setAcceptsMouseMovedEvents:YES];
487         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
488         [window setDelegate:window];
489         window.hwnd = hwnd;
490         window.queue = queue;
492         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
493                                                                   (NSString*)kUTTypeContent,
494                                                                   nil]];
496         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
497         if (!contentView)
498             return nil;
499         [contentView setAutoresizesSubviews:NO];
501         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
502            because they give us mouse moves in the background. */
503         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
504                                                      options:(NSTrackingMouseMoved |
505                                                               NSTrackingActiveAlways |
506                                                               NSTrackingInVisibleRect)
507                                                        owner:window
508                                                     userInfo:nil] autorelease];
509         if (!trackingArea)
510             return nil;
511         [contentView addTrackingArea:trackingArea];
513         [window setContentView:contentView];
514         [window setInitialFirstResponder:contentView];
516         [[NSNotificationCenter defaultCenter] addObserver:window
517                                                  selector:@selector(updateFullscreen)
518                                                      name:NSApplicationDidChangeScreenParametersNotification
519                                                    object:NSApp];
520         [window updateFullscreen];
522         return window;
523     }
525     - (void) dealloc
526     {
527         [[NSNotificationCenter defaultCenter] removeObserver:self];
528         [liveResizeDisplayTimer invalidate];
529         [liveResizeDisplayTimer release];
530         [queue release];
531         [latentParentWindow release];
532         [shape release];
533         [super dealloc];
534     }
536     - (void) adjustFeaturesForState
537     {
538         NSUInteger style = [self styleMask];
540         if (style & NSClosableWindowMask)
541             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
542         if (style & NSMiniaturizableWindowMask)
543             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
544         if (style & NSResizableWindowMask)
545             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
546     }
548     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
549     {
550         NSUInteger currentStyle = [self styleMask];
551         NSUInteger newStyle = style_mask_for_features(wf);
553         if (newStyle != currentStyle)
554         {
555             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
556             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
557             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
558             {
559                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
560                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
561                 // just NSTitledWindowMask, the window buttons should disappear rather
562                 // than just being disabled.  But they don't.  Similarly in reverse.
563                 // The workaround is to also toggle NSClosableWindowMask at the same time.
564                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
565             }
566             [self setStyleMask:newStyle];
567         }
569         [self adjustFeaturesForState];
570         [self setHasShadow:wf->shadow];
571     }
573     - (BOOL) isOrderedIn
574     {
575         return [self isVisible] || [self isMiniaturized];
576     }
578     - (NSInteger) minimumLevelForActive:(BOOL)active
579     {
580         NSInteger level;
582         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
583                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
584             level = NSFloatingWindowLevel;
585         else
586             level = NSNormalWindowLevel;
588         if (active)
589         {
590             BOOL captured;
592             captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
594             if (captured || fullscreen)
595             {
596                 if (captured)
597                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
598                 else
599                     level = NSMainMenuWindowLevel + 1;
601                 if (self.floating)
602                     level++;
603             }
604         }
606         return level;
607     }
609     - (void) setMacDrvState:(const struct macdrv_window_state*)state
610     {
611         NSWindowCollectionBehavior behavior;
613         self.disabled = state->disabled;
614         self.noActivate = state->no_activate;
616         if (self.floating != state->floating)
617         {
618             self.floating = state->floating;
619             [[WineApplicationController sharedController] adjustWindowLevels];
620         }
622         behavior = NSWindowCollectionBehaviorDefault;
623         if (state->excluded_by_expose)
624             behavior |= NSWindowCollectionBehaviorTransient;
625         else
626             behavior |= NSWindowCollectionBehaviorManaged;
627         if (state->excluded_by_cycle)
628         {
629             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
630             if ([self isOrderedIn])
631                 [NSApp removeWindowsItem:self];
632         }
633         else
634         {
635             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
636             if ([self isOrderedIn])
637                 [NSApp addWindowsItem:self title:[self title] filename:NO];
638         }
639         [self setCollectionBehavior:behavior];
641         pendingMinimize = FALSE;
642         if (state->minimized && ![self isMiniaturized])
643         {
644             if ([self isVisible])
645             {
646                 ignore_windowMiniaturize = TRUE;
647                 [self miniaturize:nil];
648             }
649             else
650                 pendingMinimize = TRUE;
651         }
652         else if (!state->minimized && [self isMiniaturized])
653         {
654             ignore_windowDeminiaturize = TRUE;
655             [self deminiaturize:nil];
656         }
658         /* Whatever events regarding minimization might have been in the queue are now stale. */
659         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
660                                          event_mask_for_type(WINDOW_DID_UNMINIMIZE)
661                                forWindow:self];
662     }
664     // Determine if, among Wine windows, this window is directly above or below
665     // a given other Wine window with no other Wine window intervening.
666     // Intervening non-Wine windows are ignored.
667     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
668     {
669         NSNumber* windowNumber;
670         NSNumber* otherWindowNumber;
671         NSArray* windowNumbers;
672         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
674         if (![self isVisible] || ![otherWindow isVisible])
675             return FALSE;
677         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
678         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
679         windowNumbers = [[self class] windowNumbersWithOptions:0];
680         windowIndex = [windowNumbers indexOfObject:windowNumber];
681         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
683         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
684             return FALSE;
686         if (orderingMode == NSWindowAbove)
687         {
688             lowIndex = windowIndex;
689             highIndex = otherWindowIndex;
690         }
691         else if (orderingMode == NSWindowBelow)
692         {
693             lowIndex = otherWindowIndex;
694             highIndex = windowIndex;
695         }
696         else
697             return FALSE;
699         if (highIndex <= lowIndex)
700             return FALSE;
702         for (i = lowIndex + 1; i < highIndex; i++)
703         {
704             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
705             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
706             if ([interveningWindow isKindOfClass:[WineWindow class]])
707                 return FALSE;
708         }
710         return TRUE;
711     }
713     /* Returns whether or not the window was ordered in, which depends on if
714        its frame intersects any screen. */
715     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
716     {
717         WineApplicationController* controller = [WineApplicationController sharedController];
718         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
719         if (on_screen && ![self isMiniaturized])
720         {
721             BOOL needAdjustWindowLevels = FALSE;
722             BOOL wasVisible = [self isVisible];
724             [controller transformProcessToForeground];
726             NSDisableScreenUpdates();
728             if (latentParentWindow)
729             {
730                 if ([latentParentWindow level] > [self level])
731                     [self setLevel:[latentParentWindow level]];
732                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
733                 self.latentParentWindow = nil;
734                 needAdjustWindowLevels = TRUE;
735             }
736             if (prev || next)
737             {
738                 WineWindow* other = [prev isVisible] ? prev : next;
739                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
741                 if (![self isOrdered:orderingMode relativeTo:other])
742                 {
743                     // This window level may not be right for this window based
744                     // on floating-ness, fullscreen-ness, etc.  But we set it
745                     // temporarily to allow us to order the windows properly.
746                     // Then the levels get fixed by -adjustWindowLevels.
747                     if ([self level] != [other level])
748                         [self setLevel:[other level]];
749                     [self orderWindow:orderingMode relativeTo:[other windowNumber]];
750                     needAdjustWindowLevels = TRUE;
751                 }
752             }
753             else
754             {
755                 // Again, temporarily set level to make sure we can order to
756                 // the right place.
757                 next = [controller frontWineWindow];
758                 if (next && [self level] < [next level])
759                     [self setLevel:[next level]];
760                 [self orderFront:nil];
761                 needAdjustWindowLevels = TRUE;
762             }
763             if (needAdjustWindowLevels)
764             {
765                 if (!wasVisible && fullscreen && [self isOnActiveSpace])
766                     [controller updateFullscreenWindows];
767                 [controller adjustWindowLevels];
768             }
770             if (pendingMinimize)
771             {
772                 ignore_windowMiniaturize = TRUE;
773                 [self miniaturize:nil];
774                 pendingMinimize = FALSE;
775             }
777             NSEnableScreenUpdates();
779             /* Cocoa may adjust the frame when the window is ordered onto the screen.
780                Generate a frame-changed event just in case.  The back end will ignore
781                it if nothing actually changed. */
782             [self windowDidResize:nil];
784             if (![self isExcludedFromWindowsMenu])
785                 [NSApp addWindowsItem:self title:[self title] filename:NO];
786         }
788         return on_screen;
789     }
791     - (void) doOrderOut
792     {
793         WineApplicationController* controller = [WineApplicationController sharedController];
794         BOOL wasVisible = [self isVisible];
795         BOOL wasOnActiveSpace = [self isOnActiveSpace];
797         if ([self isMiniaturized])
798             pendingMinimize = TRUE;
799         self.latentParentWindow = [self parentWindow];
800         [latentParentWindow removeChildWindow:self];
801         [self orderOut:nil];
802         if (wasVisible && wasOnActiveSpace && fullscreen)
803             [controller updateFullscreenWindows];
804         [controller adjustWindowLevels];
805         [NSApp removeWindowsItem:self];
806     }
808     - (void) updateFullscreen
809     {
810         NSRect contentRect = [self contentRectForFrameRect:[self frame]];
811         BOOL nowFullscreen = (screen_covered_by_rect(contentRect, [NSScreen screens]) != nil);
813         if (nowFullscreen != fullscreen)
814         {
815             WineApplicationController* controller = [WineApplicationController sharedController];
817             fullscreen = nowFullscreen;
818             if ([self isVisible] && [self isOnActiveSpace])
819                 [controller updateFullscreenWindows];
821             [controller adjustWindowLevels];
822         }
823     }
825     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
826     {
827         NSArray* screens = [NSScreen screens];
828         BOOL on_screen = [self isOrderedIn];
830         if (![screens count]) return on_screen;
832         /* Origin is (left, top) in a top-down space.  Need to convert it to
833            (left, bottom) in a bottom-up space. */
834         [[WineApplicationController sharedController] flipRect:&contentRect];
836         if (on_screen)
837         {
838             on_screen = frame_intersects_screens(contentRect, screens);
839             if (!on_screen)
840                 [self doOrderOut];
841         }
843         /* The back end is establishing a new window size and position.  It's
844            not interested in any stale events regarding those that may be sitting
845            in the queue. */
846         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
847                                forWindow:self];
849         if (!NSIsEmptyRect(contentRect))
850         {
851             NSRect frame, oldFrame;
853             oldFrame = [self frame];
854             frame = [self frameRectForContentRect:contentRect];
855             if (!NSEqualRects(frame, oldFrame))
856             {
857                 if (NSEqualSizes(frame.size, oldFrame.size))
858                     [self setFrameOrigin:frame.origin];
859                 else
860                     [self setFrame:frame display:YES];
862                 [self updateFullscreen];
864                 if (on_screen)
865                 {
866                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
867                        event.  The back end will ignore it if nothing actually changed. */
868                     [self windowDidResize:nil];
869                 }
870             }
871         }
873         return on_screen;
874     }
876     - (void) setMacDrvParentWindow:(WineWindow*)parent
877     {
878         if ([self parentWindow] != parent)
879         {
880             [[self parentWindow] removeChildWindow:self];
881             self.latentParentWindow = nil;
882             if ([self isVisible] && parent)
883             {
884                 if ([parent level] > [self level])
885                     [self setLevel:[parent level]];
886                 [parent addChildWindow:self ordered:NSWindowAbove];
887                 [[WineApplicationController sharedController] adjustWindowLevels];
888             }
889             else
890                 self.latentParentWindow = parent;
891         }
892     }
894     - (void) setDisabled:(BOOL)newValue
895     {
896         if (disabled != newValue)
897         {
898             disabled = newValue;
899             [self adjustFeaturesForState];
901             if (disabled)
902             {
903                 NSSize size = [self frame].size;
904                 [self setMinSize:size];
905                 [self setMaxSize:size];
906             }
907             else
908             {
909                 [self setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
910                 [self setMinSize:NSZeroSize];
911             }
912         }
913     }
915     - (BOOL) needsTransparency
916     {
917         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
918     }
920     - (void) checkTransparency
921     {
922         if (![self isOpaque] && !self.needsTransparency)
923         {
924             [self setBackgroundColor:[NSColor windowBackgroundColor]];
925             [self setOpaque:YES];
926         }
927         else if ([self isOpaque] && self.needsTransparency)
928         {
929             [self setBackgroundColor:[NSColor clearColor]];
930             [self setOpaque:NO];
931         }
932     }
934     - (void) setShape:(NSBezierPath*)newShape
935     {
936         if (shape == newShape) return;
937         if (shape && newShape && [shape isEqual:newShape]) return;
939         if (shape)
940         {
941             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
942             [shape release];
943         }
944         if (newShape)
945             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
947         shape = [newShape copy];
948         self.shapeChangedSinceLastDraw = TRUE;
950         [self checkTransparency];
951     }
953     - (void) makeFocused:(BOOL)activate
954     {
955         WineApplicationController* controller = [WineApplicationController sharedController];
956         NSArray* screens;
957         WineWindow* front;
958         BOOL wasVisible = [self isVisible];
960         [controller transformProcessToForeground];
962         /* If a borderless window is offscreen, orderFront: won't move
963            it onscreen like it would for a titled window.  Do that ourselves. */
964         screens = [NSScreen screens];
965         if (!([self styleMask] & NSTitledWindowMask) && ![self isOrderedIn] &&
966             !frame_intersects_screens([self frame], screens))
967         {
968             NSScreen* primaryScreen = [screens objectAtIndex:0];
969             NSRect frame = [primaryScreen frame];
970             [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
971             frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
972             [self setFrame:frame display:YES];
973         }
975         if (activate)
976             [NSApp activateIgnoringOtherApps:YES];
978         NSDisableScreenUpdates();
980         if (latentParentWindow)
981         {
982             if ([latentParentWindow level] > [self level])
983                 [self setLevel:[latentParentWindow level]];
984             [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
985             self.latentParentWindow = nil;
986         }
987         front = [controller frontWineWindow];
988         if (front && [self level] < [front level])
989             [self setLevel:[front level]];
990         [self orderFront:nil];
991         if (!wasVisible && fullscreen && [self isOnActiveSpace])
992             [controller updateFullscreenWindows];
993         [controller adjustWindowLevels];
995         if (pendingMinimize)
996         {
997             ignore_windowMiniaturize = TRUE;
998             [self miniaturize:nil];
999             pendingMinimize = FALSE;
1000         }
1002         NSEnableScreenUpdates();
1004         causing_becomeKeyWindow = TRUE;
1005         [self makeKeyWindow];
1006         causing_becomeKeyWindow = FALSE;
1008         if (![self isExcludedFromWindowsMenu])
1009             [NSApp addWindowsItem:self title:[self title] filename:NO];
1011         /* Cocoa may adjust the frame when the window is ordered onto the screen.
1012            Generate a frame-changed event just in case.  The back end will ignore
1013            it if nothing actually changed. */
1014         [self windowDidResize:nil];
1015     }
1017     - (void) postKey:(uint16_t)keyCode
1018              pressed:(BOOL)pressed
1019            modifiers:(NSUInteger)modifiers
1020                event:(NSEvent*)theEvent
1021     {
1022         macdrv_event* event;
1023         CGEventRef cgevent;
1024         WineApplicationController* controller = [WineApplicationController sharedController];
1026         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
1027         event->key.keycode   = keyCode;
1028         event->key.modifiers = modifiers;
1029         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
1031         if ((cgevent = [theEvent CGEvent]))
1032         {
1033             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
1034                                                         kCGKeyboardEventKeyboardType);
1035             if (keyboardType != controller.keyboardType)
1036             {
1037                 controller.keyboardType = keyboardType;
1038                 [controller keyboardSelectionDidChange];
1039             }
1040         }
1042         [queue postEvent:event];
1044         macdrv_release_event(event);
1045     }
1047     - (void) postKeyEvent:(NSEvent *)theEvent
1048     {
1049         [self flagsChanged:theEvent];
1050         [self postKey:[theEvent keyCode]
1051               pressed:[theEvent type] == NSKeyDown
1052             modifiers:[theEvent modifierFlags]
1053                 event:theEvent];
1054     }
1057     /*
1058      * ---------- NSWindow method overrides ----------
1059      */
1060     - (BOOL) canBecomeKeyWindow
1061     {
1062         if (causing_becomeKeyWindow) return YES;
1063         if (self.disabled || self.noActivate) return NO;
1064         return [self isKeyWindow];
1065     }
1067     - (BOOL) canBecomeMainWindow
1068     {
1069         return [self canBecomeKeyWindow];
1070     }
1072     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1073     {
1074         // If a window is sized to completely cover a screen, then it's in
1075         // full-screen mode.  In that case, we don't allow NSWindow to constrain
1076         // it.
1077         NSRect contentRect = [self contentRectForFrameRect:frameRect];
1078         if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
1079             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1080         return frameRect;
1081     }
1083     - (BOOL) isExcludedFromWindowsMenu
1084     {
1085         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1086     }
1088     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1089     {
1090         BOOL ret = [super validateMenuItem:menuItem];
1092         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1093             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1095         return ret;
1096     }
1098     /* We don't call this.  It's the action method of the items in the Window menu. */
1099     - (void) makeKeyAndOrderFront:(id)sender
1100     {
1101         WineApplicationController* controller = [WineApplicationController sharedController];
1102         WineWindow* front = [controller frontWineWindow];
1103         BOOL wasVisible = [self isVisible];
1105         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1106             [controller windowGotFocus:self];
1108         if (front && [self level] < [front level])
1109             [self setLevel:[front level]];
1110         [self orderFront:nil];
1111         if (!wasVisible && fullscreen && [self isOnActiveSpace])
1112             [controller updateFullscreenWindows];
1113         [controller adjustWindowLevels];
1115         if (pendingMinimize)
1116         {
1117             ignore_windowMiniaturize = TRUE;
1118             [self miniaturize:nil];
1119             pendingMinimize = FALSE;
1120         }
1121     }
1123     - (void) sendEvent:(NSEvent*)event
1124     {
1125         WineApplicationController* controller = [WineApplicationController sharedController];
1127         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1128            interface control.  For example, Control-Tab switches focus among
1129            views.  We want to bypass that feature, so directly route key-down
1130            events to -keyDown:. */
1131         if ([event type] == NSKeyDown)
1132             [[self firstResponder] keyDown:event];
1133         else
1134         {
1135             if ([event type] == NSLeftMouseDown)
1136             {
1137                 /* Since our windows generally claim they can't be made key, clicks
1138                    in their title bars are swallowed by the theme frame stuff.  So,
1139                    we hook directly into the event stream and assume that any click
1140                    in the window will activate it, if Wine and the Win32 program
1141                    accept. */
1142                 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1143                     [controller windowGotFocus:self];
1144             }
1146             [super sendEvent:event];
1147         }
1148     }
1151     /*
1152      * ---------- NSResponder method overrides ----------
1153      */
1154     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1155     - (void) keyUp:(NSEvent *)theEvent   { [self postKeyEvent:theEvent]; }
1157     - (void) flagsChanged:(NSEvent *)theEvent
1158     {
1159         static const struct {
1160             NSUInteger  mask;
1161             uint16_t    keycode;
1162         } modifiers[] = {
1163             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
1164             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
1165             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
1166             { NX_DEVICELCTLKEYMASK,     kVK_Control },
1167             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
1168             { NX_DEVICELALTKEYMASK,     kVK_Option },
1169             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
1170             { NX_DEVICELCMDKEYMASK,     kVK_Command },
1171             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
1172         };
1174         NSUInteger modifierFlags = [theEvent modifierFlags];
1175         NSUInteger changed;
1176         int i, last_changed;
1178         fix_device_modifiers_by_generic(&modifierFlags);
1179         changed = modifierFlags ^ lastModifierFlags;
1181         last_changed = -1;
1182         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
1183             if (changed & modifiers[i].mask)
1184                 last_changed = i;
1186         for (i = 0; i <= last_changed; i++)
1187         {
1188             if (changed & modifiers[i].mask)
1189             {
1190                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
1192                 if (i == last_changed)
1193                     lastModifierFlags = modifierFlags;
1194                 else
1195                 {
1196                     lastModifierFlags ^= modifiers[i].mask;
1197                     fix_generic_modifiers_by_device(&lastModifierFlags);
1198                 }
1200                 // Caps lock generates one event for each press-release action.
1201                 // We need to simulate a pair of events for each actual event.
1202                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1203                 {
1204                     [self postKey:modifiers[i].keycode
1205                           pressed:TRUE
1206                         modifiers:lastModifierFlags
1207                             event:(NSEvent*)theEvent];
1208                     pressed = FALSE;
1209                 }
1211                 [self postKey:modifiers[i].keycode
1212                       pressed:pressed
1213                     modifiers:lastModifierFlags
1214                         event:(NSEvent*)theEvent];
1215             }
1216         }
1217     }
1220     /*
1221      * ---------- NSWindowDelegate methods ----------
1222      */
1223     - (void)windowDidBecomeKey:(NSNotification *)notification
1224     {
1225         WineApplicationController* controller = [WineApplicationController sharedController];
1226         NSEvent* event = [controller lastFlagsChanged];
1227         if (event)
1228             [self flagsChanged:event];
1230         if (causing_becomeKeyWindow) return;
1232         [controller windowGotFocus:self];
1233     }
1235     - (void)windowDidDeminiaturize:(NSNotification *)notification
1236     {
1237         WineApplicationController* controller = [WineApplicationController sharedController];
1239         if (!ignore_windowDeminiaturize)
1240         {
1241             macdrv_event* event;
1243             /* Coalesce events by discarding any previous ones still in the queue. */
1244             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1245                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1246                                    forWindow:self];
1248             event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1249             [queue postEvent:event];
1250             macdrv_release_event(event);
1251         }
1253         ignore_windowDeminiaturize = FALSE;
1255         if (fullscreen && [self isOnActiveSpace])
1256             [controller updateFullscreenWindows];
1257         [controller adjustWindowLevels];
1259         if (!self.disabled && !self.noActivate)
1260         {
1261             causing_becomeKeyWindow = TRUE;
1262             [self makeKeyWindow];
1263             causing_becomeKeyWindow = FALSE;
1264             [controller windowGotFocus:self];
1265         }
1267         [self windowDidResize:notification];
1268     }
1270     - (void) windowDidEndLiveResize:(NSNotification *)notification
1271     {
1272         [liveResizeDisplayTimer invalidate];
1273         [liveResizeDisplayTimer release];
1274         liveResizeDisplayTimer = nil;
1275     }
1277     - (void)windowDidMiniaturize:(NSNotification *)notification
1278     {
1279         if (fullscreen && [self isOnActiveSpace])
1280             [[WineApplicationController sharedController] updateFullscreenWindows];
1281     }
1283     - (void)windowDidMove:(NSNotification *)notification
1284     {
1285         [self windowDidResize:notification];
1286     }
1288     - (void)windowDidResignKey:(NSNotification *)notification
1289     {
1290         macdrv_event* event;
1292         if (causing_becomeKeyWindow) return;
1294         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1295         [queue postEvent:event];
1296         macdrv_release_event(event);
1297     }
1299     - (void)windowDidResize:(NSNotification *)notification
1300     {
1301         macdrv_event* event;
1302         NSRect frame = [self frame];
1304         if (self.disabled)
1305         {
1306             NSSize size = frame.size;
1307             [self setMinSize:size];
1308             [self setMaxSize:size];
1309         }
1311         frame = [self contentRectForFrameRect:frame];
1312         [[WineApplicationController sharedController] flipRect:&frame];
1314         /* Coalesce events by discarding any previous ones still in the queue. */
1315         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1316                                forWindow:self];
1318         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1319         event->window_frame_changed.frame = NSRectToCGRect(frame);
1320         [queue postEvent:event];
1321         macdrv_release_event(event);
1323         [[[self contentView] inputContext] invalidateCharacterCoordinates];
1324         [self updateFullscreen];
1325     }
1327     - (BOOL)windowShouldClose:(id)sender
1328     {
1329         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1330         [queue postEvent:event];
1331         macdrv_release_event(event);
1332         return NO;
1333     }
1335     - (void)windowWillMiniaturize:(NSNotification *)notification
1336     {
1337         if (!ignore_windowMiniaturize)
1338         {
1339             macdrv_event* event;
1341             /* Coalesce events by discarding any previous ones still in the queue. */
1342             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1343                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1344                                    forWindow:self];
1346             event = macdrv_create_event(WINDOW_DID_MINIMIZE, self);
1347             [queue postEvent:event];
1348             macdrv_release_event(event);
1349         }
1351         ignore_windowMiniaturize = FALSE;
1352     }
1354     - (void) windowWillStartLiveResize:(NSNotification *)notification
1355     {
1356         // There's a strange restriction in window redrawing during Cocoa-
1357         // managed window resizing.  Only calls to -[NSView setNeedsDisplay...]
1358         // that happen synchronously when Cocoa tells us that our window size
1359         // has changed or asynchronously in a short interval thereafter provoke
1360         // the window to redraw.  Calls to those methods that happen asynchronously
1361         // a half second or more after the last change of the window size aren't
1362         // heeded until the next resize-related user event (e.g. mouse movement).
1363         //
1364         // Wine often has a significant delay between when it's been told that
1365         // the window has changed size and when it can flush completed drawing.
1366         // So, our windows would get stuck with incomplete drawing for as long
1367         // as the user holds the mouse button down and doesn't move it.
1368         //
1369         // We address this by "manually" asking our windows to check if they need
1370         // redrawing every so often (during live resize only).
1371         [self windowDidEndLiveResize:nil];
1372         liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
1373                                                                   target:self
1374                                                                 selector:@selector(displayIfNeeded)
1375                                                                 userInfo:nil
1376                                                                  repeats:YES];
1377         [liveResizeDisplayTimer retain];
1378         [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
1379                                      forMode:NSRunLoopCommonModes];
1380     }
1383     /*
1384      * ---------- NSPasteboardOwner methods ----------
1385      */
1386     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
1387     {
1388         macdrv_query* query = macdrv_create_query();
1389         query->type = QUERY_PASTEBOARD_DATA;
1390         query->window = (macdrv_window)[self retain];
1391         query->pasteboard_data.type = (CFStringRef)[type copy];
1393         [self.queue query:query timeout:3];
1394         macdrv_release_query(query);
1395     }
1398     /*
1399      * ---------- NSDraggingDestination methods ----------
1400      */
1401     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
1402     {
1403         return [self draggingUpdated:sender];
1404     }
1406     - (void) draggingExited:(id <NSDraggingInfo>)sender
1407     {
1408         // This isn't really a query.  We don't need any response.  However, it
1409         // has to be processed in a similar manner as the other drag-and-drop
1410         // queries in order to maintain the proper order of operations.
1411         macdrv_query* query = macdrv_create_query();
1412         query->type = QUERY_DRAG_EXITED;
1413         query->window = (macdrv_window)[self retain];
1415         [self.queue query:query timeout:0.1];
1416         macdrv_release_query(query);
1417     }
1419     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
1420     {
1421         NSDragOperation ret;
1422         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1423         NSPasteboard* pb = [sender draggingPasteboard];
1425         macdrv_query* query = macdrv_create_query();
1426         query->type = QUERY_DRAG_OPERATION;
1427         query->window = (macdrv_window)[self retain];
1428         query->drag_operation.x = pt.x;
1429         query->drag_operation.y = pt.y;
1430         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
1431         query->drag_operation.accepted_op = NSDragOperationNone;
1432         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
1434         [self.queue query:query timeout:3];
1435         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
1436         macdrv_release_query(query);
1438         return ret;
1439     }
1441     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
1442     {
1443         BOOL ret;
1444         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1445         NSPasteboard* pb = [sender draggingPasteboard];
1447         macdrv_query* query = macdrv_create_query();
1448         query->type = QUERY_DRAG_DROP;
1449         query->window = (macdrv_window)[self retain];
1450         query->drag_drop.x = pt.x;
1451         query->drag_drop.y = pt.y;
1452         query->drag_drop.op = [sender draggingSourceOperationMask];
1453         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
1455         [self.queue query:query timeout:3 * 60 processEvents:YES];
1456         ret = query->status;
1457         macdrv_release_query(query);
1459         return ret;
1460     }
1462     - (BOOL) wantsPeriodicDraggingUpdates
1463     {
1464         return NO;
1465     }
1467 @end
1470 /***********************************************************************
1471  *              macdrv_create_cocoa_window
1473  * Create a Cocoa window with the given content frame and features (e.g.
1474  * title bar, close box, etc.).
1475  */
1476 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1477         CGRect frame, void* hwnd, macdrv_event_queue queue)
1479     __block WineWindow* window;
1481     OnMainThread(^{
1482         window = [[WineWindow createWindowWithFeatures:wf
1483                                            windowFrame:NSRectFromCGRect(frame)
1484                                                   hwnd:hwnd
1485                                                  queue:(WineEventQueue*)queue] retain];
1486     });
1488     return (macdrv_window)window;
1491 /***********************************************************************
1492  *              macdrv_destroy_cocoa_window
1494  * Destroy a Cocoa window.
1495  */
1496 void macdrv_destroy_cocoa_window(macdrv_window w)
1498     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1499     WineWindow* window = (WineWindow*)w;
1501     [window.queue discardEventsMatchingMask:-1 forWindow:window];
1502     [window close];
1503     [window release];
1505     [pool release];
1508 /***********************************************************************
1509  *              macdrv_get_window_hwnd
1511  * Get the hwnd that was set for the window at creation.
1512  */
1513 void* macdrv_get_window_hwnd(macdrv_window w)
1515     WineWindow* window = (WineWindow*)w;
1516     return window.hwnd;
1519 /***********************************************************************
1520  *              macdrv_set_cocoa_window_features
1522  * Update a Cocoa window's features.
1523  */
1524 void macdrv_set_cocoa_window_features(macdrv_window w,
1525         const struct macdrv_window_features* wf)
1527     WineWindow* window = (WineWindow*)w;
1529     OnMainThread(^{
1530         [window setWindowFeatures:wf];
1531     });
1534 /***********************************************************************
1535  *              macdrv_set_cocoa_window_state
1537  * Update a Cocoa window's state.
1538  */
1539 void macdrv_set_cocoa_window_state(macdrv_window w,
1540         const struct macdrv_window_state* state)
1542     WineWindow* window = (WineWindow*)w;
1544     OnMainThread(^{
1545         [window setMacDrvState:state];
1546     });
1549 /***********************************************************************
1550  *              macdrv_set_cocoa_window_title
1552  * Set a Cocoa window's title.
1553  */
1554 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1555         size_t length)
1557     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1558     WineWindow* window = (WineWindow*)w;
1559     NSString* titleString;
1561     if (title)
1562         titleString = [NSString stringWithCharacters:title length:length];
1563     else
1564         titleString = @"";
1565     OnMainThreadAsync(^{
1566         [window setTitle:titleString];
1567         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
1568             [NSApp changeWindowsItem:window title:titleString filename:NO];
1569     });
1571     [pool release];
1574 /***********************************************************************
1575  *              macdrv_order_cocoa_window
1577  * Reorder a Cocoa window relative to other windows.  If prev is
1578  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
1579  * it is ordered above that window.  Otherwise, it is ordered to the
1580  * front.
1582  * Returns true if the window has actually been ordered onto the screen
1583  * (i.e. if its frame intersects with a screen).  Otherwise, false.
1584  */
1585 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1586         macdrv_window next)
1588     WineWindow* window = (WineWindow*)w;
1589     __block BOOL on_screen;
1591     OnMainThread(^{
1592         on_screen = [window orderBelow:(WineWindow*)prev
1593                                orAbove:(WineWindow*)next];
1594     });
1596     return on_screen;
1599 /***********************************************************************
1600  *              macdrv_hide_cocoa_window
1602  * Hides a Cocoa window.
1603  */
1604 void macdrv_hide_cocoa_window(macdrv_window w)
1606     WineWindow* window = (WineWindow*)w;
1608     OnMainThread(^{
1609         [window doOrderOut];
1610     });
1613 /***********************************************************************
1614  *              macdrv_set_cocoa_window_frame
1616  * Move a Cocoa window.  If the window has been moved out of the bounds
1617  * of the desktop, it is ordered out.  (This routine won't ever order a
1618  * window in, though.)
1620  * Returns true if the window is on screen; false otherwise.
1621  */
1622 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1624     WineWindow* window = (WineWindow*)w;
1625     __block BOOL on_screen;
1627     OnMainThread(^{
1628         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1629     });
1631     return on_screen;
1634 /***********************************************************************
1635  *              macdrv_get_cocoa_window_frame
1637  * Gets the frame of a Cocoa window.
1638  */
1639 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1641     WineWindow* window = (WineWindow*)w;
1643     OnMainThread(^{
1644         NSRect frame;
1646         frame = [window contentRectForFrameRect:[window frame]];
1647         [[WineApplicationController sharedController] flipRect:&frame];
1648         *out_frame = NSRectToCGRect(frame);
1649     });
1652 /***********************************************************************
1653  *              macdrv_set_cocoa_parent_window
1655  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
1656  * the parent window.
1657  */
1658 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1660     WineWindow* window = (WineWindow*)w;
1662     OnMainThread(^{
1663         [window setMacDrvParentWindow:(WineWindow*)parent];
1664     });
1667 /***********************************************************************
1668  *              macdrv_set_window_surface
1669  */
1670 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1672     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1673     WineWindow* window = (WineWindow*)w;
1675     OnMainThread(^{
1676         window.surface = surface;
1677         window.surface_mutex = mutex;
1678     });
1680     [pool release];
1683 /***********************************************************************
1684  *              macdrv_window_needs_display
1686  * Mark a window as needing display in a specified rect (in non-client
1687  * area coordinates).
1688  */
1689 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1691     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1692     WineWindow* window = (WineWindow*)w;
1694     OnMainThreadAsync(^{
1695         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1696     });
1698     [pool release];
1701 /***********************************************************************
1702  *              macdrv_set_window_shape
1704  * Sets the shape of a Cocoa window from an array of rectangles.  If
1705  * rects is NULL, resets the window's shape to its frame.
1706  */
1707 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1709     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1710     WineWindow* window = (WineWindow*)w;
1712     OnMainThread(^{
1713         if (!rects || !count)
1714             window.shape = nil;
1715         else
1716         {
1717             NSBezierPath* path;
1718             unsigned int i;
1720             path = [NSBezierPath bezierPath];
1721             for (i = 0; i < count; i++)
1722                 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1723             window.shape = path;
1724         }
1725     });
1727     [pool release];
1730 /***********************************************************************
1731  *              macdrv_set_window_alpha
1732  */
1733 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1735     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1736     WineWindow* window = (WineWindow*)w;
1738     [window setAlphaValue:alpha];
1740     [pool release];
1743 /***********************************************************************
1744  *              macdrv_set_window_color_key
1745  */
1746 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1747                                  CGFloat keyBlue)
1749     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1750     WineWindow* window = (WineWindow*)w;
1752     OnMainThread(^{
1753         window.colorKeyed       = TRUE;
1754         window.colorKeyRed      = keyRed;
1755         window.colorKeyGreen    = keyGreen;
1756         window.colorKeyBlue     = keyBlue;
1757         [window checkTransparency];
1758     });
1760     [pool release];
1763 /***********************************************************************
1764  *              macdrv_clear_window_color_key
1765  */
1766 void macdrv_clear_window_color_key(macdrv_window w)
1768     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1769     WineWindow* window = (WineWindow*)w;
1771     OnMainThread(^{
1772         window.colorKeyed = FALSE;
1773         [window checkTransparency];
1774     });
1776     [pool release];
1779 /***********************************************************************
1780  *              macdrv_window_use_per_pixel_alpha
1781  */
1782 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1784     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1785     WineWindow* window = (WineWindow*)w;
1787     OnMainThread(^{
1788         window.usePerPixelAlpha = use_per_pixel_alpha;
1789         [window checkTransparency];
1790     });
1792     [pool release];
1795 /***********************************************************************
1796  *              macdrv_give_cocoa_window_focus
1798  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
1799  * orders it front and, if its frame was not within the desktop bounds,
1800  * Cocoa will typically move it on-screen.
1801  */
1802 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
1804     WineWindow* window = (WineWindow*)w;
1806     OnMainThread(^{
1807         [window makeFocused:activate];
1808     });
1811 /***********************************************************************
1812  *              macdrv_create_view
1814  * Creates and returns a view in the specified rect of the window.  The
1815  * caller is responsible for calling macdrv_dispose_view() on the view
1816  * when it is done with it.
1817  */
1818 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
1820     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1821     WineWindow* window = (WineWindow*)w;
1822     __block WineContentView* view;
1824     if (CGRectIsNull(rect)) rect = CGRectZero;
1826     OnMainThread(^{
1827         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1829         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
1830         [view setAutoresizesSubviews:NO];
1831         [nc addObserver:view
1832                selector:@selector(updateGLContexts)
1833                    name:NSViewGlobalFrameDidChangeNotification
1834                  object:view];
1835         [nc addObserver:view
1836                selector:@selector(updateGLContexts)
1837                    name:NSApplicationDidChangeScreenParametersNotification
1838                  object:NSApp];
1839         [[window contentView] addSubview:view];
1840     });
1842     [pool release];
1843     return (macdrv_view)view;
1846 /***********************************************************************
1847  *              macdrv_dispose_view
1849  * Destroys a view previously returned by macdrv_create_view.
1850  */
1851 void macdrv_dispose_view(macdrv_view v)
1853     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1854     WineContentView* view = (WineContentView*)v;
1856     OnMainThread(^{
1857         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1859         [nc removeObserver:view
1860                       name:NSViewGlobalFrameDidChangeNotification
1861                     object:view];
1862         [nc removeObserver:view
1863                       name:NSApplicationDidChangeScreenParametersNotification
1864                     object:NSApp];
1865         [view removeFromSuperview];
1866         [view release];
1867     });
1869     [pool release];
1872 /***********************************************************************
1873  *              macdrv_set_view_window_and_frame
1875  * Move a view to a new window and/or position within its window.  If w
1876  * is NULL, leave the view in its current window and just change its
1877  * frame.
1878  */
1879 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
1881     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1882     WineContentView* view = (WineContentView*)v;
1883     WineWindow* window = (WineWindow*)w;
1885     if (CGRectIsNull(rect)) rect = CGRectZero;
1887     OnMainThread(^{
1888         BOOL changedWindow = (window && window != [view window]);
1889         NSRect newFrame = NSRectFromCGRect(rect);
1890         NSRect oldFrame = [view frame];
1892         if (changedWindow)
1893         {
1894             [view removeFromSuperview];
1895             [[window contentView] addSubview:view];
1896         }
1898         if (!NSEqualRects(oldFrame, newFrame))
1899         {
1900             if (!changedWindow)
1901                 [[view superview] setNeedsDisplayInRect:oldFrame];
1902             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
1903                 [view setFrameSize:newFrame.size];
1904             else if (NSEqualSizes(oldFrame.size, newFrame.size))
1905                 [view setFrameOrigin:newFrame.origin];
1906             else
1907                 [view setFrame:newFrame];
1908             [view setNeedsDisplay:YES];
1909         }
1910     });
1912     [pool release];
1915 /***********************************************************************
1916  *              macdrv_add_view_opengl_context
1918  * Add an OpenGL context to the list being tracked for each view.
1919  */
1920 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1922     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1923     WineContentView* view = (WineContentView*)v;
1924     WineOpenGLContext *context = (WineOpenGLContext*)c;
1926     OnMainThreadAsync(^{
1927         [view addGLContext:context];
1928     });
1930     [pool release];
1933 /***********************************************************************
1934  *              macdrv_remove_view_opengl_context
1936  * Add an OpenGL context to the list being tracked for each view.
1937  */
1938 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1940     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1941     WineContentView* view = (WineContentView*)v;
1942     WineOpenGLContext *context = (WineOpenGLContext*)c;
1944     OnMainThreadAsync(^{
1945         [view removeGLContext:context];
1946     });
1948     [pool release];
1951 /***********************************************************************
1952  *              macdrv_window_background_color
1954  * Returns the standard Mac window background color as a 32-bit value of
1955  * the form 0x00rrggbb.
1956  */
1957 uint32_t macdrv_window_background_color(void)
1959     static uint32_t result;
1960     static dispatch_once_t once;
1962     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
1963     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
1964     // of it is to draw with it.
1965     dispatch_once(&once, ^{
1966         OnMainThread(^{
1967             unsigned char rgbx[4];
1968             unsigned char *planes = rgbx;
1969             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
1970                                                                                pixelsWide:1
1971                                                                                pixelsHigh:1
1972                                                                             bitsPerSample:8
1973                                                                           samplesPerPixel:3
1974                                                                                  hasAlpha:NO
1975                                                                                  isPlanar:NO
1976                                                                            colorSpaceName:NSCalibratedRGBColorSpace
1977                                                                              bitmapFormat:0
1978                                                                               bytesPerRow:4
1979                                                                              bitsPerPixel:32];
1980             [NSGraphicsContext saveGraphicsState];
1981             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
1982             [[NSColor windowBackgroundColor] set];
1983             NSRectFill(NSMakeRect(0, 0, 1, 1));
1984             [NSGraphicsContext restoreGraphicsState];
1985             [bitmap release];
1986             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
1987         });
1988     });
1990     return result;
1993 /***********************************************************************
1994  *              macdrv_send_text_input_event
1995  */
1996 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
1998     __block BOOL ret;
2000     OnMainThread(^{
2001         WineWindow* window = (WineWindow*)[NSApp keyWindow];
2002         if (![window isKindOfClass:[WineWindow class]])
2003         {
2004             window = (WineWindow*)[NSApp mainWindow];
2005             if (![window isKindOfClass:[WineWindow class]])
2006                 window = [[WineApplicationController sharedController] frontWineWindow];
2007         }
2009         if (window)
2010         {
2011             NSUInteger localFlags = flags;
2012             CGEventRef c;
2013             NSEvent* event;
2015             window.imeData = data;
2016             fix_device_modifiers_by_generic(&localFlags);
2018             // An NSEvent created with +keyEventWithType:... is internally marked
2019             // as synthetic and doesn't get sent through input methods.  But one
2020             // created from a CGEvent doesn't have that problem.
2021             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
2022             CGEventSetFlags(c, localFlags);
2023             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
2024             event = [NSEvent eventWithCGEvent:c];
2025             CFRelease(c);
2027             window.commandDone = FALSE;
2028             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
2029         }
2030         else
2031             ret = FALSE;
2032     });
2034     return ret;