jscript: Rename jsheap_t to heap_pool_t.
[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
29
30 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
31 enum {
32     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
33 };
34
35
36 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
37 {
38     NSUInteger style_mask;
39
40     if (wf->title_bar)
41     {
42         style_mask = NSTitledWindowMask;
43         if (wf->close_button) style_mask |= NSClosableWindowMask;
44         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
45         if (wf->resizable) style_mask |= NSResizableWindowMask;
46         if (wf->utility) style_mask |= NSUtilityWindowMask;
47     }
48     else style_mask = NSBorderlessWindowMask;
49
50     return style_mask;
51 }
52
53
54 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
55 {
56     NSScreen* screen;
57     for (screen in screens)
58     {
59         if (NSIntersectsRect(frame, [screen frame]))
60             return TRUE;
61     }
62     return FALSE;
63 }
64
65
66 /* We rely on the supposedly device-dependent modifier flags to distinguish the
67    keys on the left side of the keyboard from those on the right.  Some event
68    sources don't set those device-depdendent flags.  If we see a device-independent
69    flag for a modifier without either corresponding device-dependent flag, assume
70    the left one. */
71 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
72 {
73     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
74         *modifiers |= NX_DEVICELCMDKEYMASK;
75     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
76         *modifiers |= NX_DEVICELSHIFTKEYMASK;
77     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
78         *modifiers |= NX_DEVICELCTLKEYMASK;
79     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
80         *modifiers |= NX_DEVICELALTKEYMASK;
81 }
82
83 /* As we manipulate individual bits of a modifier mask, we can end up with
84    inconsistent sets of flags.  In particular, we might set or clear one of the
85    left/right-specific bits, but not the corresponding non-side-specific bit.
86    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
87    otherwise clear it. */
88 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
89 {
90     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
91         *modifiers |= NX_COMMANDMASK;
92     else
93         *modifiers &= ~NX_COMMANDMASK;
94     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
95         *modifiers |= NX_SHIFTMASK;
96     else
97         *modifiers &= ~NX_SHIFTMASK;
98     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
99         *modifiers |= NX_CONTROLMASK;
100     else
101         *modifiers &= ~NX_CONTROLMASK;
102     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
103         *modifiers |= NX_ALTERNATEMASK;
104     else
105         *modifiers &= ~NX_ALTERNATEMASK;
106 }
107
108
109 @interface WineContentView : NSView
110 @end
111
112
113 @interface WineWindow ()
114
115 @property (nonatomic) BOOL disabled;
116 @property (nonatomic) BOOL noActivate;
117 @property (nonatomic) BOOL floating;
118 @property (retain, nonatomic) NSWindow* latentParentWindow;
119
120 @property (nonatomic) void* hwnd;
121 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
122
123 @property (nonatomic) void* surface;
124 @property (nonatomic) pthread_mutex_t* surface_mutex;
125
126 @property (copy, nonatomic) NSBezierPath* shape;
127 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
128 @property (readonly, nonatomic) BOOL needsTransparency;
129
130 @property (nonatomic) BOOL colorKeyed;
131 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
132 @property (nonatomic) BOOL usePerPixelAlpha;
133
134     + (void) flipRect:(NSRect*)rect;
135
136 @end
137
138
139 @implementation WineContentView
140
141     - (BOOL) isFlipped
142     {
143         return YES;
144     }
145
146     - (void) drawRect:(NSRect)rect
147     {
148         WineWindow* window = (WineWindow*)[self window];
149
150         if (window.surface && window.surface_mutex &&
151             !pthread_mutex_lock(window.surface_mutex))
152         {
153             const CGRect* rects;
154             int count;
155
156             if (!get_surface_region_rects(window.surface, &rects, &count) || count)
157             {
158                 CGRect imageRect;
159                 CGImageRef image;
160
161                 imageRect = NSRectToCGRect(rect);
162                 image = create_surface_image(window.surface, &imageRect, FALSE);
163
164                 if (image)
165                 {
166                     CGContextRef context;
167
168                     if (rects && count)
169                     {
170                         NSBezierPath* surfaceClip = [NSBezierPath bezierPath];
171                         int i;
172                         for (i = 0; i < count; i++)
173                             [surfaceClip appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
174                         [surfaceClip addClip];
175                     }
176
177                     [window.shape addClip];
178
179                     if (window.colorKeyed)
180                     {
181                         CGImageRef maskedImage;
182                         CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
183                                                  window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
184                                                  window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
185                         maskedImage = CGImageCreateWithMaskingColors(image, components);
186                         if (maskedImage)
187                         {
188                             CGImageRelease(image);
189                             image = maskedImage;
190                         }
191                     }
192
193                     context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
194                     CGContextSetBlendMode(context, kCGBlendModeCopy);
195                     CGContextDrawImage(context, imageRect, image);
196
197                     CGImageRelease(image);
198
199                     if (window.shapeChangedSinceLastDraw || window.colorKeyed ||
200                         window.usePerPixelAlpha)
201                     {
202                         window.shapeChangedSinceLastDraw = FALSE;
203                         [window invalidateShadow];
204                     }
205                 }
206             }
207
208             pthread_mutex_unlock(window.surface_mutex);
209         }
210     }
211
212     /* By default, NSView will swallow right-clicks in an attempt to support contextual
213        menus.  We need to bypass that and allow the event to make it to the window. */
214     - (void) rightMouseDown:(NSEvent*)theEvent
215     {
216         [[self window] rightMouseDown:theEvent];
217     }
218
219     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
220     {
221         return YES;
222     }
223
224 @end
225
226
227 @implementation WineWindow
228
229     @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
230     @synthesize surface, surface_mutex;
231     @synthesize shape, shapeChangedSinceLastDraw;
232     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
233     @synthesize usePerPixelAlpha;
234
235     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
236                                  windowFrame:(NSRect)window_frame
237                                         hwnd:(void*)hwnd
238                                        queue:(WineEventQueue*)queue
239     {
240         WineWindow* window;
241         WineContentView* contentView;
242         NSTrackingArea* trackingArea;
243
244         [self flipRect:&window_frame];
245
246         window = [[[self alloc] initWithContentRect:window_frame
247                                           styleMask:style_mask_for_features(wf)
248                                             backing:NSBackingStoreBuffered
249                                               defer:YES] autorelease];
250
251         if (!window) return nil;
252         window->normalStyleMask = [window styleMask];
253         window->forceNextMouseMoveAbsolute = TRUE;
254
255         /* Standardize windows to eliminate differences between titled and
256            borderless windows and between NSWindow and NSPanel. */
257         [window setHidesOnDeactivate:NO];
258         [window setReleasedWhenClosed:NO];
259
260         [window disableCursorRects];
261         [window setShowsResizeIndicator:NO];
262         [window setHasShadow:wf->shadow];
263         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
264         [window setDelegate:window];
265         window.hwnd = hwnd;
266         window.queue = queue;
267
268         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
269         if (!contentView)
270             return nil;
271         [contentView setAutoresizesSubviews:NO];
272
273         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
274                                                      options:(NSTrackingMouseEnteredAndExited |
275                                                               NSTrackingMouseMoved |
276                                                               NSTrackingActiveAlways |
277                                                               NSTrackingInVisibleRect)
278                                                        owner:window
279                                                     userInfo:nil] autorelease];
280         if (!trackingArea)
281             return nil;
282         [contentView addTrackingArea:trackingArea];
283
284         [window setContentView:contentView];
285
286         return window;
287     }
288
289     - (void) dealloc
290     {
291         [queue release];
292         [latentParentWindow release];
293         [shape release];
294         [super dealloc];
295     }
296
297     + (void) flipRect:(NSRect*)rect
298     {
299         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
300     }
301
302     - (void) adjustFeaturesForState
303     {
304         NSUInteger style = normalStyleMask;
305
306         if (self.disabled)
307             style &= ~NSResizableWindowMask;
308         if (style != [self styleMask])
309             [self setStyleMask:style];
310
311         if (style & NSClosableWindowMask)
312             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
313         if (style & NSMiniaturizableWindowMask)
314             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
315     }
316
317     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
318     {
319         normalStyleMask = style_mask_for_features(wf);
320         [self adjustFeaturesForState];
321         [self setHasShadow:wf->shadow];
322     }
323
324     - (void) setMacDrvState:(const struct macdrv_window_state*)state
325     {
326         NSInteger level;
327         NSWindowCollectionBehavior behavior;
328
329         self.disabled = state->disabled;
330         self.noActivate = state->no_activate;
331
332         self.floating = state->floating;
333         level = state->floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
334         if (level != [self level])
335             [self setLevel:level];
336
337         behavior = NSWindowCollectionBehaviorDefault;
338         if (state->excluded_by_expose)
339             behavior |= NSWindowCollectionBehaviorTransient;
340         else
341             behavior |= NSWindowCollectionBehaviorManaged;
342         if (state->excluded_by_cycle)
343         {
344             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
345             if ([self isVisible])
346                 [NSApp removeWindowsItem:self];
347         }
348         else
349         {
350             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
351             if ([self isVisible])
352                 [NSApp addWindowsItem:self title:[self title] filename:NO];
353         }
354         [self setCollectionBehavior:behavior];
355
356         if (state->minimized && ![self isMiniaturized])
357         {
358             ignore_windowMiniaturize = TRUE;
359             [self miniaturize:nil];
360         }
361         else if (!state->minimized && [self isMiniaturized])
362         {
363             ignore_windowDeminiaturize = TRUE;
364             [self deminiaturize:nil];
365         }
366
367         /* Whatever events regarding minimization might have been in the queue are now stale. */
368         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
369                                          event_mask_for_type(WINDOW_DID_UNMINIMIZE)
370                                forWindow:self];
371     }
372
373     /* Returns whether or not the window was ordered in, which depends on if
374        its frame intersects any screen. */
375     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
376     {
377         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
378         if (on_screen)
379         {
380             [NSApp transformProcessToForeground];
381
382             if (prev)
383                 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
384             else
385                 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
386             if (latentParentWindow)
387             {
388                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
389                 self.latentParentWindow = nil;
390             }
391
392             /* Cocoa may adjust the frame when the window is ordered onto the screen.
393                Generate a frame-changed event just in case.  The back end will ignore
394                it if nothing actually changed. */
395             [self windowDidResize:nil];
396
397             if (![self isExcludedFromWindowsMenu])
398                 [NSApp addWindowsItem:self title:[self title] filename:NO];
399         }
400
401         return on_screen;
402     }
403
404     - (void) doOrderOut
405     {
406         self.latentParentWindow = [self parentWindow];
407         [latentParentWindow removeChildWindow:self];
408         forceNextMouseMoveAbsolute = TRUE;
409         [self orderOut:nil];
410         [NSApp removeWindowsItem:self];
411     }
412
413     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
414     {
415         NSArray* screens = [NSScreen screens];
416         BOOL on_screen = [self isVisible];
417         NSRect frame, oldFrame;
418
419         if (![screens count]) return on_screen;
420
421         /* Origin is (left, top) in a top-down space.  Need to convert it to
422            (left, bottom) in a bottom-up space. */
423         [[self class] flipRect:&contentRect];
424
425         if (on_screen)
426         {
427             on_screen = frame_intersects_screens(contentRect, screens);
428             if (!on_screen)
429                 [self doOrderOut];
430         }
431
432         oldFrame = [self frame];
433         frame = [self frameRectForContentRect:contentRect];
434         if (!NSEqualRects(frame, oldFrame))
435         {
436             if (NSEqualSizes(frame.size, oldFrame.size))
437                 [self setFrameOrigin:frame.origin];
438             else
439                 [self setFrame:frame display:YES];
440         }
441
442         if (on_screen)
443         {
444             /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
445                event.  The back end will ignore it if nothing actually changed. */
446             [self windowDidResize:nil];
447         }
448
449         return on_screen;
450     }
451
452     - (void) setMacDrvParentWindow:(WineWindow*)parent
453     {
454         if ([self parentWindow] != parent)
455         {
456             [[self parentWindow] removeChildWindow:self];
457             self.latentParentWindow = nil;
458             if ([self isVisible] && parent)
459                 [parent addChildWindow:self ordered:NSWindowAbove];
460             else
461                 self.latentParentWindow = parent;
462         }
463     }
464
465     - (void) setDisabled:(BOOL)newValue
466     {
467         if (disabled != newValue)
468         {
469             disabled = newValue;
470             [self adjustFeaturesForState];
471         }
472     }
473
474     - (BOOL) needsTransparency
475     {
476         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
477     }
478
479     - (void) checkTransparency
480     {
481         if (![self isOpaque] && !self.needsTransparency)
482         {
483             [self setBackgroundColor:[NSColor windowBackgroundColor]];
484             [self setOpaque:YES];
485         }
486         else if ([self isOpaque] && self.needsTransparency)
487         {
488             [self setBackgroundColor:[NSColor clearColor]];
489             [self setOpaque:NO];
490         }
491     }
492
493     - (void) setShape:(NSBezierPath*)newShape
494     {
495         if (shape == newShape) return;
496         if (shape && newShape && [shape isEqual:newShape]) return;
497
498         if (shape)
499         {
500             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
501             [shape release];
502         }
503         if (newShape)
504             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
505
506         shape = [newShape copy];
507         self.shapeChangedSinceLastDraw = TRUE;
508
509         [self checkTransparency];
510     }
511
512     - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
513     {
514         CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
515         macdrv_event event;
516
517         event.type = MOUSE_BUTTON;
518         event.window = (macdrv_window)[self retain];
519         event.mouse_button.button = [theEvent buttonNumber];
520         event.mouse_button.pressed = pressed;
521         event.mouse_button.x = pt.x;
522         event.mouse_button.y = pt.y;
523         event.mouse_button.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
524
525         [queue postEvent:&event];
526     }
527
528     - (void) makeFocused
529     {
530         NSArray* screens;
531
532         [NSApp transformProcessToForeground];
533
534         /* If a borderless window is offscreen, orderFront: won't move
535            it onscreen like it would for a titled window.  Do that ourselves. */
536         screens = [NSScreen screens];
537         if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
538             !frame_intersects_screens([self frame], screens))
539         {
540             NSScreen* primaryScreen = [screens objectAtIndex:0];
541             NSRect frame = [primaryScreen frame];
542             [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
543             frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
544             [self setFrame:frame display:YES];
545         }
546
547         [self orderFront:nil];
548         causing_becomeKeyWindow = TRUE;
549         [self makeKeyWindow];
550         causing_becomeKeyWindow = FALSE;
551         if (latentParentWindow)
552         {
553             [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
554             self.latentParentWindow = nil;
555         }
556         if (![self isExcludedFromWindowsMenu])
557             [NSApp addWindowsItem:self title:[self title] filename:NO];
558
559         /* Cocoa may adjust the frame when the window is ordered onto the screen.
560            Generate a frame-changed event just in case.  The back end will ignore
561            it if nothing actually changed. */
562         [self windowDidResize:nil];
563     }
564
565     - (void) postKey:(uint16_t)keyCode
566              pressed:(BOOL)pressed
567            modifiers:(NSUInteger)modifiers
568                event:(NSEvent*)theEvent
569     {
570         macdrv_event event;
571         CGEventRef cgevent;
572         WineApplication* app = (WineApplication*)NSApp;
573
574         event.type          = pressed ? KEY_PRESS : KEY_RELEASE;
575         event.window        = (macdrv_window)[self retain];
576         event.key.keycode   = keyCode;
577         event.key.modifiers = modifiers;
578         event.key.time_ms   = [app ticksForEventTime:[theEvent timestamp]];
579
580         if ((cgevent = [theEvent CGEvent]))
581         {
582             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
583                                                         kCGKeyboardEventKeyboardType);
584             if (keyboardType != app.keyboardType)
585             {
586                 app.keyboardType = keyboardType;
587                 [app keyboardSelectionDidChange];
588             }
589         }
590
591         [queue postEvent:&event];
592     }
593
594     - (void) postKeyEvent:(NSEvent *)theEvent
595     {
596         [self flagsChanged:theEvent];
597         [self postKey:[theEvent keyCode]
598               pressed:[theEvent type] == NSKeyDown
599             modifiers:[theEvent modifierFlags]
600                 event:theEvent];
601     }
602
603     - (void) postMouseMovedEvent:(NSEvent *)theEvent
604     {
605         macdrv_event event;
606
607         if (forceNextMouseMoveAbsolute)
608         {
609             CGPoint point = CGEventGetLocation([theEvent CGEvent]);
610
611             event.type = MOUSE_MOVED_ABSOLUTE;
612             event.mouse_moved.x = point.x;
613             event.mouse_moved.y = point.y;
614
615             mouseMoveDeltaX = 0;
616             mouseMoveDeltaY = 0;
617
618             forceNextMouseMoveAbsolute = FALSE;
619         }
620         else
621         {
622             /* Add event delta to accumulated delta error */
623             /* deltaY is already flipped */
624             mouseMoveDeltaX += [theEvent deltaX];
625             mouseMoveDeltaY += [theEvent deltaY];
626
627             event.type = MOUSE_MOVED;
628             event.mouse_moved.x = mouseMoveDeltaX;
629             event.mouse_moved.y = mouseMoveDeltaY;
630
631             /* Keep the remainder after integer truncation. */
632             mouseMoveDeltaX -= event.mouse_moved.x;
633             mouseMoveDeltaY -= event.mouse_moved.y;
634         }
635
636         if (event.type == MOUSE_MOVED_ABSOLUTE || event.mouse_moved.x || event.mouse_moved.y)
637         {
638             event.window = (macdrv_window)[self retain];
639             event.mouse_moved.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
640
641             [queue postEvent:&event];
642         }
643     }
644
645
646     /*
647      * ---------- NSWindow method overrides ----------
648      */
649     - (BOOL) canBecomeKeyWindow
650     {
651         if (causing_becomeKeyWindow) return YES;
652         if (self.disabled || self.noActivate) return NO;
653         return [self isKeyWindow];
654     }
655
656     - (BOOL) canBecomeMainWindow
657     {
658         return [self canBecomeKeyWindow];
659     }
660
661     - (BOOL) isExcludedFromWindowsMenu
662     {
663         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
664     }
665
666     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
667     {
668         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
669             return [self isKeyWindow] || (!self.disabled && !self.noActivate);
670         return [super validateMenuItem:menuItem];
671     }
672
673     /* We don't call this.  It's the action method of the items in the Window menu. */
674     - (void) makeKeyAndOrderFront:(id)sender
675     {
676         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
677             [NSApp windowGotFocus:self];
678     }
679
680     - (void) sendEvent:(NSEvent*)event
681     {
682         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
683            interface control.  For example, Control-Tab switches focus among
684            views.  We want to bypass that feature, so directly route key-down
685            events to -keyDown:. */
686         if ([event type] == NSKeyDown)
687             [[self firstResponder] keyDown:event];
688         else
689         {
690             if ([event type] == NSLeftMouseDown)
691             {
692                 /* Since our windows generally claim they can't be made key, clicks
693                    in their title bars are swallowed by the theme frame stuff.  So,
694                    we hook directly into the event stream and assume that any click
695                    in the window will activate it, if Wine and the Win32 program
696                    accept. */
697                 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
698                     [NSApp windowGotFocus:self];
699             }
700
701             [super sendEvent:event];
702         }
703     }
704
705
706     /*
707      * ---------- NSResponder method overrides ----------
708      */
709     - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
710     - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
711     - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
712
713     - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
714     - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
715     - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
716
717     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
718     - (void) keyUp:(NSEvent *)theEvent   { [self postKeyEvent:theEvent]; }
719
720     - (void) flagsChanged:(NSEvent *)theEvent
721     {
722         static const struct {
723             NSUInteger  mask;
724             uint16_t    keycode;
725         } modifiers[] = {
726             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
727             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
728             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
729             { NX_DEVICELCTLKEYMASK,     kVK_Control },
730             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
731             { NX_DEVICELALTKEYMASK,     kVK_Option },
732             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
733             { NX_DEVICELCMDKEYMASK,     kVK_Command },
734             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
735         };
736
737         NSUInteger modifierFlags = [theEvent modifierFlags];
738         NSUInteger changed;
739         int i, last_changed;
740
741         fix_device_modifiers_by_generic(&modifierFlags);
742         changed = modifierFlags ^ lastModifierFlags;
743
744         last_changed = -1;
745         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
746             if (changed & modifiers[i].mask)
747                 last_changed = i;
748
749         for (i = 0; i <= last_changed; i++)
750         {
751             if (changed & modifiers[i].mask)
752             {
753                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
754
755                 if (i == last_changed)
756                     lastModifierFlags = modifierFlags;
757                 else
758                 {
759                     lastModifierFlags ^= modifiers[i].mask;
760                     fix_generic_modifiers_by_device(&lastModifierFlags);
761                 }
762
763                 // Caps lock generates one event for each press-release action.
764                 // We need to simulate a pair of events for each actual event.
765                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
766                 {
767                     [self postKey:modifiers[i].keycode
768                           pressed:TRUE
769                         modifiers:lastModifierFlags
770                             event:(NSEvent*)theEvent];
771                     pressed = FALSE;
772                 }
773
774                 [self postKey:modifiers[i].keycode
775                       pressed:pressed
776                     modifiers:lastModifierFlags
777                         event:(NSEvent*)theEvent];
778             }
779         }
780     }
781
782     - (void) mouseEntered:(NSEvent *)theEvent { forceNextMouseMoveAbsolute = TRUE; }
783     - (void) mouseExited:(NSEvent *)theEvent  { forceNextMouseMoveAbsolute = TRUE; }
784
785     - (void) mouseMoved:(NSEvent *)theEvent         { [self postMouseMovedEvent:theEvent]; }
786     - (void) mouseDragged:(NSEvent *)theEvent       { [self postMouseMovedEvent:theEvent]; }
787     - (void) rightMouseDragged:(NSEvent *)theEvent  { [self postMouseMovedEvent:theEvent]; }
788     - (void) otherMouseDragged:(NSEvent *)theEvent  { [self postMouseMovedEvent:theEvent]; }
789
790     - (void) scrollWheel:(NSEvent *)theEvent
791     {
792         CGPoint pt;
793         macdrv_event event;
794         CGEventRef cgevent;
795         CGFloat x, y;
796         BOOL continuous = FALSE;
797
798         cgevent = [theEvent CGEvent];
799         pt = CGEventGetLocation(cgevent);
800
801         event.type = MOUSE_SCROLL;
802         event.window = (macdrv_window)[self retain];
803         event.mouse_scroll.x = pt.x;
804         event.mouse_scroll.y = pt.y;
805         event.mouse_scroll.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
806
807         if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
808         {
809             continuous = TRUE;
810
811             /* Continuous scroll wheel events come from high-precision scrolling
812                hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
813                For these, we can get more precise data from the CGEvent API. */
814             /* Axis 1 is vertical, axis 2 is horizontal. */
815             x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
816             y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
817         }
818         else
819         {
820             double pixelsPerLine = 10;
821             CGEventSourceRef source;
822
823             /* The non-continuous values are in units of "lines", not pixels. */
824             if ((source = CGEventCreateSourceFromEvent(cgevent)))
825             {
826                 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
827                 CFRelease(source);
828             }
829
830             x = pixelsPerLine * [theEvent deltaX];
831             y = pixelsPerLine * [theEvent deltaY];
832         }
833
834         /* Mac: negative is right or down, positive is left or up.
835            Win32: negative is left or down, positive is right or up.
836            So, negate the X scroll value to translate. */
837         x = -x;
838
839         /* The x,y values so far are in pixels.  Win32 expects to receive some
840            fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
841            6 times the pixel value. */
842         event.mouse_scroll.x_scroll = 6 * x;
843         event.mouse_scroll.y_scroll = 6 * y;
844
845         if (!continuous)
846         {
847             /* For non-continuous "clicky" wheels, if there was any motion, make
848                sure there was at least WHEEL_DELTA motion.  This is so, at slow
849                speeds where the system's acceleration curve is actually reducing the
850                scroll distance, the user is sure to get some action out of each click.
851                For example, this is important for rotating though weapons in a
852                first-person shooter. */
853             if (0 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 120)
854                 event.mouse_scroll.x_scroll = 120;
855             else if (-120 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 0)
856                 event.mouse_scroll.x_scroll = -120;
857
858             if (0 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 120)
859                 event.mouse_scroll.y_scroll = 120;
860             else if (-120 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 0)
861                 event.mouse_scroll.y_scroll = -120;
862         }
863
864         if (event.mouse_scroll.x_scroll || event.mouse_scroll.y_scroll)
865             [queue postEvent:&event];
866     }
867
868
869     /*
870      * ---------- NSWindowDelegate methods ----------
871      */
872     - (void)windowDidBecomeKey:(NSNotification *)notification
873     {
874         NSEvent* event = [NSApp lastFlagsChanged];
875         if (event)
876             [self flagsChanged:event];
877
878         if (causing_becomeKeyWindow) return;
879
880         [NSApp windowGotFocus:self];
881     }
882
883     - (void)windowDidDeminiaturize:(NSNotification *)notification
884     {
885         if (!ignore_windowDeminiaturize)
886         {
887             macdrv_event event;
888
889             /* Coalesce events by discarding any previous ones still in the queue. */
890             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
891                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
892                                    forWindow:self];
893
894             event.type = WINDOW_DID_UNMINIMIZE;
895             event.window = (macdrv_window)[self retain];
896             [queue postEvent:&event];
897         }
898
899         ignore_windowDeminiaturize = FALSE;
900     }
901
902     - (void)windowDidMove:(NSNotification *)notification
903     {
904         [self windowDidResize:notification];
905     }
906
907     - (void)windowDidResignKey:(NSNotification *)notification
908     {
909         macdrv_event event;
910
911         if (causing_becomeKeyWindow) return;
912
913         event.type = WINDOW_LOST_FOCUS;
914         event.window = (macdrv_window)[self retain];
915         [queue postEvent:&event];
916     }
917
918     - (void)windowDidResize:(NSNotification *)notification
919     {
920         macdrv_event event;
921         NSRect frame = [self contentRectForFrameRect:[self frame]];
922
923         [[self class] flipRect:&frame];
924
925         /* Coalesce events by discarding any previous ones still in the queue. */
926         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
927                                forWindow:self];
928
929         event.type = WINDOW_FRAME_CHANGED;
930         event.window = (macdrv_window)[self retain];
931         event.window_frame_changed.frame = NSRectToCGRect(frame);
932         [queue postEvent:&event];
933     }
934
935     - (BOOL)windowShouldClose:(id)sender
936     {
937         macdrv_event event;
938         event.type = WINDOW_CLOSE_REQUESTED;
939         event.window = (macdrv_window)[self retain];
940         [queue postEvent:&event];
941         return NO;
942     }
943
944     - (void)windowWillMiniaturize:(NSNotification *)notification
945     {
946         if (!ignore_windowMiniaturize)
947         {
948             macdrv_event event;
949
950             /* Coalesce events by discarding any previous ones still in the queue. */
951             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
952                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
953                                    forWindow:self];
954
955             event.type = WINDOW_DID_MINIMIZE;
956             event.window = (macdrv_window)[self retain];
957             [queue postEvent:&event];
958         }
959
960         ignore_windowMiniaturize = FALSE;
961     }
962
963 @end
964
965
966 /***********************************************************************
967  *              macdrv_create_cocoa_window
968  *
969  * Create a Cocoa window with the given content frame and features (e.g.
970  * title bar, close box, etc.).
971  */
972 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
973         CGRect frame, void* hwnd, macdrv_event_queue queue)
974 {
975     __block WineWindow* window;
976
977     OnMainThread(^{
978         window = [[WineWindow createWindowWithFeatures:wf
979                                            windowFrame:NSRectFromCGRect(frame)
980                                                   hwnd:hwnd
981                                                  queue:(WineEventQueue*)queue] retain];
982     });
983
984     return (macdrv_window)window;
985 }
986
987 /***********************************************************************
988  *              macdrv_destroy_cocoa_window
989  *
990  * Destroy a Cocoa window.
991  */
992 void macdrv_destroy_cocoa_window(macdrv_window w)
993 {
994     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
995     WineWindow* window = (WineWindow*)w;
996
997     [window.queue discardEventsMatchingMask:-1 forWindow:window];
998     [window close];
999     [window release];
1000
1001     [pool release];
1002 }
1003
1004 /***********************************************************************
1005  *              macdrv_get_window_hwnd
1006  *
1007  * Get the hwnd that was set for the window at creation.
1008  */
1009 void* macdrv_get_window_hwnd(macdrv_window w)
1010 {
1011     WineWindow* window = (WineWindow*)w;
1012     return window.hwnd;
1013 }
1014
1015 /***********************************************************************
1016  *              macdrv_set_cocoa_window_features
1017  *
1018  * Update a Cocoa window's features.
1019  */
1020 void macdrv_set_cocoa_window_features(macdrv_window w,
1021         const struct macdrv_window_features* wf)
1022 {
1023     WineWindow* window = (WineWindow*)w;
1024
1025     OnMainThread(^{
1026         [window setWindowFeatures:wf];
1027     });
1028 }
1029
1030 /***********************************************************************
1031  *              macdrv_set_cocoa_window_state
1032  *
1033  * Update a Cocoa window's state.
1034  */
1035 void macdrv_set_cocoa_window_state(macdrv_window w,
1036         const struct macdrv_window_state* state)
1037 {
1038     WineWindow* window = (WineWindow*)w;
1039
1040     OnMainThread(^{
1041         [window setMacDrvState:state];
1042     });
1043 }
1044
1045 /***********************************************************************
1046  *              macdrv_set_cocoa_window_title
1047  *
1048  * Set a Cocoa window's title.
1049  */
1050 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1051         size_t length)
1052 {
1053     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1054     WineWindow* window = (WineWindow*)w;
1055     NSString* titleString;
1056
1057     if (title)
1058         titleString = [NSString stringWithCharacters:title length:length];
1059     else
1060         titleString = @"";
1061     OnMainThreadAsync(^{
1062         [window setTitle:titleString];
1063         if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1064             [NSApp changeWindowsItem:window title:titleString filename:NO];
1065     });
1066
1067     [pool release];
1068 }
1069
1070 /***********************************************************************
1071  *              macdrv_order_cocoa_window
1072  *
1073  * Reorder a Cocoa window relative to other windows.  If prev is
1074  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
1075  * it is ordered above that window.  Otherwise, it is ordered to the
1076  * front.
1077  *
1078  * Returns true if the window has actually been ordered onto the screen
1079  * (i.e. if its frame intersects with a screen).  Otherwise, false.
1080  */
1081 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1082         macdrv_window next)
1083 {
1084     WineWindow* window = (WineWindow*)w;
1085     __block BOOL on_screen;
1086
1087     OnMainThread(^{
1088         on_screen = [window orderBelow:(WineWindow*)prev
1089                                orAbove:(WineWindow*)next];
1090     });
1091
1092     return on_screen;
1093 }
1094
1095 /***********************************************************************
1096  *              macdrv_hide_cocoa_window
1097  *
1098  * Hides a Cocoa window.
1099  */
1100 void macdrv_hide_cocoa_window(macdrv_window w)
1101 {
1102     WineWindow* window = (WineWindow*)w;
1103
1104     OnMainThread(^{
1105         [window doOrderOut];
1106     });
1107 }
1108
1109 /***********************************************************************
1110  *              macdrv_set_cocoa_window_frame
1111  *
1112  * Move a Cocoa window.  If the window has been moved out of the bounds
1113  * of the desktop, it is ordered out.  (This routine won't ever order a
1114  * window in, though.)
1115  *
1116  * Returns true if the window is on screen; false otherwise.
1117  */
1118 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1119 {
1120     WineWindow* window = (WineWindow*)w;
1121     __block BOOL on_screen;
1122
1123     OnMainThread(^{
1124         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1125     });
1126
1127     return on_screen;
1128 }
1129
1130 /***********************************************************************
1131  *              macdrv_get_cocoa_window_frame
1132  *
1133  * Gets the frame of a Cocoa window.
1134  */
1135 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1136 {
1137     WineWindow* window = (WineWindow*)w;
1138
1139     OnMainThread(^{
1140         NSRect frame;
1141
1142         frame = [window contentRectForFrameRect:[window frame]];
1143         [[window class] flipRect:&frame];
1144         *out_frame = NSRectToCGRect(frame);
1145     });
1146 }
1147
1148 /***********************************************************************
1149  *              macdrv_set_cocoa_parent_window
1150  *
1151  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
1152  * the parent window.
1153  */
1154 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1155 {
1156     WineWindow* window = (WineWindow*)w;
1157
1158     OnMainThread(^{
1159         [window setMacDrvParentWindow:(WineWindow*)parent];
1160     });
1161 }
1162
1163 /***********************************************************************
1164  *              macdrv_set_window_surface
1165  */
1166 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1167 {
1168     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1169     WineWindow* window = (WineWindow*)w;
1170
1171     OnMainThread(^{
1172         window.surface = surface;
1173         window.surface_mutex = mutex;
1174     });
1175
1176     [pool release];
1177 }
1178
1179 /***********************************************************************
1180  *              macdrv_window_needs_display
1181  *
1182  * Mark a window as needing display in a specified rect (in non-client
1183  * area coordinates).
1184  */
1185 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1186 {
1187     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1188     WineWindow* window = (WineWindow*)w;
1189
1190     OnMainThreadAsync(^{
1191         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1192     });
1193
1194     [pool release];
1195 }
1196
1197 /***********************************************************************
1198  *              macdrv_set_window_shape
1199  *
1200  * Sets the shape of a Cocoa window from an array of rectangles.  If
1201  * rects is NULL, resets the window's shape to its frame.
1202  */
1203 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1204 {
1205     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1206     WineWindow* window = (WineWindow*)w;
1207
1208     OnMainThread(^{
1209         if (!rects || !count)
1210             window.shape = nil;
1211         else
1212         {
1213             NSBezierPath* path;
1214             unsigned int i;
1215
1216             path = [NSBezierPath bezierPath];
1217             for (i = 0; i < count; i++)
1218                 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1219             window.shape = path;
1220         }
1221     });
1222
1223     [pool release];
1224 }
1225
1226 /***********************************************************************
1227  *              macdrv_set_window_alpha
1228  */
1229 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1230 {
1231     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1232     WineWindow* window = (WineWindow*)w;
1233
1234     [window setAlphaValue:alpha];
1235
1236     [pool release];
1237 }
1238
1239 /***********************************************************************
1240  *              macdrv_set_window_color_key
1241  */
1242 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1243                                  CGFloat keyBlue)
1244 {
1245     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1246     WineWindow* window = (WineWindow*)w;
1247
1248     OnMainThread(^{
1249         window.colorKeyed       = TRUE;
1250         window.colorKeyRed      = keyRed;
1251         window.colorKeyGreen    = keyGreen;
1252         window.colorKeyBlue     = keyBlue;
1253         [window checkTransparency];
1254     });
1255
1256     [pool release];
1257 }
1258
1259 /***********************************************************************
1260  *              macdrv_clear_window_color_key
1261  */
1262 void macdrv_clear_window_color_key(macdrv_window w)
1263 {
1264     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1265     WineWindow* window = (WineWindow*)w;
1266
1267     OnMainThread(^{
1268         window.colorKeyed = FALSE;
1269         [window checkTransparency];
1270     });
1271
1272     [pool release];
1273 }
1274
1275 /***********************************************************************
1276  *              macdrv_window_use_per_pixel_alpha
1277  */
1278 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1279 {
1280     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1281     WineWindow* window = (WineWindow*)w;
1282
1283     OnMainThread(^{
1284         window.usePerPixelAlpha = use_per_pixel_alpha;
1285         [window checkTransparency];
1286     });
1287
1288     [pool release];
1289 }
1290
1291 /***********************************************************************
1292  *              macdrv_give_cocoa_window_focus
1293  *
1294  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
1295  * orders it front and, if its frame was not within the desktop bounds,
1296  * Cocoa will typically move it on-screen.
1297  */
1298 void macdrv_give_cocoa_window_focus(macdrv_window w)
1299 {
1300     WineWindow* window = (WineWindow*)w;
1301
1302     OnMainThread(^{
1303         [window makeFocused];
1304     });
1305 }