2 * MACDRV Cocoa window code
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
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.
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.
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
21 #import <Carbon/Carbon.h>
23 #import "cocoa_window.h"
25 #include "macdrv_cocoa.h"
27 #import "cocoa_event.h"
30 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
32 kVK_RightCommand = 0x36, /* Invented for Wine; was unused */
36 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
38 NSUInteger style_mask;
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;
48 else style_mask = NSBorderlessWindowMask;
54 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
57 for (screen in screens)
59 if (NSIntersectsRect(frame, [screen frame]))
66 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
68 for (NSScreen* screen in screens)
70 if (NSContainsRect(rect, [screen frame]))
77 /* We rely on the supposedly device-dependent modifier flags to distinguish the
78 keys on the left side of the keyboard from those on the right. Some event
79 sources don't set those device-depdendent flags. If we see a device-independent
80 flag for a modifier without either corresponding device-dependent flag, assume
82 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
84 if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
85 *modifiers |= NX_DEVICELCMDKEYMASK;
86 if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
87 *modifiers |= NX_DEVICELSHIFTKEYMASK;
88 if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
89 *modifiers |= NX_DEVICELCTLKEYMASK;
90 if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
91 *modifiers |= NX_DEVICELALTKEYMASK;
94 /* As we manipulate individual bits of a modifier mask, we can end up with
95 inconsistent sets of flags. In particular, we might set or clear one of the
96 left/right-specific bits, but not the corresponding non-side-specific bit.
97 Fix that. If either side-specific bit is set, set the non-side-specific bit,
98 otherwise clear it. */
99 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
101 if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
102 *modifiers |= NX_COMMANDMASK;
104 *modifiers &= ~NX_COMMANDMASK;
105 if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
106 *modifiers |= NX_SHIFTMASK;
108 *modifiers &= ~NX_SHIFTMASK;
109 if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
110 *modifiers |= NX_CONTROLMASK;
112 *modifiers &= ~NX_CONTROLMASK;
113 if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
114 *modifiers |= NX_ALTERNATEMASK;
116 *modifiers &= ~NX_ALTERNATEMASK;
120 @interface WineContentView : NSView
124 @interface WineWindow ()
126 @property (nonatomic) BOOL disabled;
127 @property (nonatomic) BOOL noActivate;
128 @property (readwrite, nonatomic) BOOL floating;
129 @property (retain, nonatomic) NSWindow* latentParentWindow;
131 @property (nonatomic) void* hwnd;
132 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
134 @property (nonatomic) void* surface;
135 @property (nonatomic) pthread_mutex_t* surface_mutex;
137 @property (copy, nonatomic) NSBezierPath* shape;
138 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
139 @property (readonly, nonatomic) BOOL needsTransparency;
141 @property (nonatomic) BOOL colorKeyed;
142 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
143 @property (nonatomic) BOOL usePerPixelAlpha;
145 @property (readwrite, nonatomic) NSInteger levelWhenActive;
147 + (void) flipRect:(NSRect*)rect;
152 @implementation WineContentView
159 - (void) drawRect:(NSRect)rect
161 WineWindow* window = (WineWindow*)[self window];
163 if (window.surface && window.surface_mutex &&
164 !pthread_mutex_lock(window.surface_mutex))
169 if (!get_surface_region_rects(window.surface, &rects, &count) || count)
174 imageRect = NSRectToCGRect(rect);
175 image = create_surface_image(window.surface, &imageRect, FALSE);
179 CGContextRef context;
183 NSBezierPath* surfaceClip = [NSBezierPath bezierPath];
185 for (i = 0; i < count; i++)
186 [surfaceClip appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
187 [surfaceClip addClip];
190 [window.shape addClip];
192 if (window.colorKeyed)
194 CGImageRef maskedImage;
195 CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
196 window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
197 window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
198 maskedImage = CGImageCreateWithMaskingColors(image, components);
201 CGImageRelease(image);
206 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
207 CGContextSetBlendMode(context, kCGBlendModeCopy);
208 CGContextDrawImage(context, imageRect, image);
210 CGImageRelease(image);
212 if (window.shapeChangedSinceLastDraw || window.colorKeyed ||
213 window.usePerPixelAlpha)
215 window.shapeChangedSinceLastDraw = FALSE;
216 [window invalidateShadow];
221 pthread_mutex_unlock(window.surface_mutex);
225 /* By default, NSView will swallow right-clicks in an attempt to support contextual
226 menus. We need to bypass that and allow the event to make it to the window. */
227 - (void) rightMouseDown:(NSEvent*)theEvent
229 [[self window] rightMouseDown:theEvent];
232 - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
240 @implementation WineWindow
242 @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
243 @synthesize surface, surface_mutex;
244 @synthesize shape, shapeChangedSinceLastDraw;
245 @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
246 @synthesize usePerPixelAlpha;
247 @synthesize levelWhenActive;
249 + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
250 windowFrame:(NSRect)window_frame
252 queue:(WineEventQueue*)queue
255 WineContentView* contentView;
256 NSTrackingArea* trackingArea;
258 [self flipRect:&window_frame];
260 window = [[[self alloc] initWithContentRect:window_frame
261 styleMask:style_mask_for_features(wf)
262 backing:NSBackingStoreBuffered
263 defer:YES] autorelease];
265 if (!window) return nil;
266 window->normalStyleMask = [window styleMask];
267 window->forceNextMouseMoveAbsolute = TRUE;
269 /* Standardize windows to eliminate differences between titled and
270 borderless windows and between NSWindow and NSPanel. */
271 [window setHidesOnDeactivate:NO];
272 [window setReleasedWhenClosed:NO];
274 [window disableCursorRects];
275 [window setShowsResizeIndicator:NO];
276 [window setHasShadow:wf->shadow];
277 [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
278 [window setDelegate:window];
280 window.queue = queue;
282 contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
285 [contentView setAutoresizesSubviews:NO];
287 trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
288 options:(NSTrackingMouseEnteredAndExited |
289 NSTrackingMouseMoved |
290 NSTrackingActiveAlways |
291 NSTrackingInVisibleRect)
293 userInfo:nil] autorelease];
296 [contentView addTrackingArea:trackingArea];
298 [window setContentView:contentView];
306 [latentParentWindow release];
311 + (void) flipRect:(NSRect*)rect
313 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
316 - (void) adjustFeaturesForState
318 NSUInteger style = normalStyleMask;
321 style &= ~NSResizableWindowMask;
322 if (style != [self styleMask])
323 [self setStyleMask:style];
325 if (style & NSClosableWindowMask)
326 [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
327 if (style & NSMiniaturizableWindowMask)
328 [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
331 - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
333 normalStyleMask = style_mask_for_features(wf);
334 [self adjustFeaturesForState];
335 [self setHasShadow:wf->shadow];
338 - (void) adjustWindowLevel
344 WineWindow* other = nil;
346 screen = screen_covered_by_rect([self frame], [NSScreen screens]);
347 fullscreen = (screen != nil);
351 level = NSMainMenuWindowLevel + 1;
355 else if (self.floating)
356 level = NSFloatingWindowLevel;
358 level = NSNormalWindowLevel;
360 index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:self];
361 if (index != NSNotFound && index + 1 < [[NSApp orderedWineWindows] count])
363 other = [[NSApp orderedWineWindows] objectAtIndex:index + 1];
364 if (level < [other level])
365 level = [other level];
368 if (level != [self level])
370 [self setLevelWhenActive:level];
372 /* Setting the window level above has moved this window to the front
373 of all other windows at the same level. We need to move it
374 back into its proper place among other windows of that level.
375 Also, any windows which are supposed to be in front of it had
376 better have the same or higher window level. If not, bump them
378 if (index != NSNotFound)
380 for (; index > 0; index--)
382 other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
383 if ([other level] < level)
384 [other setLevelWhenActive:level];
387 [self orderWindow:NSWindowBelow relativeTo:[other windowNumber]];
395 - (void) setMacDrvState:(const struct macdrv_window_state*)state
397 NSWindowCollectionBehavior behavior;
399 self.disabled = state->disabled;
400 self.noActivate = state->no_activate;
402 self.floating = state->floating;
403 [self adjustWindowLevel];
405 behavior = NSWindowCollectionBehaviorDefault;
406 if (state->excluded_by_expose)
407 behavior |= NSWindowCollectionBehaviorTransient;
409 behavior |= NSWindowCollectionBehaviorManaged;
410 if (state->excluded_by_cycle)
412 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
413 if ([self isVisible])
414 [NSApp removeWindowsItem:self];
418 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
419 if ([self isVisible])
420 [NSApp addWindowsItem:self title:[self title] filename:NO];
422 [self setCollectionBehavior:behavior];
424 if (state->minimized && ![self isMiniaturized])
426 ignore_windowMiniaturize = TRUE;
427 [self miniaturize:nil];
429 else if (!state->minimized && [self isMiniaturized])
431 ignore_windowDeminiaturize = TRUE;
432 [self deminiaturize:nil];
435 /* Whatever events regarding minimization might have been in the queue are now stale. */
436 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
437 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
441 /* Returns whether or not the window was ordered in, which depends on if
442 its frame intersects any screen. */
443 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
445 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
448 [NSApp transformProcessToForeground];
452 /* Make sure that windows that should be above this one really are.
453 This is necessary since a full-screen window gets a boost to its
454 window level to be in front of the menu bar and Dock and that moves
455 it out of the z-order that Win32 would otherwise establish. */
456 if ([prev level] < [self level])
458 NSUInteger index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:prev];
459 if (index != NSNotFound)
461 [prev setLevelWhenActive:[self level]];
462 for (; index > 0; index--)
464 WineWindow* other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
465 if ([other level] < [self level])
466 [other setLevelWhenActive:[self level]];
470 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
471 [NSApp wineWindow:self ordered:NSWindowBelow relativeTo:prev];
475 /* Similarly, make sure this window is really above what it should be. */
476 if (next && [next level] > [self level])
477 [self setLevelWhenActive:[next level]];
478 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
479 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:next];
481 if (latentParentWindow)
483 if ([latentParentWindow level] > [self level])
484 [self setLevelWhenActive:[latentParentWindow level]];
485 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
486 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
487 self.latentParentWindow = nil;
490 /* Cocoa may adjust the frame when the window is ordered onto the screen.
491 Generate a frame-changed event just in case. The back end will ignore
492 it if nothing actually changed. */
493 [self windowDidResize:nil];
495 if (![self isExcludedFromWindowsMenu])
496 [NSApp addWindowsItem:self title:[self title] filename:NO];
504 self.latentParentWindow = [self parentWindow];
505 [latentParentWindow removeChildWindow:self];
506 forceNextMouseMoveAbsolute = TRUE;
508 [NSApp wineWindow:self ordered:NSWindowOut relativeTo:nil];
509 [NSApp removeWindowsItem:self];
512 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
514 NSArray* screens = [NSScreen screens];
515 BOOL on_screen = [self isVisible];
516 NSRect frame, oldFrame;
518 if (![screens count]) return on_screen;
520 /* Origin is (left, top) in a top-down space. Need to convert it to
521 (left, bottom) in a bottom-up space. */
522 [[self class] flipRect:&contentRect];
526 on_screen = frame_intersects_screens(contentRect, screens);
531 if (!NSIsEmptyRect(contentRect))
533 oldFrame = [self frame];
534 frame = [self frameRectForContentRect:contentRect];
535 if (!NSEqualRects(frame, oldFrame))
537 if (NSEqualSizes(frame.size, oldFrame.size))
538 [self setFrameOrigin:frame.origin];
540 [self setFrame:frame display:YES];
546 [self adjustWindowLevel];
548 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
549 event. The back end will ignore it if nothing actually changed. */
550 [self windowDidResize:nil];
554 /* The back end is establishing a new window size and position. It's
555 not interested in any stale events regarding those that may be sitting
557 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
564 - (void) setMacDrvParentWindow:(WineWindow*)parent
566 if ([self parentWindow] != parent)
568 [[self parentWindow] removeChildWindow:self];
569 self.latentParentWindow = nil;
570 if ([self isVisible] && parent)
572 if ([parent level] > [self level])
573 [self setLevelWhenActive:[parent level]];
574 [parent addChildWindow:self ordered:NSWindowAbove];
575 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:parent];
578 self.latentParentWindow = parent;
582 - (void) setDisabled:(BOOL)newValue
584 if (disabled != newValue)
587 [self adjustFeaturesForState];
591 - (BOOL) needsTransparency
593 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
596 - (void) checkTransparency
598 if (![self isOpaque] && !self.needsTransparency)
600 [self setBackgroundColor:[NSColor windowBackgroundColor]];
601 [self setOpaque:YES];
603 else if ([self isOpaque] && self.needsTransparency)
605 [self setBackgroundColor:[NSColor clearColor]];
610 - (void) setShape:(NSBezierPath*)newShape
612 if (shape == newShape) return;
613 if (shape && newShape && [shape isEqual:newShape]) return;
617 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
621 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
623 shape = [newShape copy];
624 self.shapeChangedSinceLastDraw = TRUE;
626 [self checkTransparency];
629 - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
631 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
634 event.type = MOUSE_BUTTON;
635 event.window = (macdrv_window)[self retain];
636 event.mouse_button.button = [theEvent buttonNumber];
637 event.mouse_button.pressed = pressed;
638 event.mouse_button.x = pt.x;
639 event.mouse_button.y = pt.y;
640 event.mouse_button.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
642 [queue postEvent:&event];
649 [NSApp transformProcessToForeground];
651 /* If a borderless window is offscreen, orderFront: won't move
652 it onscreen like it would for a titled window. Do that ourselves. */
653 screens = [NSScreen screens];
654 if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
655 !frame_intersects_screens([self frame], screens))
657 NSScreen* primaryScreen = [screens objectAtIndex:0];
658 NSRect frame = [primaryScreen frame];
659 [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
660 frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
661 [self setFrame:frame display:YES];
664 if ([[NSApp orderedWineWindows] count])
668 front = [[NSApp orderedWineWindows] objectAtIndex:0];
671 for (front in [NSApp orderedWineWindows])
672 if (!front.floating) break;
674 if (front && [front levelWhenActive] > [self levelWhenActive])
675 [self setLevelWhenActive:[front levelWhenActive]];
677 [self orderFront:nil];
678 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
679 causing_becomeKeyWindow = TRUE;
680 [self makeKeyWindow];
681 causing_becomeKeyWindow = FALSE;
682 if (latentParentWindow)
684 if ([latentParentWindow level] > [self level])
685 [self setLevelWhenActive:[latentParentWindow level]];
686 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
687 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
688 self.latentParentWindow = nil;
690 if (![self isExcludedFromWindowsMenu])
691 [NSApp addWindowsItem:self title:[self title] filename:NO];
693 /* Cocoa may adjust the frame when the window is ordered onto the screen.
694 Generate a frame-changed event just in case. The back end will ignore
695 it if nothing actually changed. */
696 [self windowDidResize:nil];
699 - (void) postKey:(uint16_t)keyCode
700 pressed:(BOOL)pressed
701 modifiers:(NSUInteger)modifiers
702 event:(NSEvent*)theEvent
706 WineApplication* app = (WineApplication*)NSApp;
708 event.type = pressed ? KEY_PRESS : KEY_RELEASE;
709 event.window = (macdrv_window)[self retain];
710 event.key.keycode = keyCode;
711 event.key.modifiers = modifiers;
712 event.key.time_ms = [app ticksForEventTime:[theEvent timestamp]];
714 if ((cgevent = [theEvent CGEvent]))
716 CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
717 kCGKeyboardEventKeyboardType);
718 if (keyboardType != app.keyboardType)
720 app.keyboardType = keyboardType;
721 [app keyboardSelectionDidChange];
725 [queue postEvent:&event];
728 - (void) postKeyEvent:(NSEvent *)theEvent
730 [self flagsChanged:theEvent];
731 [self postKey:[theEvent keyCode]
732 pressed:[theEvent type] == NSKeyDown
733 modifiers:[theEvent modifierFlags]
737 - (void) postMouseMovedEvent:(NSEvent *)theEvent
741 if (forceNextMouseMoveAbsolute)
743 CGPoint point = CGEventGetLocation([theEvent CGEvent]);
745 event.type = MOUSE_MOVED_ABSOLUTE;
746 event.mouse_moved.x = point.x;
747 event.mouse_moved.y = point.y;
752 forceNextMouseMoveAbsolute = FALSE;
756 /* Add event delta to accumulated delta error */
757 /* deltaY is already flipped */
758 mouseMoveDeltaX += [theEvent deltaX];
759 mouseMoveDeltaY += [theEvent deltaY];
761 event.type = MOUSE_MOVED;
762 event.mouse_moved.x = mouseMoveDeltaX;
763 event.mouse_moved.y = mouseMoveDeltaY;
765 /* Keep the remainder after integer truncation. */
766 mouseMoveDeltaX -= event.mouse_moved.x;
767 mouseMoveDeltaY -= event.mouse_moved.y;
770 if (event.type == MOUSE_MOVED_ABSOLUTE || event.mouse_moved.x || event.mouse_moved.y)
772 event.window = (macdrv_window)[self retain];
773 event.mouse_moved.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
775 [queue postEvent:&event];
779 - (void) setLevelWhenActive:(NSInteger)level
781 levelWhenActive = level;
782 if (([NSApp isActive] || level <= NSFloatingWindowLevel) &&
783 level != [self level])
784 [self setLevel:level];
789 * ---------- NSWindow method overrides ----------
791 - (BOOL) canBecomeKeyWindow
793 if (causing_becomeKeyWindow) return YES;
794 if (self.disabled || self.noActivate) return NO;
795 return [self isKeyWindow];
798 - (BOOL) canBecomeMainWindow
800 return [self canBecomeKeyWindow];
803 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
805 // If a window is sized to completely cover a screen, then it's in
806 // full-screen mode. In that case, we don't allow NSWindow to constrain
808 NSRect contentRect = [self contentRectForFrameRect:frameRect];
809 if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
810 frameRect = [super constrainFrameRect:frameRect toScreen:screen];
814 - (BOOL) isExcludedFromWindowsMenu
816 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
819 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
821 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
822 return [self isKeyWindow] || (!self.disabled && !self.noActivate);
823 return [super validateMenuItem:menuItem];
826 /* We don't call this. It's the action method of the items in the Window menu. */
827 - (void) makeKeyAndOrderFront:(id)sender
829 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
830 [NSApp windowGotFocus:self];
833 - (void) sendEvent:(NSEvent*)event
835 /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
836 interface control. For example, Control-Tab switches focus among
837 views. We want to bypass that feature, so directly route key-down
838 events to -keyDown:. */
839 if ([event type] == NSKeyDown)
840 [[self firstResponder] keyDown:event];
843 if ([event type] == NSLeftMouseDown)
845 NSWindowButton windowButton;
846 BOOL broughtWindowForward = TRUE;
848 /* Since our windows generally claim they can't be made key, clicks
849 in their title bars are swallowed by the theme frame stuff. So,
850 we hook directly into the event stream and assume that any click
851 in the window will activate it, if Wine and the Win32 program
853 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
854 [NSApp windowGotFocus:self];
856 /* Any left-click on our window anyplace other than the close or
857 minimize buttons will bring it forward. */
858 for (windowButton = NSWindowCloseButton;
859 windowButton <= NSWindowMiniaturizeButton;
862 NSButton* button = [[event window] standardWindowButton:windowButton];
865 NSPoint point = [button convertPoint:[event locationInWindow] fromView:nil];
866 if ([button mouse:point inRect:[button bounds]])
868 broughtWindowForward = FALSE;
874 if (broughtWindowForward)
875 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
878 [super sendEvent:event];
884 * ---------- NSResponder method overrides ----------
886 - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
887 - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
888 - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
890 - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
891 - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
892 - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
894 - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
895 - (void) keyUp:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
897 - (void) flagsChanged:(NSEvent *)theEvent
899 static const struct {
903 { NX_ALPHASHIFTMASK, kVK_CapsLock },
904 { NX_DEVICELSHIFTKEYMASK, kVK_Shift },
905 { NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
906 { NX_DEVICELCTLKEYMASK, kVK_Control },
907 { NX_DEVICERCTLKEYMASK, kVK_RightControl },
908 { NX_DEVICELALTKEYMASK, kVK_Option },
909 { NX_DEVICERALTKEYMASK, kVK_RightOption },
910 { NX_DEVICELCMDKEYMASK, kVK_Command },
911 { NX_DEVICERCMDKEYMASK, kVK_RightCommand },
914 NSUInteger modifierFlags = [theEvent modifierFlags];
918 fix_device_modifiers_by_generic(&modifierFlags);
919 changed = modifierFlags ^ lastModifierFlags;
922 for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
923 if (changed & modifiers[i].mask)
926 for (i = 0; i <= last_changed; i++)
928 if (changed & modifiers[i].mask)
930 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
932 if (i == last_changed)
933 lastModifierFlags = modifierFlags;
936 lastModifierFlags ^= modifiers[i].mask;
937 fix_generic_modifiers_by_device(&lastModifierFlags);
940 // Caps lock generates one event for each press-release action.
941 // We need to simulate a pair of events for each actual event.
942 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
944 [self postKey:modifiers[i].keycode
946 modifiers:lastModifierFlags
947 event:(NSEvent*)theEvent];
951 [self postKey:modifiers[i].keycode
953 modifiers:lastModifierFlags
954 event:(NSEvent*)theEvent];
959 - (void) mouseEntered:(NSEvent *)theEvent { forceNextMouseMoveAbsolute = TRUE; }
960 - (void) mouseExited:(NSEvent *)theEvent { forceNextMouseMoveAbsolute = TRUE; }
962 - (void) mouseMoved:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
963 - (void) mouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
964 - (void) rightMouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
965 - (void) otherMouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
967 - (void) scrollWheel:(NSEvent *)theEvent
973 BOOL continuous = FALSE;
975 cgevent = [theEvent CGEvent];
976 pt = CGEventGetLocation(cgevent);
978 event.type = MOUSE_SCROLL;
979 event.window = (macdrv_window)[self retain];
980 event.mouse_scroll.x = pt.x;
981 event.mouse_scroll.y = pt.y;
982 event.mouse_scroll.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
984 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
988 /* Continuous scroll wheel events come from high-precision scrolling
989 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
990 For these, we can get more precise data from the CGEvent API. */
991 /* Axis 1 is vertical, axis 2 is horizontal. */
992 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
993 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
997 double pixelsPerLine = 10;
998 CGEventSourceRef source;
1000 /* The non-continuous values are in units of "lines", not pixels. */
1001 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1003 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1007 x = pixelsPerLine * [theEvent deltaX];
1008 y = pixelsPerLine * [theEvent deltaY];
1011 /* Mac: negative is right or down, positive is left or up.
1012 Win32: negative is left or down, positive is right or up.
1013 So, negate the X scroll value to translate. */
1016 /* The x,y values so far are in pixels. Win32 expects to receive some
1017 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1018 6 times the pixel value. */
1019 event.mouse_scroll.x_scroll = 6 * x;
1020 event.mouse_scroll.y_scroll = 6 * y;
1024 /* For non-continuous "clicky" wheels, if there was any motion, make
1025 sure there was at least WHEEL_DELTA motion. This is so, at slow
1026 speeds where the system's acceleration curve is actually reducing the
1027 scroll distance, the user is sure to get some action out of each click.
1028 For example, this is important for rotating though weapons in a
1029 first-person shooter. */
1030 if (0 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 120)
1031 event.mouse_scroll.x_scroll = 120;
1032 else if (-120 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 0)
1033 event.mouse_scroll.x_scroll = -120;
1035 if (0 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 120)
1036 event.mouse_scroll.y_scroll = 120;
1037 else if (-120 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 0)
1038 event.mouse_scroll.y_scroll = -120;
1041 if (event.mouse_scroll.x_scroll || event.mouse_scroll.y_scroll)
1042 [queue postEvent:&event];
1047 * ---------- NSWindowDelegate methods ----------
1049 - (void)windowDidBecomeKey:(NSNotification *)notification
1051 NSEvent* event = [NSApp lastFlagsChanged];
1053 [self flagsChanged:event];
1055 if (causing_becomeKeyWindow) return;
1057 [NSApp windowGotFocus:self];
1060 - (void)windowDidDeminiaturize:(NSNotification *)notification
1062 if (!ignore_windowDeminiaturize)
1066 /* Coalesce events by discarding any previous ones still in the queue. */
1067 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1068 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1071 event.type = WINDOW_DID_UNMINIMIZE;
1072 event.window = (macdrv_window)[self retain];
1073 [queue postEvent:&event];
1076 ignore_windowDeminiaturize = FALSE;
1078 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1081 - (void)windowDidMove:(NSNotification *)notification
1083 [self windowDidResize:notification];
1086 - (void)windowDidResignKey:(NSNotification *)notification
1090 if (causing_becomeKeyWindow) return;
1092 event.type = WINDOW_LOST_FOCUS;
1093 event.window = (macdrv_window)[self retain];
1094 [queue postEvent:&event];
1097 - (void)windowDidResize:(NSNotification *)notification
1100 NSRect frame = [self contentRectForFrameRect:[self frame]];
1102 [[self class] flipRect:&frame];
1104 /* Coalesce events by discarding any previous ones still in the queue. */
1105 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1108 event.type = WINDOW_FRAME_CHANGED;
1109 event.window = (macdrv_window)[self retain];
1110 event.window_frame_changed.frame = NSRectToCGRect(frame);
1111 [queue postEvent:&event];
1114 - (BOOL)windowShouldClose:(id)sender
1117 event.type = WINDOW_CLOSE_REQUESTED;
1118 event.window = (macdrv_window)[self retain];
1119 [queue postEvent:&event];
1123 - (void)windowWillMiniaturize:(NSNotification *)notification
1125 if (!ignore_windowMiniaturize)
1129 /* Coalesce events by discarding any previous ones still in the queue. */
1130 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1131 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1134 event.type = WINDOW_DID_MINIMIZE;
1135 event.window = (macdrv_window)[self retain];
1136 [queue postEvent:&event];
1139 ignore_windowMiniaturize = FALSE;
1145 /***********************************************************************
1146 * macdrv_create_cocoa_window
1148 * Create a Cocoa window with the given content frame and features (e.g.
1149 * title bar, close box, etc.).
1151 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1152 CGRect frame, void* hwnd, macdrv_event_queue queue)
1154 __block WineWindow* window;
1157 window = [[WineWindow createWindowWithFeatures:wf
1158 windowFrame:NSRectFromCGRect(frame)
1160 queue:(WineEventQueue*)queue] retain];
1163 return (macdrv_window)window;
1166 /***********************************************************************
1167 * macdrv_destroy_cocoa_window
1169 * Destroy a Cocoa window.
1171 void macdrv_destroy_cocoa_window(macdrv_window w)
1173 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1174 WineWindow* window = (WineWindow*)w;
1176 [window.queue discardEventsMatchingMask:-1 forWindow:window];
1183 /***********************************************************************
1184 * macdrv_get_window_hwnd
1186 * Get the hwnd that was set for the window at creation.
1188 void* macdrv_get_window_hwnd(macdrv_window w)
1190 WineWindow* window = (WineWindow*)w;
1194 /***********************************************************************
1195 * macdrv_set_cocoa_window_features
1197 * Update a Cocoa window's features.
1199 void macdrv_set_cocoa_window_features(macdrv_window w,
1200 const struct macdrv_window_features* wf)
1202 WineWindow* window = (WineWindow*)w;
1205 [window setWindowFeatures:wf];
1209 /***********************************************************************
1210 * macdrv_set_cocoa_window_state
1212 * Update a Cocoa window's state.
1214 void macdrv_set_cocoa_window_state(macdrv_window w,
1215 const struct macdrv_window_state* state)
1217 WineWindow* window = (WineWindow*)w;
1220 [window setMacDrvState:state];
1224 /***********************************************************************
1225 * macdrv_set_cocoa_window_title
1227 * Set a Cocoa window's title.
1229 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1232 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1233 WineWindow* window = (WineWindow*)w;
1234 NSString* titleString;
1237 titleString = [NSString stringWithCharacters:title length:length];
1240 OnMainThreadAsync(^{
1241 [window setTitle:titleString];
1242 if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1243 [NSApp changeWindowsItem:window title:titleString filename:NO];
1249 /***********************************************************************
1250 * macdrv_order_cocoa_window
1252 * Reorder a Cocoa window relative to other windows. If prev is
1253 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
1254 * it is ordered above that window. Otherwise, it is ordered to the
1257 * Returns true if the window has actually been ordered onto the screen
1258 * (i.e. if its frame intersects with a screen). Otherwise, false.
1260 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1263 WineWindow* window = (WineWindow*)w;
1264 __block BOOL on_screen;
1267 on_screen = [window orderBelow:(WineWindow*)prev
1268 orAbove:(WineWindow*)next];
1274 /***********************************************************************
1275 * macdrv_hide_cocoa_window
1277 * Hides a Cocoa window.
1279 void macdrv_hide_cocoa_window(macdrv_window w)
1281 WineWindow* window = (WineWindow*)w;
1284 [window doOrderOut];
1288 /***********************************************************************
1289 * macdrv_set_cocoa_window_frame
1291 * Move a Cocoa window. If the window has been moved out of the bounds
1292 * of the desktop, it is ordered out. (This routine won't ever order a
1293 * window in, though.)
1295 * Returns true if the window is on screen; false otherwise.
1297 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1299 WineWindow* window = (WineWindow*)w;
1300 __block BOOL on_screen;
1303 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1309 /***********************************************************************
1310 * macdrv_get_cocoa_window_frame
1312 * Gets the frame of a Cocoa window.
1314 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1316 WineWindow* window = (WineWindow*)w;
1321 frame = [window contentRectForFrameRect:[window frame]];
1322 [[window class] flipRect:&frame];
1323 *out_frame = NSRectToCGRect(frame);
1327 /***********************************************************************
1328 * macdrv_set_cocoa_parent_window
1330 * Sets the parent window for a Cocoa window. If parent is NULL, clears
1331 * the parent window.
1333 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1335 WineWindow* window = (WineWindow*)w;
1338 [window setMacDrvParentWindow:(WineWindow*)parent];
1342 /***********************************************************************
1343 * macdrv_set_window_surface
1345 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1347 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1348 WineWindow* window = (WineWindow*)w;
1351 window.surface = surface;
1352 window.surface_mutex = mutex;
1358 /***********************************************************************
1359 * macdrv_window_needs_display
1361 * Mark a window as needing display in a specified rect (in non-client
1362 * area coordinates).
1364 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1366 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1367 WineWindow* window = (WineWindow*)w;
1369 OnMainThreadAsync(^{
1370 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1376 /***********************************************************************
1377 * macdrv_set_window_shape
1379 * Sets the shape of a Cocoa window from an array of rectangles. If
1380 * rects is NULL, resets the window's shape to its frame.
1382 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1384 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1385 WineWindow* window = (WineWindow*)w;
1388 if (!rects || !count)
1395 path = [NSBezierPath bezierPath];
1396 for (i = 0; i < count; i++)
1397 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1398 window.shape = path;
1405 /***********************************************************************
1406 * macdrv_set_window_alpha
1408 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1410 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1411 WineWindow* window = (WineWindow*)w;
1413 [window setAlphaValue:alpha];
1418 /***********************************************************************
1419 * macdrv_set_window_color_key
1421 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1424 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1425 WineWindow* window = (WineWindow*)w;
1428 window.colorKeyed = TRUE;
1429 window.colorKeyRed = keyRed;
1430 window.colorKeyGreen = keyGreen;
1431 window.colorKeyBlue = keyBlue;
1432 [window checkTransparency];
1438 /***********************************************************************
1439 * macdrv_clear_window_color_key
1441 void macdrv_clear_window_color_key(macdrv_window w)
1443 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1444 WineWindow* window = (WineWindow*)w;
1447 window.colorKeyed = FALSE;
1448 [window checkTransparency];
1454 /***********************************************************************
1455 * macdrv_window_use_per_pixel_alpha
1457 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1459 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1460 WineWindow* window = (WineWindow*)w;
1463 window.usePerPixelAlpha = use_per_pixel_alpha;
1464 [window checkTransparency];
1470 /***********************************************************************
1471 * macdrv_give_cocoa_window_focus
1473 * Makes the Cocoa window "key" (gives it keyboard focus). This also
1474 * orders it front and, if its frame was not within the desktop bounds,
1475 * Cocoa will typically move it on-screen.
1477 void macdrv_give_cocoa_window_focus(macdrv_window w)
1479 WineWindow* window = (WineWindow*)w;
1482 [window makeFocused];