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
341 BOOL fullscreen, captured;
344 WineWindow* other = nil;
346 screen = screen_covered_by_rect([self frame], [NSScreen screens]);
347 fullscreen = (screen != nil);
348 captured = (screen || [self screen]) && [NSApp areDisplaysCaptured];
350 if (captured || fullscreen)
353 level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
355 level = NSMainMenuWindowLevel + 1;
360 else if (self.floating)
361 level = NSFloatingWindowLevel;
363 level = NSNormalWindowLevel;
365 index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:self];
366 if (index != NSNotFound && index + 1 < [[NSApp orderedWineWindows] count])
368 other = [[NSApp orderedWineWindows] objectAtIndex:index + 1];
369 if (level < [other level])
370 level = [other level];
373 if (level != [self level])
375 [self setLevelWhenActive:level];
377 /* Setting the window level above has moved this window to the front
378 of all other windows at the same level. We need to move it
379 back into its proper place among other windows of that level.
380 Also, any windows which are supposed to be in front of it had
381 better have the same or higher window level. If not, bump them
383 if (index != NSNotFound)
385 for (; index > 0; index--)
387 other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
388 if ([other level] < level)
389 [other setLevelWhenActive:level];
392 [self orderWindow:NSWindowBelow relativeTo:[other windowNumber]];
400 - (void) setMacDrvState:(const struct macdrv_window_state*)state
402 NSWindowCollectionBehavior behavior;
404 self.disabled = state->disabled;
405 self.noActivate = state->no_activate;
407 self.floating = state->floating;
408 [self adjustWindowLevel];
410 behavior = NSWindowCollectionBehaviorDefault;
411 if (state->excluded_by_expose)
412 behavior |= NSWindowCollectionBehaviorTransient;
414 behavior |= NSWindowCollectionBehaviorManaged;
415 if (state->excluded_by_cycle)
417 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
418 if ([self isVisible])
419 [NSApp removeWindowsItem:self];
423 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
424 if ([self isVisible])
425 [NSApp addWindowsItem:self title:[self title] filename:NO];
427 [self setCollectionBehavior:behavior];
429 if (state->minimized && ![self isMiniaturized])
431 ignore_windowMiniaturize = TRUE;
432 [self miniaturize:nil];
434 else if (!state->minimized && [self isMiniaturized])
436 ignore_windowDeminiaturize = TRUE;
437 [self deminiaturize:nil];
440 /* Whatever events regarding minimization might have been in the queue are now stale. */
441 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
442 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
446 /* Returns whether or not the window was ordered in, which depends on if
447 its frame intersects any screen. */
448 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
450 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
453 [NSApp transformProcessToForeground];
457 /* Make sure that windows that should be above this one really are.
458 This is necessary since a full-screen window gets a boost to its
459 window level to be in front of the menu bar and Dock and that moves
460 it out of the z-order that Win32 would otherwise establish. */
461 if ([prev level] < [self level])
463 NSUInteger index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:prev];
464 if (index != NSNotFound)
466 [prev setLevelWhenActive:[self level]];
467 for (; index > 0; index--)
469 WineWindow* other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
470 if ([other level] < [self level])
471 [other setLevelWhenActive:[self level]];
475 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
476 [NSApp wineWindow:self ordered:NSWindowBelow relativeTo:prev];
480 /* Similarly, make sure this window is really above what it should be. */
481 if (next && [next level] > [self level])
482 [self setLevelWhenActive:[next level]];
483 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
484 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:next];
486 if (latentParentWindow)
488 if ([latentParentWindow level] > [self level])
489 [self setLevelWhenActive:[latentParentWindow level]];
490 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
491 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
492 self.latentParentWindow = nil;
495 /* Cocoa may adjust the frame when the window is ordered onto the screen.
496 Generate a frame-changed event just in case. The back end will ignore
497 it if nothing actually changed. */
498 [self windowDidResize:nil];
500 if (![self isExcludedFromWindowsMenu])
501 [NSApp addWindowsItem:self title:[self title] filename:NO];
509 self.latentParentWindow = [self parentWindow];
510 [latentParentWindow removeChildWindow:self];
511 forceNextMouseMoveAbsolute = TRUE;
513 [NSApp wineWindow:self ordered:NSWindowOut relativeTo:nil];
514 [NSApp removeWindowsItem:self];
517 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
519 NSArray* screens = [NSScreen screens];
520 BOOL on_screen = [self isVisible];
521 NSRect frame, oldFrame;
523 if (![screens count]) return on_screen;
525 /* Origin is (left, top) in a top-down space. Need to convert it to
526 (left, bottom) in a bottom-up space. */
527 [[self class] flipRect:&contentRect];
531 on_screen = frame_intersects_screens(contentRect, screens);
536 if (!NSIsEmptyRect(contentRect))
538 oldFrame = [self frame];
539 frame = [self frameRectForContentRect:contentRect];
540 if (!NSEqualRects(frame, oldFrame))
542 if (NSEqualSizes(frame.size, oldFrame.size))
543 [self setFrameOrigin:frame.origin];
545 [self setFrame:frame display:YES];
551 [self adjustWindowLevel];
553 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
554 event. The back end will ignore it if nothing actually changed. */
555 [self windowDidResize:nil];
559 /* The back end is establishing a new window size and position. It's
560 not interested in any stale events regarding those that may be sitting
562 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
569 - (void) setMacDrvParentWindow:(WineWindow*)parent
571 if ([self parentWindow] != parent)
573 [[self parentWindow] removeChildWindow:self];
574 self.latentParentWindow = nil;
575 if ([self isVisible] && parent)
577 if ([parent level] > [self level])
578 [self setLevelWhenActive:[parent level]];
579 [parent addChildWindow:self ordered:NSWindowAbove];
580 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:parent];
583 self.latentParentWindow = parent;
587 - (void) setDisabled:(BOOL)newValue
589 if (disabled != newValue)
592 [self adjustFeaturesForState];
596 - (BOOL) needsTransparency
598 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
601 - (void) checkTransparency
603 if (![self isOpaque] && !self.needsTransparency)
605 [self setBackgroundColor:[NSColor windowBackgroundColor]];
606 [self setOpaque:YES];
608 else if ([self isOpaque] && self.needsTransparency)
610 [self setBackgroundColor:[NSColor clearColor]];
615 - (void) setShape:(NSBezierPath*)newShape
617 if (shape == newShape) return;
618 if (shape && newShape && [shape isEqual:newShape]) return;
622 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
626 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
628 shape = [newShape copy];
629 self.shapeChangedSinceLastDraw = TRUE;
631 [self checkTransparency];
634 - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
636 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
639 event.type = MOUSE_BUTTON;
640 event.window = (macdrv_window)[self retain];
641 event.mouse_button.button = [theEvent buttonNumber];
642 event.mouse_button.pressed = pressed;
643 event.mouse_button.x = pt.x;
644 event.mouse_button.y = pt.y;
645 event.mouse_button.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
647 [queue postEvent:&event];
654 [NSApp transformProcessToForeground];
656 /* If a borderless window is offscreen, orderFront: won't move
657 it onscreen like it would for a titled window. Do that ourselves. */
658 screens = [NSScreen screens];
659 if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
660 !frame_intersects_screens([self frame], screens))
662 NSScreen* primaryScreen = [screens objectAtIndex:0];
663 NSRect frame = [primaryScreen frame];
664 [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
665 frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
666 [self setFrame:frame display:YES];
669 if ([[NSApp orderedWineWindows] count])
673 front = [[NSApp orderedWineWindows] objectAtIndex:0];
676 for (front in [NSApp orderedWineWindows])
677 if (!front.floating) break;
679 if (front && [front levelWhenActive] > [self levelWhenActive])
680 [self setLevelWhenActive:[front levelWhenActive]];
682 [self orderFront:nil];
683 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
684 causing_becomeKeyWindow = TRUE;
685 [self makeKeyWindow];
686 causing_becomeKeyWindow = FALSE;
687 if (latentParentWindow)
689 if ([latentParentWindow level] > [self level])
690 [self setLevelWhenActive:[latentParentWindow level]];
691 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
692 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
693 self.latentParentWindow = nil;
695 if (![self isExcludedFromWindowsMenu])
696 [NSApp addWindowsItem:self title:[self title] filename:NO];
698 /* Cocoa may adjust the frame when the window is ordered onto the screen.
699 Generate a frame-changed event just in case. The back end will ignore
700 it if nothing actually changed. */
701 [self windowDidResize:nil];
704 - (void) postKey:(uint16_t)keyCode
705 pressed:(BOOL)pressed
706 modifiers:(NSUInteger)modifiers
707 event:(NSEvent*)theEvent
711 WineApplication* app = (WineApplication*)NSApp;
713 event.type = pressed ? KEY_PRESS : KEY_RELEASE;
714 event.window = (macdrv_window)[self retain];
715 event.key.keycode = keyCode;
716 event.key.modifiers = modifiers;
717 event.key.time_ms = [app ticksForEventTime:[theEvent timestamp]];
719 if ((cgevent = [theEvent CGEvent]))
721 CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
722 kCGKeyboardEventKeyboardType);
723 if (keyboardType != app.keyboardType)
725 app.keyboardType = keyboardType;
726 [app keyboardSelectionDidChange];
730 [queue postEvent:&event];
733 - (void) postKeyEvent:(NSEvent *)theEvent
735 [self flagsChanged:theEvent];
736 [self postKey:[theEvent keyCode]
737 pressed:[theEvent type] == NSKeyDown
738 modifiers:[theEvent modifierFlags]
742 - (void) postMouseMovedEvent:(NSEvent *)theEvent
746 if (forceNextMouseMoveAbsolute)
748 CGPoint point = CGEventGetLocation([theEvent CGEvent]);
750 event.type = MOUSE_MOVED_ABSOLUTE;
751 event.mouse_moved.x = point.x;
752 event.mouse_moved.y = point.y;
757 forceNextMouseMoveAbsolute = FALSE;
761 /* Add event delta to accumulated delta error */
762 /* deltaY is already flipped */
763 mouseMoveDeltaX += [theEvent deltaX];
764 mouseMoveDeltaY += [theEvent deltaY];
766 event.type = MOUSE_MOVED;
767 event.mouse_moved.x = mouseMoveDeltaX;
768 event.mouse_moved.y = mouseMoveDeltaY;
770 /* Keep the remainder after integer truncation. */
771 mouseMoveDeltaX -= event.mouse_moved.x;
772 mouseMoveDeltaY -= event.mouse_moved.y;
775 if (event.type == MOUSE_MOVED_ABSOLUTE || event.mouse_moved.x || event.mouse_moved.y)
777 event.window = (macdrv_window)[self retain];
778 event.mouse_moved.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
780 [queue postEvent:&event];
784 - (void) setLevelWhenActive:(NSInteger)level
786 levelWhenActive = level;
787 if (([NSApp isActive] || level <= NSFloatingWindowLevel) &&
788 level != [self level])
789 [self setLevel:level];
794 * ---------- NSWindow method overrides ----------
796 - (BOOL) canBecomeKeyWindow
798 if (causing_becomeKeyWindow) return YES;
799 if (self.disabled || self.noActivate) return NO;
800 return [self isKeyWindow];
803 - (BOOL) canBecomeMainWindow
805 return [self canBecomeKeyWindow];
808 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
810 // If a window is sized to completely cover a screen, then it's in
811 // full-screen mode. In that case, we don't allow NSWindow to constrain
813 NSRect contentRect = [self contentRectForFrameRect:frameRect];
814 if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
815 frameRect = [super constrainFrameRect:frameRect toScreen:screen];
819 - (BOOL) isExcludedFromWindowsMenu
821 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
824 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
826 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
827 return [self isKeyWindow] || (!self.disabled && !self.noActivate);
828 return [super validateMenuItem:menuItem];
831 /* We don't call this. It's the action method of the items in the Window menu. */
832 - (void) makeKeyAndOrderFront:(id)sender
834 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
835 [NSApp windowGotFocus:self];
838 - (void) sendEvent:(NSEvent*)event
840 /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
841 interface control. For example, Control-Tab switches focus among
842 views. We want to bypass that feature, so directly route key-down
843 events to -keyDown:. */
844 if ([event type] == NSKeyDown)
845 [[self firstResponder] keyDown:event];
848 if ([event type] == NSLeftMouseDown)
850 NSWindowButton windowButton;
851 BOOL broughtWindowForward = TRUE;
853 /* Since our windows generally claim they can't be made key, clicks
854 in their title bars are swallowed by the theme frame stuff. So,
855 we hook directly into the event stream and assume that any click
856 in the window will activate it, if Wine and the Win32 program
858 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
859 [NSApp windowGotFocus:self];
861 /* Any left-click on our window anyplace other than the close or
862 minimize buttons will bring it forward. */
863 for (windowButton = NSWindowCloseButton;
864 windowButton <= NSWindowMiniaturizeButton;
867 NSButton* button = [[event window] standardWindowButton:windowButton];
870 NSPoint point = [button convertPoint:[event locationInWindow] fromView:nil];
871 if ([button mouse:point inRect:[button bounds]])
873 broughtWindowForward = FALSE;
879 if (broughtWindowForward)
880 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
883 [super sendEvent:event];
889 * ---------- NSResponder method overrides ----------
891 - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
892 - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
893 - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
895 - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
896 - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
897 - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
899 - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
900 - (void) keyUp:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
902 - (void) flagsChanged:(NSEvent *)theEvent
904 static const struct {
908 { NX_ALPHASHIFTMASK, kVK_CapsLock },
909 { NX_DEVICELSHIFTKEYMASK, kVK_Shift },
910 { NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
911 { NX_DEVICELCTLKEYMASK, kVK_Control },
912 { NX_DEVICERCTLKEYMASK, kVK_RightControl },
913 { NX_DEVICELALTKEYMASK, kVK_Option },
914 { NX_DEVICERALTKEYMASK, kVK_RightOption },
915 { NX_DEVICELCMDKEYMASK, kVK_Command },
916 { NX_DEVICERCMDKEYMASK, kVK_RightCommand },
919 NSUInteger modifierFlags = [theEvent modifierFlags];
923 fix_device_modifiers_by_generic(&modifierFlags);
924 changed = modifierFlags ^ lastModifierFlags;
927 for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
928 if (changed & modifiers[i].mask)
931 for (i = 0; i <= last_changed; i++)
933 if (changed & modifiers[i].mask)
935 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
937 if (i == last_changed)
938 lastModifierFlags = modifierFlags;
941 lastModifierFlags ^= modifiers[i].mask;
942 fix_generic_modifiers_by_device(&lastModifierFlags);
945 // Caps lock generates one event for each press-release action.
946 // We need to simulate a pair of events for each actual event.
947 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
949 [self postKey:modifiers[i].keycode
951 modifiers:lastModifierFlags
952 event:(NSEvent*)theEvent];
956 [self postKey:modifiers[i].keycode
958 modifiers:lastModifierFlags
959 event:(NSEvent*)theEvent];
964 - (void) mouseEntered:(NSEvent *)theEvent { forceNextMouseMoveAbsolute = TRUE; }
965 - (void) mouseExited:(NSEvent *)theEvent { forceNextMouseMoveAbsolute = TRUE; }
967 - (void) mouseMoved:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
968 - (void) mouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
969 - (void) rightMouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
970 - (void) otherMouseDragged:(NSEvent *)theEvent { [self postMouseMovedEvent:theEvent]; }
972 - (void) scrollWheel:(NSEvent *)theEvent
978 BOOL continuous = FALSE;
980 cgevent = [theEvent CGEvent];
981 pt = CGEventGetLocation(cgevent);
983 event.type = MOUSE_SCROLL;
984 event.window = (macdrv_window)[self retain];
985 event.mouse_scroll.x = pt.x;
986 event.mouse_scroll.y = pt.y;
987 event.mouse_scroll.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
989 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
993 /* Continuous scroll wheel events come from high-precision scrolling
994 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
995 For these, we can get more precise data from the CGEvent API. */
996 /* Axis 1 is vertical, axis 2 is horizontal. */
997 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
998 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1002 double pixelsPerLine = 10;
1003 CGEventSourceRef source;
1005 /* The non-continuous values are in units of "lines", not pixels. */
1006 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1008 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1012 x = pixelsPerLine * [theEvent deltaX];
1013 y = pixelsPerLine * [theEvent deltaY];
1016 /* Mac: negative is right or down, positive is left or up.
1017 Win32: negative is left or down, positive is right or up.
1018 So, negate the X scroll value to translate. */
1021 /* The x,y values so far are in pixels. Win32 expects to receive some
1022 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1023 6 times the pixel value. */
1024 event.mouse_scroll.x_scroll = 6 * x;
1025 event.mouse_scroll.y_scroll = 6 * y;
1029 /* For non-continuous "clicky" wheels, if there was any motion, make
1030 sure there was at least WHEEL_DELTA motion. This is so, at slow
1031 speeds where the system's acceleration curve is actually reducing the
1032 scroll distance, the user is sure to get some action out of each click.
1033 For example, this is important for rotating though weapons in a
1034 first-person shooter. */
1035 if (0 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 120)
1036 event.mouse_scroll.x_scroll = 120;
1037 else if (-120 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 0)
1038 event.mouse_scroll.x_scroll = -120;
1040 if (0 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 120)
1041 event.mouse_scroll.y_scroll = 120;
1042 else if (-120 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 0)
1043 event.mouse_scroll.y_scroll = -120;
1046 if (event.mouse_scroll.x_scroll || event.mouse_scroll.y_scroll)
1047 [queue postEvent:&event];
1052 * ---------- NSWindowDelegate methods ----------
1054 - (void)windowDidBecomeKey:(NSNotification *)notification
1056 NSEvent* event = [NSApp lastFlagsChanged];
1058 [self flagsChanged:event];
1060 if (causing_becomeKeyWindow) return;
1062 [NSApp windowGotFocus:self];
1065 - (void)windowDidDeminiaturize:(NSNotification *)notification
1067 if (!ignore_windowDeminiaturize)
1071 /* Coalesce events by discarding any previous ones still in the queue. */
1072 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1073 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1076 event.type = WINDOW_DID_UNMINIMIZE;
1077 event.window = (macdrv_window)[self retain];
1078 [queue postEvent:&event];
1081 ignore_windowDeminiaturize = FALSE;
1083 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1086 - (void)windowDidMove:(NSNotification *)notification
1088 [self windowDidResize:notification];
1091 - (void)windowDidResignKey:(NSNotification *)notification
1095 if (causing_becomeKeyWindow) return;
1097 event.type = WINDOW_LOST_FOCUS;
1098 event.window = (macdrv_window)[self retain];
1099 [queue postEvent:&event];
1102 - (void)windowDidResize:(NSNotification *)notification
1105 NSRect frame = [self contentRectForFrameRect:[self frame]];
1107 [[self class] flipRect:&frame];
1109 /* Coalesce events by discarding any previous ones still in the queue. */
1110 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1113 event.type = WINDOW_FRAME_CHANGED;
1114 event.window = (macdrv_window)[self retain];
1115 event.window_frame_changed.frame = NSRectToCGRect(frame);
1116 [queue postEvent:&event];
1119 - (BOOL)windowShouldClose:(id)sender
1122 event.type = WINDOW_CLOSE_REQUESTED;
1123 event.window = (macdrv_window)[self retain];
1124 [queue postEvent:&event];
1128 - (void)windowWillMiniaturize:(NSNotification *)notification
1130 if (!ignore_windowMiniaturize)
1134 /* Coalesce events by discarding any previous ones still in the queue. */
1135 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1136 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1139 event.type = WINDOW_DID_MINIMIZE;
1140 event.window = (macdrv_window)[self retain];
1141 [queue postEvent:&event];
1144 ignore_windowMiniaturize = FALSE;
1150 /***********************************************************************
1151 * macdrv_create_cocoa_window
1153 * Create a Cocoa window with the given content frame and features (e.g.
1154 * title bar, close box, etc.).
1156 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1157 CGRect frame, void* hwnd, macdrv_event_queue queue)
1159 __block WineWindow* window;
1162 window = [[WineWindow createWindowWithFeatures:wf
1163 windowFrame:NSRectFromCGRect(frame)
1165 queue:(WineEventQueue*)queue] retain];
1168 return (macdrv_window)window;
1171 /***********************************************************************
1172 * macdrv_destroy_cocoa_window
1174 * Destroy a Cocoa window.
1176 void macdrv_destroy_cocoa_window(macdrv_window w)
1178 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1179 WineWindow* window = (WineWindow*)w;
1181 [window.queue discardEventsMatchingMask:-1 forWindow:window];
1188 /***********************************************************************
1189 * macdrv_get_window_hwnd
1191 * Get the hwnd that was set for the window at creation.
1193 void* macdrv_get_window_hwnd(macdrv_window w)
1195 WineWindow* window = (WineWindow*)w;
1199 /***********************************************************************
1200 * macdrv_set_cocoa_window_features
1202 * Update a Cocoa window's features.
1204 void macdrv_set_cocoa_window_features(macdrv_window w,
1205 const struct macdrv_window_features* wf)
1207 WineWindow* window = (WineWindow*)w;
1210 [window setWindowFeatures:wf];
1214 /***********************************************************************
1215 * macdrv_set_cocoa_window_state
1217 * Update a Cocoa window's state.
1219 void macdrv_set_cocoa_window_state(macdrv_window w,
1220 const struct macdrv_window_state* state)
1222 WineWindow* window = (WineWindow*)w;
1225 [window setMacDrvState:state];
1229 /***********************************************************************
1230 * macdrv_set_cocoa_window_title
1232 * Set a Cocoa window's title.
1234 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1237 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1238 WineWindow* window = (WineWindow*)w;
1239 NSString* titleString;
1242 titleString = [NSString stringWithCharacters:title length:length];
1245 OnMainThreadAsync(^{
1246 [window setTitle:titleString];
1247 if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1248 [NSApp changeWindowsItem:window title:titleString filename:NO];
1254 /***********************************************************************
1255 * macdrv_order_cocoa_window
1257 * Reorder a Cocoa window relative to other windows. If prev is
1258 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
1259 * it is ordered above that window. Otherwise, it is ordered to the
1262 * Returns true if the window has actually been ordered onto the screen
1263 * (i.e. if its frame intersects with a screen). Otherwise, false.
1265 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1268 WineWindow* window = (WineWindow*)w;
1269 __block BOOL on_screen;
1272 on_screen = [window orderBelow:(WineWindow*)prev
1273 orAbove:(WineWindow*)next];
1279 /***********************************************************************
1280 * macdrv_hide_cocoa_window
1282 * Hides a Cocoa window.
1284 void macdrv_hide_cocoa_window(macdrv_window w)
1286 WineWindow* window = (WineWindow*)w;
1289 [window doOrderOut];
1293 /***********************************************************************
1294 * macdrv_set_cocoa_window_frame
1296 * Move a Cocoa window. If the window has been moved out of the bounds
1297 * of the desktop, it is ordered out. (This routine won't ever order a
1298 * window in, though.)
1300 * Returns true if the window is on screen; false otherwise.
1302 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1304 WineWindow* window = (WineWindow*)w;
1305 __block BOOL on_screen;
1308 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1314 /***********************************************************************
1315 * macdrv_get_cocoa_window_frame
1317 * Gets the frame of a Cocoa window.
1319 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1321 WineWindow* window = (WineWindow*)w;
1326 frame = [window contentRectForFrameRect:[window frame]];
1327 [[window class] flipRect:&frame];
1328 *out_frame = NSRectToCGRect(frame);
1332 /***********************************************************************
1333 * macdrv_set_cocoa_parent_window
1335 * Sets the parent window for a Cocoa window. If parent is NULL, clears
1336 * the parent window.
1338 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1340 WineWindow* window = (WineWindow*)w;
1343 [window setMacDrvParentWindow:(WineWindow*)parent];
1347 /***********************************************************************
1348 * macdrv_set_window_surface
1350 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1352 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1353 WineWindow* window = (WineWindow*)w;
1356 window.surface = surface;
1357 window.surface_mutex = mutex;
1363 /***********************************************************************
1364 * macdrv_window_needs_display
1366 * Mark a window as needing display in a specified rect (in non-client
1367 * area coordinates).
1369 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1371 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1372 WineWindow* window = (WineWindow*)w;
1374 OnMainThreadAsync(^{
1375 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1381 /***********************************************************************
1382 * macdrv_set_window_shape
1384 * Sets the shape of a Cocoa window from an array of rectangles. If
1385 * rects is NULL, resets the window's shape to its frame.
1387 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1389 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1390 WineWindow* window = (WineWindow*)w;
1393 if (!rects || !count)
1400 path = [NSBezierPath bezierPath];
1401 for (i = 0; i < count; i++)
1402 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1403 window.shape = path;
1410 /***********************************************************************
1411 * macdrv_set_window_alpha
1413 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1415 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1416 WineWindow* window = (WineWindow*)w;
1418 [window setAlphaValue:alpha];
1423 /***********************************************************************
1424 * macdrv_set_window_color_key
1426 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1429 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1430 WineWindow* window = (WineWindow*)w;
1433 window.colorKeyed = TRUE;
1434 window.colorKeyRed = keyRed;
1435 window.colorKeyGreen = keyGreen;
1436 window.colorKeyBlue = keyBlue;
1437 [window checkTransparency];
1443 /***********************************************************************
1444 * macdrv_clear_window_color_key
1446 void macdrv_clear_window_color_key(macdrv_window w)
1448 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1449 WineWindow* window = (WineWindow*)w;
1452 window.colorKeyed = FALSE;
1453 [window checkTransparency];
1459 /***********************************************************************
1460 * macdrv_window_use_per_pixel_alpha
1462 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1464 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1465 WineWindow* window = (WineWindow*)w;
1468 window.usePerPixelAlpha = use_per_pixel_alpha;
1469 [window checkTransparency];
1475 /***********************************************************************
1476 * macdrv_give_cocoa_window_focus
1478 * Makes the Cocoa window "key" (gives it keyboard focus). This also
1479 * orders it front and, if its frame was not within the desktop bounds,
1480 * Cocoa will typically move it on-screen.
1482 void macdrv_give_cocoa_window_focus(macdrv_window w)
1484 WineWindow* window = (WineWindow*)w;
1487 [window makeFocused];