winemac: Don't order the window on-screen in -[WineWindow adjustWindowLevel].
[wine] / dlls / winemac.drv / cocoa_window.m
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  */
20
21 #import <Carbon/Carbon.h>
22
23 #import "cocoa_window.h"
24
25 #include "macdrv_cocoa.h"
26 #import "cocoa_app.h"
27 #import "cocoa_event.h"
28 #import "cocoa_opengl.h"
29
30
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 */
34 };
35
36
37 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
38 {
39     NSUInteger style_mask;
40
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;
50
51     return style_mask;
52 }
53
54
55 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
56 {
57     NSScreen* screen;
58     for (screen in screens)
59     {
60         if (NSIntersectsRect(frame, [screen frame]))
61             return TRUE;
62     }
63     return FALSE;
64 }
65
66
67 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
68 {
69     for (NSScreen* screen in screens)
70     {
71         if (NSContainsRect(rect, [screen frame]))
72             return screen;
73     }
74     return nil;
75 }
76
77
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)
84 {
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;
93 }
94
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)
101 {
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;
118 }
119
120
121 @interface WineContentView : NSView
122 {
123     NSMutableArray* glContexts;
124     NSMutableArray* pendingGlContexts;
125 }
126
127     - (void) addGLContext:(WineOpenGLContext*)context;
128     - (void) removeGLContext:(WineOpenGLContext*)context;
129     - (void) updateGLContexts;
130
131 @end
132
133
134 @interface WineWindow ()
135
136 @property (nonatomic) BOOL disabled;
137 @property (nonatomic) BOOL noActivate;
138 @property (readwrite, nonatomic) BOOL floating;
139 @property (retain, nonatomic) NSWindow* latentParentWindow;
140
141 @property (nonatomic) void* hwnd;
142 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
143
144 @property (nonatomic) void* surface;
145 @property (nonatomic) pthread_mutex_t* surface_mutex;
146
147 @property (copy, nonatomic) NSBezierPath* shape;
148 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
149 @property (readonly, nonatomic) BOOL needsTransparency;
150
151 @property (nonatomic) BOOL colorKeyed;
152 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
153 @property (nonatomic) BOOL usePerPixelAlpha;
154
155 @property (readwrite, nonatomic) NSInteger levelWhenActive;
156
157 @end
158
159
160 @implementation WineContentView
161
162     - (void) dealloc
163     {
164         [glContexts release];
165         [pendingGlContexts release];
166         [super dealloc];
167     }
168
169     - (BOOL) isFlipped
170     {
171         return YES;
172     }
173
174     - (void) drawRect:(NSRect)rect
175     {
176         WineWindow* window = (WineWindow*)[self window];
177
178         for (WineOpenGLContext* context in pendingGlContexts)
179             context.needsUpdate = TRUE;
180         [glContexts addObjectsFromArray:pendingGlContexts];
181         [pendingGlContexts removeAllObjects];
182
183         if ([window contentView] != self)
184             return;
185
186         if (window.surface && window.surface_mutex &&
187             !pthread_mutex_lock(window.surface_mutex))
188         {
189             const CGRect* rects;
190             int count;
191
192             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
193             {
194                 CGContextRef context;
195                 int i;
196
197                 [window.shape addClip];
198
199                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
200                 CGContextSetBlendMode(context, kCGBlendModeCopy);
201
202                 for (i = 0; i < count; i++)
203                 {
204                     CGRect imageRect;
205                     CGImageRef image;
206
207                     imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
208                     image = create_surface_image(window.surface, &imageRect, FALSE);
209
210                     if (image)
211                     {
212                         if (window.colorKeyed)
213                         {
214                             CGImageRef maskedImage;
215                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
216                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
217                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
218                             maskedImage = CGImageCreateWithMaskingColors(image, components);
219                             if (maskedImage)
220                             {
221                                 CGImageRelease(image);
222                                 image = maskedImage;
223                             }
224                         }
225
226                         CGContextDrawImage(context, imageRect, image);
227
228                         CGImageRelease(image);
229                     }
230                 }
231             }
232
233             pthread_mutex_unlock(window.surface_mutex);
234         }
235
236         // If the window may be transparent, then we have to invalidate the
237         // shadow every time we draw.  Also, if this is the first time we've
238         // drawn since changing from transparent to opaque.
239         if (![window isOpaque] || window.shapeChangedSinceLastDraw)
240         {
241             window.shapeChangedSinceLastDraw = FALSE;
242             [window invalidateShadow];
243         }
244     }
245
246     /* By default, NSView will swallow right-clicks in an attempt to support contextual
247        menus.  We need to bypass that and allow the event to make it to the window. */
248     - (void) rightMouseDown:(NSEvent*)theEvent
249     {
250         [[self window] rightMouseDown:theEvent];
251     }
252
253     - (void) addGLContext:(WineOpenGLContext*)context
254     {
255         if (!glContexts)
256             glContexts = [[NSMutableArray alloc] init];
257         if (!pendingGlContexts)
258             pendingGlContexts = [[NSMutableArray alloc] init];
259         [pendingGlContexts addObject:context];
260         [self setNeedsDisplay:YES];
261     }
262
263     - (void) removeGLContext:(WineOpenGLContext*)context
264     {
265         [glContexts removeObjectIdenticalTo:context];
266         [pendingGlContexts removeObjectIdenticalTo:context];
267     }
268
269     - (void) updateGLContexts
270     {
271         for (WineOpenGLContext* context in glContexts)
272             context.needsUpdate = TRUE;
273     }
274
275     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
276     {
277         return YES;
278     }
279
280     - (BOOL) preservesContentDuringLiveResize
281     {
282         // Returning YES from this tells Cocoa to keep our view's content during
283         // a Cocoa-driven resize.  In theory, we're also supposed to override
284         // -setFrameSize: to mark exposed sections as needing redisplay, but
285         // user32 will take care of that in a roundabout way.  This way, we don't
286         // redraw until the window surface is flushed.
287         //
288         // This doesn't do anything when we resize the window ourselves.
289         return YES;
290     }
291
292 @end
293
294
295 @implementation WineWindow
296
297     @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
298     @synthesize surface, surface_mutex;
299     @synthesize shape, shapeChangedSinceLastDraw;
300     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
301     @synthesize usePerPixelAlpha;
302     @synthesize levelWhenActive;
303
304     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
305                                  windowFrame:(NSRect)window_frame
306                                         hwnd:(void*)hwnd
307                                        queue:(WineEventQueue*)queue
308     {
309         WineWindow* window;
310         WineContentView* contentView;
311         NSTrackingArea* trackingArea;
312
313         [NSApp flipRect:&window_frame];
314
315         window = [[[self alloc] initWithContentRect:window_frame
316                                           styleMask:style_mask_for_features(wf)
317                                             backing:NSBackingStoreBuffered
318                                               defer:YES] autorelease];
319
320         if (!window) return nil;
321         window->normalStyleMask = [window styleMask];
322
323         /* Standardize windows to eliminate differences between titled and
324            borderless windows and between NSWindow and NSPanel. */
325         [window setHidesOnDeactivate:NO];
326         [window setReleasedWhenClosed:NO];
327
328         [window disableCursorRects];
329         [window setShowsResizeIndicator:NO];
330         [window setHasShadow:wf->shadow];
331         [window setAcceptsMouseMovedEvents:YES];
332         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
333         [window setDelegate:window];
334         window.hwnd = hwnd;
335         window.queue = queue;
336
337         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
338                                                                   (NSString*)kUTTypeContent,
339                                                                   nil]];
340
341         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
342         if (!contentView)
343             return nil;
344         [contentView setAutoresizesSubviews:NO];
345
346         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
347            because they give us mouse moves in the background. */
348         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
349                                                      options:(NSTrackingMouseMoved |
350                                                               NSTrackingActiveAlways |
351                                                               NSTrackingInVisibleRect)
352                                                        owner:window
353                                                     userInfo:nil] autorelease];
354         if (!trackingArea)
355             return nil;
356         [contentView addTrackingArea:trackingArea];
357
358         [window setContentView:contentView];
359
360         return window;
361     }
362
363     - (void) dealloc
364     {
365         [liveResizeDisplayTimer invalidate];
366         [liveResizeDisplayTimer release];
367         [queue release];
368         [latentParentWindow release];
369         [shape release];
370         [super dealloc];
371     }
372
373     - (void) adjustFeaturesForState
374     {
375         NSUInteger style = normalStyleMask;
376
377         if (self.disabled)
378             style &= ~NSResizableWindowMask;
379         if (style != [self styleMask])
380             [self setStyleMask:style];
381
382         if (style & NSClosableWindowMask)
383             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
384         if (style & NSMiniaturizableWindowMask)
385             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
386     }
387
388     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
389     {
390         normalStyleMask = style_mask_for_features(wf);
391         [self adjustFeaturesForState];
392         [self setHasShadow:wf->shadow];
393     }
394
395     - (void) adjustWindowLevel
396     {
397         NSInteger level;
398         BOOL fullscreen, captured;
399         NSScreen* screen;
400         NSUInteger index;
401         WineWindow* other = nil;
402
403         screen = screen_covered_by_rect([self frame], [NSScreen screens]);
404         fullscreen = (screen != nil);
405         captured = (screen || [self screen]) && [NSApp areDisplaysCaptured];
406
407         if (captured || fullscreen)
408         {
409             if (captured)
410                 level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
411             else
412                 level = NSMainMenuWindowLevel + 1;
413
414             if (self.floating)
415                 level++;
416         }
417         else if (self.floating)
418             level = NSFloatingWindowLevel;
419         else
420             level = NSNormalWindowLevel;
421
422         index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:self];
423         if (index != NSNotFound && index + 1 < [[NSApp orderedWineWindows] count])
424         {
425             other = [[NSApp orderedWineWindows] objectAtIndex:index + 1];
426             if (level < [other level])
427                 level = [other level];
428         }
429
430         if (level != [self level])
431         {
432             [self setLevelWhenActive:level];
433
434             /* Setting the window level above has moved this window to the front
435                of all other windows at the same level.  We need to move it
436                back into its proper place among other windows of that level.
437                Also, any windows which are supposed to be in front of it had
438                better have the same or higher window level.  If not, bump them
439                up. */
440             if (index != NSNotFound && [self isVisible])
441             {
442                 for (; index > 0; index--)
443                 {
444                     other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
445                     if ([other level] < level)
446                         [other setLevelWhenActive:level];
447                     else
448                     {
449                         [self orderWindow:NSWindowBelow relativeTo:[other windowNumber]];
450                         break;
451                     }
452                 }
453             }
454         }
455     }
456
457     - (void) setMacDrvState:(const struct macdrv_window_state*)state
458     {
459         NSWindowCollectionBehavior behavior;
460
461         self.disabled = state->disabled;
462         self.noActivate = state->no_activate;
463
464         self.floating = state->floating;
465         [self adjustWindowLevel];
466
467         behavior = NSWindowCollectionBehaviorDefault;
468         if (state->excluded_by_expose)
469             behavior |= NSWindowCollectionBehaviorTransient;
470         else
471             behavior |= NSWindowCollectionBehaviorManaged;
472         if (state->excluded_by_cycle)
473         {
474             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
475             if ([self isVisible])
476                 [NSApp removeWindowsItem:self];
477         }
478         else
479         {
480             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
481             if ([self isVisible])
482                 [NSApp addWindowsItem:self title:[self title] filename:NO];
483         }
484         [self setCollectionBehavior:behavior];
485
486         if (state->minimized && ![self isMiniaturized])
487         {
488             ignore_windowMiniaturize = TRUE;
489             [self miniaturize:nil];
490         }
491         else if (!state->minimized && [self isMiniaturized])
492         {
493             ignore_windowDeminiaturize = TRUE;
494             [self deminiaturize:nil];
495         }
496
497         /* Whatever events regarding minimization might have been in the queue are now stale. */
498         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
499                                          event_mask_for_type(WINDOW_DID_UNMINIMIZE)
500                                forWindow:self];
501     }
502
503     /* Returns whether or not the window was ordered in, which depends on if
504        its frame intersects any screen. */
505     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
506     {
507         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
508         if (on_screen)
509         {
510             [NSApp transformProcessToForeground];
511
512             if (prev)
513             {
514                 /* Make sure that windows that should be above this one really are.
515                    This is necessary since a full-screen window gets a boost to its
516                    window level to be in front of the menu bar and Dock and that moves
517                    it out of the z-order that Win32 would otherwise establish. */
518                 if ([prev level] < [self level])
519                 {
520                     NSUInteger index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:prev];
521                     if (index != NSNotFound)
522                     {
523                         [prev setLevelWhenActive:[self level]];
524                         for (; index > 0; index--)
525                         {
526                             WineWindow* other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
527                             if ([other level] < [self level])
528                                 [other setLevelWhenActive:[self level]];
529                         }
530                     }
531                 }
532                 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
533                 [NSApp wineWindow:self ordered:NSWindowBelow relativeTo:prev];
534             }
535             else
536             {
537                 /* Similarly, make sure this window is really above what it should be. */
538                 if (next && [next level] > [self level])
539                     [self setLevelWhenActive:[next level]];
540                 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
541                 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:next];
542             }
543             if (latentParentWindow)
544             {
545                 if ([latentParentWindow level] > [self level])
546                     [self setLevelWhenActive:[latentParentWindow level]];
547                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
548                 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
549                 self.latentParentWindow = nil;
550             }
551
552             /* Cocoa may adjust the frame when the window is ordered onto the screen.
553                Generate a frame-changed event just in case.  The back end will ignore
554                it if nothing actually changed. */
555             [self windowDidResize:nil];
556
557             if (![self isExcludedFromWindowsMenu])
558                 [NSApp addWindowsItem:self title:[self title] filename:NO];
559         }
560
561         return on_screen;
562     }
563
564     - (void) doOrderOut
565     {
566         self.latentParentWindow = [self parentWindow];
567         [latentParentWindow removeChildWindow:self];
568         [self orderOut:nil];
569         [NSApp wineWindow:self ordered:NSWindowOut relativeTo:nil];
570         [NSApp removeWindowsItem:self];
571     }
572
573     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
574     {
575         NSArray* screens = [NSScreen screens];
576         BOOL on_screen = [self isVisible];
577         NSRect frame, oldFrame;
578
579         if (![screens count]) return on_screen;
580
581         /* Origin is (left, top) in a top-down space.  Need to convert it to
582            (left, bottom) in a bottom-up space. */
583         [NSApp flipRect:&contentRect];
584
585         if (on_screen)
586         {
587             on_screen = frame_intersects_screens(contentRect, screens);
588             if (!on_screen)
589                 [self doOrderOut];
590         }
591
592         if (!NSIsEmptyRect(contentRect))
593         {
594             oldFrame = [self frame];
595             frame = [self frameRectForContentRect:contentRect];
596             if (!NSEqualRects(frame, oldFrame))
597             {
598                 if (NSEqualSizes(frame.size, oldFrame.size))
599                     [self setFrameOrigin:frame.origin];
600                 else
601                     [self setFrame:frame display:YES];
602             }
603         }
604
605         if (on_screen)
606         {
607             [self adjustWindowLevel];
608
609             /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
610                event.  The back end will ignore it if nothing actually changed. */
611             [self windowDidResize:nil];
612         }
613         else
614         {
615             /* The back end is establishing a new window size and position.  It's
616                not interested in any stale events regarding those that may be sitting
617                in the queue. */
618             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
619                                    forWindow:self];
620         }
621
622         return on_screen;
623     }
624
625     - (void) setMacDrvParentWindow:(WineWindow*)parent
626     {
627         if ([self parentWindow] != parent)
628         {
629             [[self parentWindow] removeChildWindow:self];
630             self.latentParentWindow = nil;
631             if ([self isVisible] && parent)
632             {
633                 if ([parent level] > [self level])
634                     [self setLevelWhenActive:[parent level]];
635                 [parent addChildWindow:self ordered:NSWindowAbove];
636                 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:parent];
637             }
638             else
639                 self.latentParentWindow = parent;
640         }
641     }
642
643     - (void) setDisabled:(BOOL)newValue
644     {
645         if (disabled != newValue)
646         {
647             disabled = newValue;
648             [self adjustFeaturesForState];
649         }
650     }
651
652     - (BOOL) needsTransparency
653     {
654         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
655     }
656
657     - (void) checkTransparency
658     {
659         if (![self isOpaque] && !self.needsTransparency)
660         {
661             [self setBackgroundColor:[NSColor windowBackgroundColor]];
662             [self setOpaque:YES];
663         }
664         else if ([self isOpaque] && self.needsTransparency)
665         {
666             [self setBackgroundColor:[NSColor clearColor]];
667             [self setOpaque:NO];
668         }
669     }
670
671     - (void) setShape:(NSBezierPath*)newShape
672     {
673         if (shape == newShape) return;
674         if (shape && newShape && [shape isEqual:newShape]) return;
675
676         if (shape)
677         {
678             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
679             [shape release];
680         }
681         if (newShape)
682             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
683
684         shape = [newShape copy];
685         self.shapeChangedSinceLastDraw = TRUE;
686
687         [self checkTransparency];
688     }
689
690     - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
691     {
692         CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
693         macdrv_event* event;
694
695         event = macdrv_create_event(MOUSE_BUTTON, self);
696         event->mouse_button.button = [theEvent buttonNumber];
697         event->mouse_button.pressed = pressed;
698         event->mouse_button.x = pt.x;
699         event->mouse_button.y = pt.y;
700         event->mouse_button.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
701
702         [queue postEvent:event];
703
704         macdrv_release_event(event);
705     }
706
707     - (void) makeFocused
708     {
709         NSArray* screens;
710
711         [NSApp transformProcessToForeground];
712
713         /* If a borderless window is offscreen, orderFront: won't move
714            it onscreen like it would for a titled window.  Do that ourselves. */
715         screens = [NSScreen screens];
716         if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
717             !frame_intersects_screens([self frame], screens))
718         {
719             NSScreen* primaryScreen = [screens objectAtIndex:0];
720             NSRect frame = [primaryScreen frame];
721             [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
722             frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
723             [self setFrame:frame display:YES];
724         }
725
726         if ([[NSApp orderedWineWindows] count])
727         {
728             WineWindow* front;
729             if (self.floating)
730                 front = [[NSApp orderedWineWindows] objectAtIndex:0];
731             else
732             {
733                 for (front in [NSApp orderedWineWindows])
734                     if (!front.floating) break;
735             }
736             if (front && [front levelWhenActive] > [self levelWhenActive])
737                 [self setLevelWhenActive:[front levelWhenActive]];
738         }
739         [self orderFront:nil];
740         [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
741         causing_becomeKeyWindow = TRUE;
742         [self makeKeyWindow];
743         causing_becomeKeyWindow = FALSE;
744         if (latentParentWindow)
745         {
746             if ([latentParentWindow level] > [self level])
747                 [self setLevelWhenActive:[latentParentWindow level]];
748             [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
749             [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
750             self.latentParentWindow = nil;
751         }
752         if (![self isExcludedFromWindowsMenu])
753             [NSApp addWindowsItem:self title:[self title] filename:NO];
754
755         /* Cocoa may adjust the frame when the window is ordered onto the screen.
756            Generate a frame-changed event just in case.  The back end will ignore
757            it if nothing actually changed. */
758         [self windowDidResize:nil];
759     }
760
761     - (void) postKey:(uint16_t)keyCode
762              pressed:(BOOL)pressed
763            modifiers:(NSUInteger)modifiers
764                event:(NSEvent*)theEvent
765     {
766         macdrv_event* event;
767         CGEventRef cgevent;
768         WineApplication* app = (WineApplication*)NSApp;
769
770         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
771         event->key.keycode   = keyCode;
772         event->key.modifiers = modifiers;
773         event->key.time_ms   = [app ticksForEventTime:[theEvent timestamp]];
774
775         if ((cgevent = [theEvent CGEvent]))
776         {
777             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
778                                                         kCGKeyboardEventKeyboardType);
779             if (keyboardType != app.keyboardType)
780             {
781                 app.keyboardType = keyboardType;
782                 [app keyboardSelectionDidChange];
783             }
784         }
785
786         [queue postEvent:event];
787
788         macdrv_release_event(event);
789     }
790
791     - (void) postKeyEvent:(NSEvent *)theEvent
792     {
793         [self flagsChanged:theEvent];
794         [self postKey:[theEvent keyCode]
795               pressed:[theEvent type] == NSKeyDown
796             modifiers:[theEvent modifierFlags]
797                 event:theEvent];
798     }
799
800     - (void) postMouseMovedEvent:(NSEvent *)theEvent absolute:(BOOL)absolute
801     {
802         macdrv_event* event;
803
804         if (absolute)
805         {
806             CGPoint point = CGEventGetLocation([theEvent CGEvent]);
807
808             event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, self);
809             event->mouse_moved.x = point.x;
810             event->mouse_moved.y = point.y;
811
812             mouseMoveDeltaX = 0;
813             mouseMoveDeltaY = 0;
814         }
815         else
816         {
817             /* Add event delta to accumulated delta error */
818             /* deltaY is already flipped */
819             mouseMoveDeltaX += [theEvent deltaX];
820             mouseMoveDeltaY += [theEvent deltaY];
821
822             event = macdrv_create_event(MOUSE_MOVED, self);
823             event->mouse_moved.x = mouseMoveDeltaX;
824             event->mouse_moved.y = mouseMoveDeltaY;
825
826             /* Keep the remainder after integer truncation. */
827             mouseMoveDeltaX -= event->mouse_moved.x;
828             mouseMoveDeltaY -= event->mouse_moved.y;
829         }
830
831         if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
832         {
833             event->mouse_moved.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
834
835             [queue postEvent:event];
836         }
837
838         macdrv_release_event(event);
839     }
840
841     - (void) setLevelWhenActive:(NSInteger)level
842     {
843         levelWhenActive = level;
844         if (([NSApp isActive] || level <= NSFloatingWindowLevel) &&
845             level != [self level])
846             [self setLevel:level];
847     }
848
849
850     /*
851      * ---------- NSWindow method overrides ----------
852      */
853     - (BOOL) canBecomeKeyWindow
854     {
855         if (causing_becomeKeyWindow) return YES;
856         if (self.disabled || self.noActivate) return NO;
857         return [self isKeyWindow];
858     }
859
860     - (BOOL) canBecomeMainWindow
861     {
862         return [self canBecomeKeyWindow];
863     }
864
865     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
866     {
867         // If a window is sized to completely cover a screen, then it's in
868         // full-screen mode.  In that case, we don't allow NSWindow to constrain
869         // it.
870         NSRect contentRect = [self contentRectForFrameRect:frameRect];
871         if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
872             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
873         return frameRect;
874     }
875
876     - (BOOL) isExcludedFromWindowsMenu
877     {
878         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
879     }
880
881     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
882     {
883         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
884             return [self isKeyWindow] || (!self.disabled && !self.noActivate);
885         return [super validateMenuItem:menuItem];
886     }
887
888     /* We don't call this.  It's the action method of the items in the Window menu. */
889     - (void) makeKeyAndOrderFront:(id)sender
890     {
891         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
892             [NSApp windowGotFocus:self];
893     }
894
895     - (void) sendEvent:(NSEvent*)event
896     {
897         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
898            interface control.  For example, Control-Tab switches focus among
899            views.  We want to bypass that feature, so directly route key-down
900            events to -keyDown:. */
901         if ([event type] == NSKeyDown)
902             [[self firstResponder] keyDown:event];
903         else
904         {
905             if ([event type] == NSLeftMouseDown)
906             {
907                 NSWindowButton windowButton;
908                 BOOL broughtWindowForward = TRUE;
909
910                 /* Since our windows generally claim they can't be made key, clicks
911                    in their title bars are swallowed by the theme frame stuff.  So,
912                    we hook directly into the event stream and assume that any click
913                    in the window will activate it, if Wine and the Win32 program
914                    accept. */
915                 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
916                     [NSApp windowGotFocus:self];
917
918                 /* Any left-click on our window anyplace other than the close or
919                    minimize buttons will bring it forward. */
920                 for (windowButton = NSWindowCloseButton;
921                      windowButton <= NSWindowMiniaturizeButton;
922                      windowButton++)
923                 {
924                     NSButton* button = [[event window] standardWindowButton:windowButton];
925                     if (button)
926                     {
927                         NSPoint point = [button convertPoint:[event locationInWindow] fromView:nil];
928                         if ([button mouse:point inRect:[button bounds]])
929                         {
930                             broughtWindowForward = FALSE;
931                             break;
932                         }
933                     }
934                 }
935
936                 if (broughtWindowForward)
937                     [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
938             }
939
940             [super sendEvent:event];
941         }
942     }
943
944
945     /*
946      * ---------- NSResponder method overrides ----------
947      */
948     - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
949     - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
950     - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
951
952     - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
953     - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
954     - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
955
956     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
957     - (void) keyUp:(NSEvent *)theEvent   { [self postKeyEvent:theEvent]; }
958
959     - (void) flagsChanged:(NSEvent *)theEvent
960     {
961         static const struct {
962             NSUInteger  mask;
963             uint16_t    keycode;
964         } modifiers[] = {
965             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
966             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
967             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
968             { NX_DEVICELCTLKEYMASK,     kVK_Control },
969             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
970             { NX_DEVICELALTKEYMASK,     kVK_Option },
971             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
972             { NX_DEVICELCMDKEYMASK,     kVK_Command },
973             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
974         };
975
976         NSUInteger modifierFlags = [theEvent modifierFlags];
977         NSUInteger changed;
978         int i, last_changed;
979
980         fix_device_modifiers_by_generic(&modifierFlags);
981         changed = modifierFlags ^ lastModifierFlags;
982
983         last_changed = -1;
984         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
985             if (changed & modifiers[i].mask)
986                 last_changed = i;
987
988         for (i = 0; i <= last_changed; i++)
989         {
990             if (changed & modifiers[i].mask)
991             {
992                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
993
994                 if (i == last_changed)
995                     lastModifierFlags = modifierFlags;
996                 else
997                 {
998                     lastModifierFlags ^= modifiers[i].mask;
999                     fix_generic_modifiers_by_device(&lastModifierFlags);
1000                 }
1001
1002                 // Caps lock generates one event for each press-release action.
1003                 // We need to simulate a pair of events for each actual event.
1004                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1005                 {
1006                     [self postKey:modifiers[i].keycode
1007                           pressed:TRUE
1008                         modifiers:lastModifierFlags
1009                             event:(NSEvent*)theEvent];
1010                     pressed = FALSE;
1011                 }
1012
1013                 [self postKey:modifiers[i].keycode
1014                       pressed:pressed
1015                     modifiers:lastModifierFlags
1016                         event:(NSEvent*)theEvent];
1017             }
1018         }
1019     }
1020
1021     - (void) scrollWheel:(NSEvent *)theEvent
1022     {
1023         CGPoint pt;
1024         macdrv_event* event;
1025         CGEventRef cgevent;
1026         CGFloat x, y;
1027         BOOL continuous = FALSE;
1028
1029         cgevent = [theEvent CGEvent];
1030         pt = CGEventGetLocation(cgevent);
1031
1032         event = macdrv_create_event(MOUSE_SCROLL, self);
1033         event->mouse_scroll.x = pt.x;
1034         event->mouse_scroll.y = pt.y;
1035         event->mouse_scroll.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
1036
1037         if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1038         {
1039             continuous = TRUE;
1040
1041             /* Continuous scroll wheel events come from high-precision scrolling
1042                hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1043                For these, we can get more precise data from the CGEvent API. */
1044             /* Axis 1 is vertical, axis 2 is horizontal. */
1045             x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1046             y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1047         }
1048         else
1049         {
1050             double pixelsPerLine = 10;
1051             CGEventSourceRef source;
1052
1053             /* The non-continuous values are in units of "lines", not pixels. */
1054             if ((source = CGEventCreateSourceFromEvent(cgevent)))
1055             {
1056                 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1057                 CFRelease(source);
1058             }
1059
1060             x = pixelsPerLine * [theEvent deltaX];
1061             y = pixelsPerLine * [theEvent deltaY];
1062         }
1063
1064         /* Mac: negative is right or down, positive is left or up.
1065            Win32: negative is left or down, positive is right or up.
1066            So, negate the X scroll value to translate. */
1067         x = -x;
1068
1069         /* The x,y values so far are in pixels.  Win32 expects to receive some
1070            fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1071            6 times the pixel value. */
1072         event->mouse_scroll.x_scroll = 6 * x;
1073         event->mouse_scroll.y_scroll = 6 * y;
1074
1075         if (!continuous)
1076         {
1077             /* For non-continuous "clicky" wheels, if there was any motion, make
1078                sure there was at least WHEEL_DELTA motion.  This is so, at slow
1079                speeds where the system's acceleration curve is actually reducing the
1080                scroll distance, the user is sure to get some action out of each click.
1081                For example, this is important for rotating though weapons in a
1082                first-person shooter. */
1083             if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1084                 event->mouse_scroll.x_scroll = 120;
1085             else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1086                 event->mouse_scroll.x_scroll = -120;
1087
1088             if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1089                 event->mouse_scroll.y_scroll = 120;
1090             else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1091                 event->mouse_scroll.y_scroll = -120;
1092         }
1093
1094         if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1095             [queue postEvent:event];
1096
1097         macdrv_release_event(event);
1098     }
1099
1100
1101     /*
1102      * ---------- NSWindowDelegate methods ----------
1103      */
1104     - (void)windowDidBecomeKey:(NSNotification *)notification
1105     {
1106         NSEvent* event = [NSApp lastFlagsChanged];
1107         if (event)
1108             [self flagsChanged:event];
1109
1110         if (causing_becomeKeyWindow) return;
1111
1112         [NSApp windowGotFocus:self];
1113     }
1114
1115     - (void)windowDidDeminiaturize:(NSNotification *)notification
1116     {
1117         if (!ignore_windowDeminiaturize)
1118         {
1119             macdrv_event* event;
1120
1121             /* Coalesce events by discarding any previous ones still in the queue. */
1122             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1123                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1124                                    forWindow:self];
1125
1126             event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1127             [queue postEvent:event];
1128             macdrv_release_event(event);
1129         }
1130
1131         ignore_windowDeminiaturize = FALSE;
1132
1133         [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1134     }
1135
1136     - (void) windowDidEndLiveResize:(NSNotification *)notification
1137     {
1138         [liveResizeDisplayTimer invalidate];
1139         [liveResizeDisplayTimer release];
1140         liveResizeDisplayTimer = nil;
1141     }
1142
1143     - (void)windowDidMove:(NSNotification *)notification
1144     {
1145         [self windowDidResize:notification];
1146     }
1147
1148     - (void)windowDidResignKey:(NSNotification *)notification
1149     {
1150         macdrv_event* event;
1151
1152         if (causing_becomeKeyWindow) return;
1153
1154         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1155         [queue postEvent:event];
1156         macdrv_release_event(event);
1157     }
1158
1159     - (void)windowDidResize:(NSNotification *)notification
1160     {
1161         macdrv_event* event;
1162         NSRect frame = [self contentRectForFrameRect:[self frame]];
1163
1164         [NSApp flipRect:&frame];
1165
1166         /* Coalesce events by discarding any previous ones still in the queue. */
1167         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1168                                forWindow:self];
1169
1170         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1171         event->window_frame_changed.frame = NSRectToCGRect(frame);
1172         [queue postEvent:event];
1173         macdrv_release_event(event);
1174     }
1175
1176     - (BOOL)windowShouldClose:(id)sender
1177     {
1178         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1179         [queue postEvent:event];
1180         macdrv_release_event(event);
1181         return NO;
1182     }
1183
1184     - (void)windowWillMiniaturize:(NSNotification *)notification
1185     {
1186         if (!ignore_windowMiniaturize)
1187         {
1188             macdrv_event* event;
1189
1190             /* Coalesce events by discarding any previous ones still in the queue. */
1191             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1192                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1193                                    forWindow:self];
1194
1195             event = macdrv_create_event(WINDOW_DID_MINIMIZE, self);
1196             [queue postEvent:event];
1197             macdrv_release_event(event);
1198         }
1199
1200         ignore_windowMiniaturize = FALSE;
1201     }
1202
1203     - (void) windowWillStartLiveResize:(NSNotification *)notification
1204     {
1205         // There's a strange restriction in window redrawing during Cocoa-
1206         // managed window resizing.  Only calls to -[NSView setNeedsDisplay...]
1207         // that happen synchronously when Cocoa tells us that our window size
1208         // has changed or asynchronously in a short interval thereafter provoke
1209         // the window to redraw.  Calls to those methods that happen asynchronously
1210         // a half second or more after the last change of the window size aren't
1211         // heeded until the next resize-related user event (e.g. mouse movement).
1212         //
1213         // Wine often has a significant delay between when it's been told that
1214         // the window has changed size and when it can flush completed drawing.
1215         // So, our windows would get stuck with incomplete drawing for as long
1216         // as the user holds the mouse button down and doesn't move it.
1217         //
1218         // We address this by "manually" asking our windows to check if they need
1219         // redrawing every so often (during live resize only).
1220         [self windowDidEndLiveResize:nil];
1221         liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
1222                                                                   target:self
1223                                                                 selector:@selector(displayIfNeeded)
1224                                                                 userInfo:nil
1225                                                                  repeats:YES];
1226         [liveResizeDisplayTimer retain];
1227         [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
1228                                      forMode:NSRunLoopCommonModes];
1229     }
1230
1231
1232     /*
1233      * ---------- NSPasteboardOwner methods ----------
1234      */
1235     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
1236     {
1237         macdrv_query* query = macdrv_create_query();
1238         query->type = QUERY_PASTEBOARD_DATA;
1239         query->window = (macdrv_window)[self retain];
1240         query->pasteboard_data.type = (CFStringRef)[type copy];
1241
1242         [self.queue query:query timeout:3];
1243         macdrv_release_query(query);
1244     }
1245
1246
1247     /*
1248      * ---------- NSDraggingDestination methods ----------
1249      */
1250     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
1251     {
1252         return [self draggingUpdated:sender];
1253     }
1254
1255     - (void) draggingExited:(id <NSDraggingInfo>)sender
1256     {
1257         // This isn't really a query.  We don't need any response.  However, it
1258         // has to be processed in a similar manner as the other drag-and-drop
1259         // queries in order to maintain the proper order of operations.
1260         macdrv_query* query = macdrv_create_query();
1261         query->type = QUERY_DRAG_EXITED;
1262         query->window = (macdrv_window)[self retain];
1263
1264         [self.queue query:query timeout:0.1];
1265         macdrv_release_query(query);
1266     }
1267
1268     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
1269     {
1270         NSDragOperation ret;
1271         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1272         NSPasteboard* pb = [sender draggingPasteboard];
1273
1274         macdrv_query* query = macdrv_create_query();
1275         query->type = QUERY_DRAG_OPERATION;
1276         query->window = (macdrv_window)[self retain];
1277         query->drag_operation.x = pt.x;
1278         query->drag_operation.y = pt.y;
1279         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
1280         query->drag_operation.accepted_op = NSDragOperationNone;
1281         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
1282
1283         [self.queue query:query timeout:3];
1284         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
1285         macdrv_release_query(query);
1286
1287         return ret;
1288     }
1289
1290     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
1291     {
1292         BOOL ret;
1293         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1294         NSPasteboard* pb = [sender draggingPasteboard];
1295
1296         macdrv_query* query = macdrv_create_query();
1297         query->type = QUERY_DRAG_DROP;
1298         query->window = (macdrv_window)[self retain];
1299         query->drag_drop.x = pt.x;
1300         query->drag_drop.y = pt.y;
1301         query->drag_drop.op = [sender draggingSourceOperationMask];
1302         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
1303
1304         [self.queue query:query timeout:3 * 60 processEvents:YES];
1305         ret = query->status;
1306         macdrv_release_query(query);
1307
1308         return ret;
1309     }
1310
1311     - (BOOL) wantsPeriodicDraggingUpdates
1312     {
1313         return NO;
1314     }
1315
1316 @end
1317
1318
1319 /***********************************************************************
1320  *              macdrv_create_cocoa_window
1321  *
1322  * Create a Cocoa window with the given content frame and features (e.g.
1323  * title bar, close box, etc.).
1324  */
1325 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1326         CGRect frame, void* hwnd, macdrv_event_queue queue)
1327 {
1328     __block WineWindow* window;
1329
1330     OnMainThread(^{
1331         window = [[WineWindow createWindowWithFeatures:wf
1332                                            windowFrame:NSRectFromCGRect(frame)
1333                                                   hwnd:hwnd
1334                                                  queue:(WineEventQueue*)queue] retain];
1335     });
1336
1337     return (macdrv_window)window;
1338 }
1339
1340 /***********************************************************************
1341  *              macdrv_destroy_cocoa_window
1342  *
1343  * Destroy a Cocoa window.
1344  */
1345 void macdrv_destroy_cocoa_window(macdrv_window w)
1346 {
1347     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1348     WineWindow* window = (WineWindow*)w;
1349
1350     [window.queue discardEventsMatchingMask:-1 forWindow:window];
1351     [window close];
1352     [window release];
1353
1354     [pool release];
1355 }
1356
1357 /***********************************************************************
1358  *              macdrv_get_window_hwnd
1359  *
1360  * Get the hwnd that was set for the window at creation.
1361  */
1362 void* macdrv_get_window_hwnd(macdrv_window w)
1363 {
1364     WineWindow* window = (WineWindow*)w;
1365     return window.hwnd;
1366 }
1367
1368 /***********************************************************************
1369  *              macdrv_set_cocoa_window_features
1370  *
1371  * Update a Cocoa window's features.
1372  */
1373 void macdrv_set_cocoa_window_features(macdrv_window w,
1374         const struct macdrv_window_features* wf)
1375 {
1376     WineWindow* window = (WineWindow*)w;
1377
1378     OnMainThread(^{
1379         [window setWindowFeatures:wf];
1380     });
1381 }
1382
1383 /***********************************************************************
1384  *              macdrv_set_cocoa_window_state
1385  *
1386  * Update a Cocoa window's state.
1387  */
1388 void macdrv_set_cocoa_window_state(macdrv_window w,
1389         const struct macdrv_window_state* state)
1390 {
1391     WineWindow* window = (WineWindow*)w;
1392
1393     OnMainThread(^{
1394         [window setMacDrvState:state];
1395     });
1396 }
1397
1398 /***********************************************************************
1399  *              macdrv_set_cocoa_window_title
1400  *
1401  * Set a Cocoa window's title.
1402  */
1403 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1404         size_t length)
1405 {
1406     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1407     WineWindow* window = (WineWindow*)w;
1408     NSString* titleString;
1409
1410     if (title)
1411         titleString = [NSString stringWithCharacters:title length:length];
1412     else
1413         titleString = @"";
1414     OnMainThreadAsync(^{
1415         [window setTitle:titleString];
1416         if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1417             [NSApp changeWindowsItem:window title:titleString filename:NO];
1418     });
1419
1420     [pool release];
1421 }
1422
1423 /***********************************************************************
1424  *              macdrv_order_cocoa_window
1425  *
1426  * Reorder a Cocoa window relative to other windows.  If prev is
1427  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
1428  * it is ordered above that window.  Otherwise, it is ordered to the
1429  * front.
1430  *
1431  * Returns true if the window has actually been ordered onto the screen
1432  * (i.e. if its frame intersects with a screen).  Otherwise, false.
1433  */
1434 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1435         macdrv_window next)
1436 {
1437     WineWindow* window = (WineWindow*)w;
1438     __block BOOL on_screen;
1439
1440     OnMainThread(^{
1441         on_screen = [window orderBelow:(WineWindow*)prev
1442                                orAbove:(WineWindow*)next];
1443     });
1444
1445     return on_screen;
1446 }
1447
1448 /***********************************************************************
1449  *              macdrv_hide_cocoa_window
1450  *
1451  * Hides a Cocoa window.
1452  */
1453 void macdrv_hide_cocoa_window(macdrv_window w)
1454 {
1455     WineWindow* window = (WineWindow*)w;
1456
1457     OnMainThread(^{
1458         [window doOrderOut];
1459     });
1460 }
1461
1462 /***********************************************************************
1463  *              macdrv_set_cocoa_window_frame
1464  *
1465  * Move a Cocoa window.  If the window has been moved out of the bounds
1466  * of the desktop, it is ordered out.  (This routine won't ever order a
1467  * window in, though.)
1468  *
1469  * Returns true if the window is on screen; false otherwise.
1470  */
1471 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1472 {
1473     WineWindow* window = (WineWindow*)w;
1474     __block BOOL on_screen;
1475
1476     OnMainThread(^{
1477         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1478     });
1479
1480     return on_screen;
1481 }
1482
1483 /***********************************************************************
1484  *              macdrv_get_cocoa_window_frame
1485  *
1486  * Gets the frame of a Cocoa window.
1487  */
1488 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1489 {
1490     WineWindow* window = (WineWindow*)w;
1491
1492     OnMainThread(^{
1493         NSRect frame;
1494
1495         frame = [window contentRectForFrameRect:[window frame]];
1496         [NSApp flipRect:&frame];
1497         *out_frame = NSRectToCGRect(frame);
1498     });
1499 }
1500
1501 /***********************************************************************
1502  *              macdrv_set_cocoa_parent_window
1503  *
1504  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
1505  * the parent window.
1506  */
1507 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1508 {
1509     WineWindow* window = (WineWindow*)w;
1510
1511     OnMainThread(^{
1512         [window setMacDrvParentWindow:(WineWindow*)parent];
1513     });
1514 }
1515
1516 /***********************************************************************
1517  *              macdrv_set_window_surface
1518  */
1519 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1520 {
1521     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1522     WineWindow* window = (WineWindow*)w;
1523
1524     OnMainThread(^{
1525         window.surface = surface;
1526         window.surface_mutex = mutex;
1527     });
1528
1529     [pool release];
1530 }
1531
1532 /***********************************************************************
1533  *              macdrv_window_needs_display
1534  *
1535  * Mark a window as needing display in a specified rect (in non-client
1536  * area coordinates).
1537  */
1538 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1539 {
1540     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1541     WineWindow* window = (WineWindow*)w;
1542
1543     OnMainThreadAsync(^{
1544         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1545     });
1546
1547     [pool release];
1548 }
1549
1550 /***********************************************************************
1551  *              macdrv_set_window_shape
1552  *
1553  * Sets the shape of a Cocoa window from an array of rectangles.  If
1554  * rects is NULL, resets the window's shape to its frame.
1555  */
1556 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1557 {
1558     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1559     WineWindow* window = (WineWindow*)w;
1560
1561     OnMainThread(^{
1562         if (!rects || !count)
1563             window.shape = nil;
1564         else
1565         {
1566             NSBezierPath* path;
1567             unsigned int i;
1568
1569             path = [NSBezierPath bezierPath];
1570             for (i = 0; i < count; i++)
1571                 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1572             window.shape = path;
1573         }
1574     });
1575
1576     [pool release];
1577 }
1578
1579 /***********************************************************************
1580  *              macdrv_set_window_alpha
1581  */
1582 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1583 {
1584     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1585     WineWindow* window = (WineWindow*)w;
1586
1587     [window setAlphaValue:alpha];
1588
1589     [pool release];
1590 }
1591
1592 /***********************************************************************
1593  *              macdrv_set_window_color_key
1594  */
1595 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1596                                  CGFloat keyBlue)
1597 {
1598     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1599     WineWindow* window = (WineWindow*)w;
1600
1601     OnMainThread(^{
1602         window.colorKeyed       = TRUE;
1603         window.colorKeyRed      = keyRed;
1604         window.colorKeyGreen    = keyGreen;
1605         window.colorKeyBlue     = keyBlue;
1606         [window checkTransparency];
1607     });
1608
1609     [pool release];
1610 }
1611
1612 /***********************************************************************
1613  *              macdrv_clear_window_color_key
1614  */
1615 void macdrv_clear_window_color_key(macdrv_window w)
1616 {
1617     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1618     WineWindow* window = (WineWindow*)w;
1619
1620     OnMainThread(^{
1621         window.colorKeyed = FALSE;
1622         [window checkTransparency];
1623     });
1624
1625     [pool release];
1626 }
1627
1628 /***********************************************************************
1629  *              macdrv_window_use_per_pixel_alpha
1630  */
1631 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1632 {
1633     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1634     WineWindow* window = (WineWindow*)w;
1635
1636     OnMainThread(^{
1637         window.usePerPixelAlpha = use_per_pixel_alpha;
1638         [window checkTransparency];
1639     });
1640
1641     [pool release];
1642 }
1643
1644 /***********************************************************************
1645  *              macdrv_give_cocoa_window_focus
1646  *
1647  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
1648  * orders it front and, if its frame was not within the desktop bounds,
1649  * Cocoa will typically move it on-screen.
1650  */
1651 void macdrv_give_cocoa_window_focus(macdrv_window w)
1652 {
1653     WineWindow* window = (WineWindow*)w;
1654
1655     OnMainThread(^{
1656         [window makeFocused];
1657     });
1658 }
1659
1660 /***********************************************************************
1661  *              macdrv_create_view
1662  *
1663  * Creates and returns a view in the specified rect of the window.  The
1664  * caller is responsible for calling macdrv_dispose_view() on the view
1665  * when it is done with it.
1666  */
1667 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
1668 {
1669     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1670     WineWindow* window = (WineWindow*)w;
1671     __block WineContentView* view;
1672
1673     if (CGRectIsNull(rect)) rect = CGRectZero;
1674
1675     OnMainThread(^{
1676         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1677
1678         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
1679         [view setAutoresizesSubviews:NO];
1680         [nc addObserver:view
1681                selector:@selector(updateGLContexts)
1682                    name:NSViewGlobalFrameDidChangeNotification
1683                  object:view];
1684         [nc addObserver:view
1685                selector:@selector(updateGLContexts)
1686                    name:NSApplicationDidChangeScreenParametersNotification
1687                  object:NSApp];
1688         [[window contentView] addSubview:view];
1689     });
1690
1691     [pool release];
1692     return (macdrv_view)view;
1693 }
1694
1695 /***********************************************************************
1696  *              macdrv_dispose_view
1697  *
1698  * Destroys a view previously returned by macdrv_create_view.
1699  */
1700 void macdrv_dispose_view(macdrv_view v)
1701 {
1702     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1703     WineContentView* view = (WineContentView*)v;
1704
1705     OnMainThread(^{
1706         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1707
1708         [nc removeObserver:view
1709                       name:NSViewGlobalFrameDidChangeNotification
1710                     object:view];
1711         [nc removeObserver:view
1712                       name:NSApplicationDidChangeScreenParametersNotification
1713                     object:NSApp];
1714         [view removeFromSuperview];
1715         [view release];
1716     });
1717
1718     [pool release];
1719 }
1720
1721 /***********************************************************************
1722  *              macdrv_set_view_window_and_frame
1723  *
1724  * Move a view to a new window and/or position within its window.  If w
1725  * is NULL, leave the view in its current window and just change its
1726  * frame.
1727  */
1728 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
1729 {
1730     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1731     WineContentView* view = (WineContentView*)v;
1732     WineWindow* window = (WineWindow*)w;
1733
1734     if (CGRectIsNull(rect)) rect = CGRectZero;
1735
1736     OnMainThread(^{
1737         BOOL changedWindow = (window && window != [view window]);
1738         NSRect newFrame = NSRectFromCGRect(rect);
1739         NSRect oldFrame = [view frame];
1740
1741         if (changedWindow)
1742         {
1743             [view removeFromSuperview];
1744             [[window contentView] addSubview:view];
1745         }
1746
1747         if (!NSEqualRects(oldFrame, newFrame))
1748         {
1749             if (!changedWindow)
1750                 [[view superview] setNeedsDisplayInRect:oldFrame];
1751             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
1752                 [view setFrameSize:newFrame.size];
1753             else if (NSEqualSizes(oldFrame.size, newFrame.size))
1754                 [view setFrameOrigin:newFrame.origin];
1755             else
1756                 [view setFrame:newFrame];
1757             [view setNeedsDisplay:YES];
1758         }
1759     });
1760
1761     [pool release];
1762 }
1763
1764 /***********************************************************************
1765  *              macdrv_add_view_opengl_context
1766  *
1767  * Add an OpenGL context to the list being tracked for each view.
1768  */
1769 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1770 {
1771     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1772     WineContentView* view = (WineContentView*)v;
1773     WineOpenGLContext *context = (WineOpenGLContext*)c;
1774
1775     OnMainThreadAsync(^{
1776         [view addGLContext:context];
1777     });
1778
1779     [pool release];
1780 }
1781
1782 /***********************************************************************
1783  *              macdrv_remove_view_opengl_context
1784  *
1785  * Add an OpenGL context to the list being tracked for each view.
1786  */
1787 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1788 {
1789     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1790     WineContentView* view = (WineContentView*)v;
1791     WineOpenGLContext *context = (WineOpenGLContext*)c;
1792
1793     OnMainThreadAsync(^{
1794         [view removeGLContext:context];
1795     });
1796
1797     [pool release];
1798 }
1799
1800 /***********************************************************************
1801  *              macdrv_window_background_color
1802  *
1803  * Returns the standard Mac window background color as a 32-bit value of
1804  * the form 0x00rrggbb.
1805  */
1806 uint32_t macdrv_window_background_color(void)
1807 {
1808     static uint32_t result;
1809     static dispatch_once_t once;
1810
1811     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
1812     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
1813     // of it is to draw with it.
1814     dispatch_once(&once, ^{
1815         OnMainThread(^{
1816             unsigned char rgbx[4];
1817             unsigned char *planes = rgbx;
1818             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
1819                                                                                pixelsWide:1
1820                                                                                pixelsHigh:1
1821                                                                             bitsPerSample:8
1822                                                                           samplesPerPixel:3
1823                                                                                  hasAlpha:NO
1824                                                                                  isPlanar:NO
1825                                                                            colorSpaceName:NSCalibratedRGBColorSpace
1826                                                                              bitmapFormat:0
1827                                                                               bytesPerRow:4
1828                                                                              bitsPerPixel:32];
1829             [NSGraphicsContext saveGraphicsState];
1830             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
1831             [[NSColor windowBackgroundColor] set];
1832             NSRectFill(NSMakeRect(0, 0, 1, 1));
1833             [NSGraphicsContext restoreGraphicsState];
1834             [bitmap release];
1835             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
1836         });
1837     });
1838
1839     return result;
1840 }