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