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"
28 #import "cocoa_opengl.h"
31 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
33 kVK_RightCommand = 0x36, /* Invented for Wine; was unused */
37 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
39 NSUInteger style_mask;
43 style_mask = NSTitledWindowMask;
44 if (wf->close_button) style_mask |= NSClosableWindowMask;
45 if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
46 if (wf->resizable) style_mask |= NSResizableWindowMask;
47 if (wf->utility) style_mask |= NSUtilityWindowMask;
49 else style_mask = NSBorderlessWindowMask;
55 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
58 for (screen in screens)
60 if (NSIntersectsRect(frame, [screen frame]))
67 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
69 for (NSScreen* screen in screens)
71 if (NSContainsRect(rect, [screen frame]))
78 /* We rely on the supposedly device-dependent modifier flags to distinguish the
79 keys on the left side of the keyboard from those on the right. Some event
80 sources don't set those device-depdendent flags. If we see a device-independent
81 flag for a modifier without either corresponding device-dependent flag, assume
83 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
85 if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
86 *modifiers |= NX_DEVICELCMDKEYMASK;
87 if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
88 *modifiers |= NX_DEVICELSHIFTKEYMASK;
89 if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
90 *modifiers |= NX_DEVICELCTLKEYMASK;
91 if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
92 *modifiers |= NX_DEVICELALTKEYMASK;
95 /* As we manipulate individual bits of a modifier mask, we can end up with
96 inconsistent sets of flags. In particular, we might set or clear one of the
97 left/right-specific bits, but not the corresponding non-side-specific bit.
98 Fix that. If either side-specific bit is set, set the non-side-specific bit,
99 otherwise clear it. */
100 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
102 if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
103 *modifiers |= NX_COMMANDMASK;
105 *modifiers &= ~NX_COMMANDMASK;
106 if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
107 *modifiers |= NX_SHIFTMASK;
109 *modifiers &= ~NX_SHIFTMASK;
110 if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
111 *modifiers |= NX_CONTROLMASK;
113 *modifiers &= ~NX_CONTROLMASK;
114 if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
115 *modifiers |= NX_ALTERNATEMASK;
117 *modifiers &= ~NX_ALTERNATEMASK;
121 @interface WineContentView : NSView
123 NSMutableArray* glContexts;
124 NSMutableArray* pendingGlContexts;
127 - (void) addGLContext:(WineOpenGLContext*)context;
128 - (void) removeGLContext:(WineOpenGLContext*)context;
129 - (void) updateGLContexts;
134 @interface WineWindow ()
136 @property (nonatomic) BOOL disabled;
137 @property (nonatomic) BOOL noActivate;
138 @property (readwrite, nonatomic) BOOL floating;
139 @property (retain, nonatomic) NSWindow* latentParentWindow;
141 @property (nonatomic) void* hwnd;
142 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
144 @property (nonatomic) void* surface;
145 @property (nonatomic) pthread_mutex_t* surface_mutex;
147 @property (copy, nonatomic) NSBezierPath* shape;
148 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
149 @property (readonly, nonatomic) BOOL needsTransparency;
151 @property (nonatomic) BOOL colorKeyed;
152 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
153 @property (nonatomic) BOOL usePerPixelAlpha;
155 @property (readwrite, nonatomic) NSInteger levelWhenActive;
160 @implementation WineContentView
164 [glContexts release];
165 [pendingGlContexts release];
174 - (void) drawRect:(NSRect)rect
176 WineWindow* window = (WineWindow*)[self window];
178 for (WineOpenGLContext* context in pendingGlContexts)
179 context.needsUpdate = TRUE;
180 [glContexts addObjectsFromArray:pendingGlContexts];
181 [pendingGlContexts removeAllObjects];
183 if ([window contentView] != self)
186 if (window.surface && window.surface_mutex &&
187 !pthread_mutex_lock(window.surface_mutex))
192 if (!get_surface_region_rects(window.surface, &rects, &count) || count)
197 imageRect = NSRectToCGRect(rect);
198 image = create_surface_image(window.surface, &imageRect, FALSE);
202 CGContextRef context;
206 NSBezierPath* surfaceClip = [NSBezierPath bezierPath];
208 for (i = 0; i < count; i++)
209 [surfaceClip appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
210 [surfaceClip addClip];
213 [window.shape addClip];
215 if (window.colorKeyed)
217 CGImageRef maskedImage;
218 CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
219 window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
220 window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
221 maskedImage = CGImageCreateWithMaskingColors(image, components);
224 CGImageRelease(image);
229 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
230 CGContextSetBlendMode(context, kCGBlendModeCopy);
231 CGContextDrawImage(context, imageRect, image);
233 CGImageRelease(image);
235 if (window.shapeChangedSinceLastDraw || window.colorKeyed ||
236 window.usePerPixelAlpha)
238 window.shapeChangedSinceLastDraw = FALSE;
239 [window invalidateShadow];
244 pthread_mutex_unlock(window.surface_mutex);
248 /* By default, NSView will swallow right-clicks in an attempt to support contextual
249 menus. We need to bypass that and allow the event to make it to the window. */
250 - (void) rightMouseDown:(NSEvent*)theEvent
252 [[self window] rightMouseDown:theEvent];
255 - (void) addGLContext:(WineOpenGLContext*)context
258 glContexts = [[NSMutableArray alloc] init];
259 if (!pendingGlContexts)
260 pendingGlContexts = [[NSMutableArray alloc] init];
261 [pendingGlContexts addObject:context];
262 [self setNeedsDisplay:YES];
265 - (void) removeGLContext:(WineOpenGLContext*)context
267 [glContexts removeObjectIdenticalTo:context];
268 [pendingGlContexts removeObjectIdenticalTo:context];
271 - (void) updateGLContexts
273 for (WineOpenGLContext* context in glContexts)
274 context.needsUpdate = TRUE;
277 - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
285 @implementation WineWindow
287 @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
288 @synthesize surface, surface_mutex;
289 @synthesize shape, shapeChangedSinceLastDraw;
290 @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
291 @synthesize usePerPixelAlpha;
292 @synthesize levelWhenActive;
294 + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
295 windowFrame:(NSRect)window_frame
297 queue:(WineEventQueue*)queue
300 WineContentView* contentView;
301 NSTrackingArea* trackingArea;
303 [NSApp flipRect:&window_frame];
305 window = [[[self alloc] initWithContentRect:window_frame
306 styleMask:style_mask_for_features(wf)
307 backing:NSBackingStoreBuffered
308 defer:YES] autorelease];
310 if (!window) return nil;
311 window->normalStyleMask = [window styleMask];
313 /* Standardize windows to eliminate differences between titled and
314 borderless windows and between NSWindow and NSPanel. */
315 [window setHidesOnDeactivate:NO];
316 [window setReleasedWhenClosed:NO];
318 [window disableCursorRects];
319 [window setShowsResizeIndicator:NO];
320 [window setHasShadow:wf->shadow];
321 [window setAcceptsMouseMovedEvents:YES];
322 [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
323 [window setDelegate:window];
325 window.queue = queue;
327 contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
330 [contentView setAutoresizesSubviews:NO];
332 /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
333 because they give us mouse moves in the background. */
334 trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
335 options:(NSTrackingMouseMoved |
336 NSTrackingActiveAlways |
337 NSTrackingInVisibleRect)
339 userInfo:nil] autorelease];
342 [contentView addTrackingArea:trackingArea];
344 [window setContentView:contentView];
352 [latentParentWindow release];
357 - (void) adjustFeaturesForState
359 NSUInteger style = normalStyleMask;
362 style &= ~NSResizableWindowMask;
363 if (style != [self styleMask])
364 [self setStyleMask:style];
366 if (style & NSClosableWindowMask)
367 [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
368 if (style & NSMiniaturizableWindowMask)
369 [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
372 - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
374 normalStyleMask = style_mask_for_features(wf);
375 [self adjustFeaturesForState];
376 [self setHasShadow:wf->shadow];
379 - (void) adjustWindowLevel
382 BOOL fullscreen, captured;
385 WineWindow* other = nil;
387 screen = screen_covered_by_rect([self frame], [NSScreen screens]);
388 fullscreen = (screen != nil);
389 captured = (screen || [self screen]) && [NSApp areDisplaysCaptured];
391 if (captured || fullscreen)
394 level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
396 level = NSMainMenuWindowLevel + 1;
401 else if (self.floating)
402 level = NSFloatingWindowLevel;
404 level = NSNormalWindowLevel;
406 index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:self];
407 if (index != NSNotFound && index + 1 < [[NSApp orderedWineWindows] count])
409 other = [[NSApp orderedWineWindows] objectAtIndex:index + 1];
410 if (level < [other level])
411 level = [other level];
414 if (level != [self level])
416 [self setLevelWhenActive:level];
418 /* Setting the window level above has moved this window to the front
419 of all other windows at the same level. We need to move it
420 back into its proper place among other windows of that level.
421 Also, any windows which are supposed to be in front of it had
422 better have the same or higher window level. If not, bump them
424 if (index != NSNotFound)
426 for (; index > 0; index--)
428 other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
429 if ([other level] < level)
430 [other setLevelWhenActive:level];
433 [self orderWindow:NSWindowBelow relativeTo:[other windowNumber]];
441 - (void) setMacDrvState:(const struct macdrv_window_state*)state
443 NSWindowCollectionBehavior behavior;
445 self.disabled = state->disabled;
446 self.noActivate = state->no_activate;
448 self.floating = state->floating;
449 [self adjustWindowLevel];
451 behavior = NSWindowCollectionBehaviorDefault;
452 if (state->excluded_by_expose)
453 behavior |= NSWindowCollectionBehaviorTransient;
455 behavior |= NSWindowCollectionBehaviorManaged;
456 if (state->excluded_by_cycle)
458 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
459 if ([self isVisible])
460 [NSApp removeWindowsItem:self];
464 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
465 if ([self isVisible])
466 [NSApp addWindowsItem:self title:[self title] filename:NO];
468 [self setCollectionBehavior:behavior];
470 if (state->minimized && ![self isMiniaturized])
472 ignore_windowMiniaturize = TRUE;
473 [self miniaturize:nil];
475 else if (!state->minimized && [self isMiniaturized])
477 ignore_windowDeminiaturize = TRUE;
478 [self deminiaturize:nil];
481 /* Whatever events regarding minimization might have been in the queue are now stale. */
482 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
483 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
487 /* Returns whether or not the window was ordered in, which depends on if
488 its frame intersects any screen. */
489 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
491 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
494 [NSApp transformProcessToForeground];
498 /* Make sure that windows that should be above this one really are.
499 This is necessary since a full-screen window gets a boost to its
500 window level to be in front of the menu bar and Dock and that moves
501 it out of the z-order that Win32 would otherwise establish. */
502 if ([prev level] < [self level])
504 NSUInteger index = [[NSApp orderedWineWindows] indexOfObjectIdenticalTo:prev];
505 if (index != NSNotFound)
507 [prev setLevelWhenActive:[self level]];
508 for (; index > 0; index--)
510 WineWindow* other = [[NSApp orderedWineWindows] objectAtIndex:index - 1];
511 if ([other level] < [self level])
512 [other setLevelWhenActive:[self level]];
516 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
517 [NSApp wineWindow:self ordered:NSWindowBelow relativeTo:prev];
521 /* Similarly, make sure this window is really above what it should be. */
522 if (next && [next level] > [self level])
523 [self setLevelWhenActive:[next level]];
524 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
525 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:next];
527 if (latentParentWindow)
529 if ([latentParentWindow level] > [self level])
530 [self setLevelWhenActive:[latentParentWindow level]];
531 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
532 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
533 self.latentParentWindow = nil;
536 /* Cocoa may adjust the frame when the window is ordered onto the screen.
537 Generate a frame-changed event just in case. The back end will ignore
538 it if nothing actually changed. */
539 [self windowDidResize:nil];
541 if (![self isExcludedFromWindowsMenu])
542 [NSApp addWindowsItem:self title:[self title] filename:NO];
550 self.latentParentWindow = [self parentWindow];
551 [latentParentWindow removeChildWindow:self];
553 [NSApp wineWindow:self ordered:NSWindowOut relativeTo:nil];
554 [NSApp removeWindowsItem:self];
557 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
559 NSArray* screens = [NSScreen screens];
560 BOOL on_screen = [self isVisible];
561 NSRect frame, oldFrame;
563 if (![screens count]) return on_screen;
565 /* Origin is (left, top) in a top-down space. Need to convert it to
566 (left, bottom) in a bottom-up space. */
567 [NSApp flipRect:&contentRect];
571 on_screen = frame_intersects_screens(contentRect, screens);
576 if (!NSIsEmptyRect(contentRect))
578 oldFrame = [self frame];
579 frame = [self frameRectForContentRect:contentRect];
580 if (!NSEqualRects(frame, oldFrame))
582 if (NSEqualSizes(frame.size, oldFrame.size))
583 [self setFrameOrigin:frame.origin];
585 [self setFrame:frame display:YES];
591 [self adjustWindowLevel];
593 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
594 event. The back end will ignore it if nothing actually changed. */
595 [self windowDidResize:nil];
599 /* The back end is establishing a new window size and position. It's
600 not interested in any stale events regarding those that may be sitting
602 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
609 - (void) setMacDrvParentWindow:(WineWindow*)parent
611 if ([self parentWindow] != parent)
613 [[self parentWindow] removeChildWindow:self];
614 self.latentParentWindow = nil;
615 if ([self isVisible] && parent)
617 if ([parent level] > [self level])
618 [self setLevelWhenActive:[parent level]];
619 [parent addChildWindow:self ordered:NSWindowAbove];
620 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:parent];
623 self.latentParentWindow = parent;
627 - (void) setDisabled:(BOOL)newValue
629 if (disabled != newValue)
632 [self adjustFeaturesForState];
636 - (BOOL) needsTransparency
638 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
641 - (void) checkTransparency
643 if (![self isOpaque] && !self.needsTransparency)
645 [self setBackgroundColor:[NSColor windowBackgroundColor]];
646 [self setOpaque:YES];
648 else if ([self isOpaque] && self.needsTransparency)
650 [self setBackgroundColor:[NSColor clearColor]];
655 - (void) setShape:(NSBezierPath*)newShape
657 if (shape == newShape) return;
658 if (shape && newShape && [shape isEqual:newShape]) return;
662 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
666 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
668 shape = [newShape copy];
669 self.shapeChangedSinceLastDraw = TRUE;
671 [self checkTransparency];
674 - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
676 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
679 event.type = MOUSE_BUTTON;
680 event.window = (macdrv_window)[self retain];
681 event.mouse_button.button = [theEvent buttonNumber];
682 event.mouse_button.pressed = pressed;
683 event.mouse_button.x = pt.x;
684 event.mouse_button.y = pt.y;
685 event.mouse_button.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
687 [queue postEvent:&event];
694 [NSApp transformProcessToForeground];
696 /* If a borderless window is offscreen, orderFront: won't move
697 it onscreen like it would for a titled window. Do that ourselves. */
698 screens = [NSScreen screens];
699 if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
700 !frame_intersects_screens([self frame], screens))
702 NSScreen* primaryScreen = [screens objectAtIndex:0];
703 NSRect frame = [primaryScreen frame];
704 [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
705 frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
706 [self setFrame:frame display:YES];
709 if ([[NSApp orderedWineWindows] count])
713 front = [[NSApp orderedWineWindows] objectAtIndex:0];
716 for (front in [NSApp orderedWineWindows])
717 if (!front.floating) break;
719 if (front && [front levelWhenActive] > [self levelWhenActive])
720 [self setLevelWhenActive:[front levelWhenActive]];
722 [self orderFront:nil];
723 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
724 causing_becomeKeyWindow = TRUE;
725 [self makeKeyWindow];
726 causing_becomeKeyWindow = FALSE;
727 if (latentParentWindow)
729 if ([latentParentWindow level] > [self level])
730 [self setLevelWhenActive:[latentParentWindow level]];
731 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
732 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
733 self.latentParentWindow = nil;
735 if (![self isExcludedFromWindowsMenu])
736 [NSApp addWindowsItem:self title:[self title] filename:NO];
738 /* Cocoa may adjust the frame when the window is ordered onto the screen.
739 Generate a frame-changed event just in case. The back end will ignore
740 it if nothing actually changed. */
741 [self windowDidResize:nil];
744 - (void) postKey:(uint16_t)keyCode
745 pressed:(BOOL)pressed
746 modifiers:(NSUInteger)modifiers
747 event:(NSEvent*)theEvent
751 WineApplication* app = (WineApplication*)NSApp;
753 event.type = pressed ? KEY_PRESS : KEY_RELEASE;
754 event.window = (macdrv_window)[self retain];
755 event.key.keycode = keyCode;
756 event.key.modifiers = modifiers;
757 event.key.time_ms = [app ticksForEventTime:[theEvent timestamp]];
759 if ((cgevent = [theEvent CGEvent]))
761 CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
762 kCGKeyboardEventKeyboardType);
763 if (keyboardType != app.keyboardType)
765 app.keyboardType = keyboardType;
766 [app keyboardSelectionDidChange];
770 [queue postEvent:&event];
773 - (void) postKeyEvent:(NSEvent *)theEvent
775 [self flagsChanged:theEvent];
776 [self postKey:[theEvent keyCode]
777 pressed:[theEvent type] == NSKeyDown
778 modifiers:[theEvent modifierFlags]
782 - (void) postMouseMovedEvent:(NSEvent *)theEvent absolute:(BOOL)absolute
788 CGPoint point = CGEventGetLocation([theEvent CGEvent]);
790 event.type = MOUSE_MOVED_ABSOLUTE;
791 event.mouse_moved.x = point.x;
792 event.mouse_moved.y = point.y;
799 /* Add event delta to accumulated delta error */
800 /* deltaY is already flipped */
801 mouseMoveDeltaX += [theEvent deltaX];
802 mouseMoveDeltaY += [theEvent deltaY];
804 event.type = MOUSE_MOVED;
805 event.mouse_moved.x = mouseMoveDeltaX;
806 event.mouse_moved.y = mouseMoveDeltaY;
808 /* Keep the remainder after integer truncation. */
809 mouseMoveDeltaX -= event.mouse_moved.x;
810 mouseMoveDeltaY -= event.mouse_moved.y;
813 if (event.type == MOUSE_MOVED_ABSOLUTE || event.mouse_moved.x || event.mouse_moved.y)
815 event.window = (macdrv_window)[self retain];
816 event.mouse_moved.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
818 [queue postEvent:&event];
822 - (void) setLevelWhenActive:(NSInteger)level
824 levelWhenActive = level;
825 if (([NSApp isActive] || level <= NSFloatingWindowLevel) &&
826 level != [self level])
827 [self setLevel:level];
832 * ---------- NSWindow method overrides ----------
834 - (BOOL) canBecomeKeyWindow
836 if (causing_becomeKeyWindow) return YES;
837 if (self.disabled || self.noActivate) return NO;
838 return [self isKeyWindow];
841 - (BOOL) canBecomeMainWindow
843 return [self canBecomeKeyWindow];
846 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
848 // If a window is sized to completely cover a screen, then it's in
849 // full-screen mode. In that case, we don't allow NSWindow to constrain
851 NSRect contentRect = [self contentRectForFrameRect:frameRect];
852 if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
853 frameRect = [super constrainFrameRect:frameRect toScreen:screen];
857 - (BOOL) isExcludedFromWindowsMenu
859 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
862 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
864 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
865 return [self isKeyWindow] || (!self.disabled && !self.noActivate);
866 return [super validateMenuItem:menuItem];
869 /* We don't call this. It's the action method of the items in the Window menu. */
870 - (void) makeKeyAndOrderFront:(id)sender
872 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
873 [NSApp windowGotFocus:self];
876 - (void) sendEvent:(NSEvent*)event
878 /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
879 interface control. For example, Control-Tab switches focus among
880 views. We want to bypass that feature, so directly route key-down
881 events to -keyDown:. */
882 if ([event type] == NSKeyDown)
883 [[self firstResponder] keyDown:event];
886 if ([event type] == NSLeftMouseDown)
888 NSWindowButton windowButton;
889 BOOL broughtWindowForward = TRUE;
891 /* Since our windows generally claim they can't be made key, clicks
892 in their title bars are swallowed by the theme frame stuff. So,
893 we hook directly into the event stream and assume that any click
894 in the window will activate it, if Wine and the Win32 program
896 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
897 [NSApp windowGotFocus:self];
899 /* Any left-click on our window anyplace other than the close or
900 minimize buttons will bring it forward. */
901 for (windowButton = NSWindowCloseButton;
902 windowButton <= NSWindowMiniaturizeButton;
905 NSButton* button = [[event window] standardWindowButton:windowButton];
908 NSPoint point = [button convertPoint:[event locationInWindow] fromView:nil];
909 if ([button mouse:point inRect:[button bounds]])
911 broughtWindowForward = FALSE;
917 if (broughtWindowForward)
918 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
921 [super sendEvent:event];
927 * ---------- NSResponder method overrides ----------
929 - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
930 - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
931 - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
933 - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
934 - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
935 - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
937 - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
938 - (void) keyUp:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
940 - (void) flagsChanged:(NSEvent *)theEvent
942 static const struct {
946 { NX_ALPHASHIFTMASK, kVK_CapsLock },
947 { NX_DEVICELSHIFTKEYMASK, kVK_Shift },
948 { NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
949 { NX_DEVICELCTLKEYMASK, kVK_Control },
950 { NX_DEVICERCTLKEYMASK, kVK_RightControl },
951 { NX_DEVICELALTKEYMASK, kVK_Option },
952 { NX_DEVICERALTKEYMASK, kVK_RightOption },
953 { NX_DEVICELCMDKEYMASK, kVK_Command },
954 { NX_DEVICERCMDKEYMASK, kVK_RightCommand },
957 NSUInteger modifierFlags = [theEvent modifierFlags];
961 fix_device_modifiers_by_generic(&modifierFlags);
962 changed = modifierFlags ^ lastModifierFlags;
965 for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
966 if (changed & modifiers[i].mask)
969 for (i = 0; i <= last_changed; i++)
971 if (changed & modifiers[i].mask)
973 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
975 if (i == last_changed)
976 lastModifierFlags = modifierFlags;
979 lastModifierFlags ^= modifiers[i].mask;
980 fix_generic_modifiers_by_device(&lastModifierFlags);
983 // Caps lock generates one event for each press-release action.
984 // We need to simulate a pair of events for each actual event.
985 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
987 [self postKey:modifiers[i].keycode
989 modifiers:lastModifierFlags
990 event:(NSEvent*)theEvent];
994 [self postKey:modifiers[i].keycode
996 modifiers:lastModifierFlags
997 event:(NSEvent*)theEvent];
1002 - (void) scrollWheel:(NSEvent *)theEvent
1008 BOOL continuous = FALSE;
1010 cgevent = [theEvent CGEvent];
1011 pt = CGEventGetLocation(cgevent);
1013 event.type = MOUSE_SCROLL;
1014 event.window = (macdrv_window)[self retain];
1015 event.mouse_scroll.x = pt.x;
1016 event.mouse_scroll.y = pt.y;
1017 event.mouse_scroll.time_ms = [NSApp ticksForEventTime:[theEvent timestamp]];
1019 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1023 /* Continuous scroll wheel events come from high-precision scrolling
1024 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1025 For these, we can get more precise data from the CGEvent API. */
1026 /* Axis 1 is vertical, axis 2 is horizontal. */
1027 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1028 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1032 double pixelsPerLine = 10;
1033 CGEventSourceRef source;
1035 /* The non-continuous values are in units of "lines", not pixels. */
1036 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1038 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1042 x = pixelsPerLine * [theEvent deltaX];
1043 y = pixelsPerLine * [theEvent deltaY];
1046 /* Mac: negative is right or down, positive is left or up.
1047 Win32: negative is left or down, positive is right or up.
1048 So, negate the X scroll value to translate. */
1051 /* The x,y values so far are in pixels. Win32 expects to receive some
1052 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1053 6 times the pixel value. */
1054 event.mouse_scroll.x_scroll = 6 * x;
1055 event.mouse_scroll.y_scroll = 6 * y;
1059 /* For non-continuous "clicky" wheels, if there was any motion, make
1060 sure there was at least WHEEL_DELTA motion. This is so, at slow
1061 speeds where the system's acceleration curve is actually reducing the
1062 scroll distance, the user is sure to get some action out of each click.
1063 For example, this is important for rotating though weapons in a
1064 first-person shooter. */
1065 if (0 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 120)
1066 event.mouse_scroll.x_scroll = 120;
1067 else if (-120 < event.mouse_scroll.x_scroll && event.mouse_scroll.x_scroll < 0)
1068 event.mouse_scroll.x_scroll = -120;
1070 if (0 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 120)
1071 event.mouse_scroll.y_scroll = 120;
1072 else if (-120 < event.mouse_scroll.y_scroll && event.mouse_scroll.y_scroll < 0)
1073 event.mouse_scroll.y_scroll = -120;
1076 if (event.mouse_scroll.x_scroll || event.mouse_scroll.y_scroll)
1077 [queue postEvent:&event];
1082 * ---------- NSWindowDelegate methods ----------
1084 - (void)windowDidBecomeKey:(NSNotification *)notification
1086 NSEvent* event = [NSApp lastFlagsChanged];
1088 [self flagsChanged:event];
1090 if (causing_becomeKeyWindow) return;
1092 [NSApp windowGotFocus:self];
1095 - (void)windowDidDeminiaturize:(NSNotification *)notification
1097 if (!ignore_windowDeminiaturize)
1101 /* Coalesce events by discarding any previous ones still in the queue. */
1102 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1103 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1106 event.type = WINDOW_DID_UNMINIMIZE;
1107 event.window = (macdrv_window)[self retain];
1108 [queue postEvent:&event];
1111 ignore_windowDeminiaturize = FALSE;
1113 [NSApp wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1116 - (void)windowDidMove:(NSNotification *)notification
1118 [self windowDidResize:notification];
1121 - (void)windowDidResignKey:(NSNotification *)notification
1125 if (causing_becomeKeyWindow) return;
1127 event.type = WINDOW_LOST_FOCUS;
1128 event.window = (macdrv_window)[self retain];
1129 [queue postEvent:&event];
1132 - (void)windowDidResize:(NSNotification *)notification
1135 NSRect frame = [self contentRectForFrameRect:[self frame]];
1137 [NSApp flipRect:&frame];
1139 /* Coalesce events by discarding any previous ones still in the queue. */
1140 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1143 event.type = WINDOW_FRAME_CHANGED;
1144 event.window = (macdrv_window)[self retain];
1145 event.window_frame_changed.frame = NSRectToCGRect(frame);
1146 [queue postEvent:&event];
1149 - (BOOL)windowShouldClose:(id)sender
1152 event.type = WINDOW_CLOSE_REQUESTED;
1153 event.window = (macdrv_window)[self retain];
1154 [queue postEvent:&event];
1158 - (void)windowWillMiniaturize:(NSNotification *)notification
1160 if (!ignore_windowMiniaturize)
1164 /* Coalesce events by discarding any previous ones still in the queue. */
1165 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1166 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1169 event.type = WINDOW_DID_MINIMIZE;
1170 event.window = (macdrv_window)[self retain];
1171 [queue postEvent:&event];
1174 ignore_windowMiniaturize = FALSE;
1180 /***********************************************************************
1181 * macdrv_create_cocoa_window
1183 * Create a Cocoa window with the given content frame and features (e.g.
1184 * title bar, close box, etc.).
1186 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1187 CGRect frame, void* hwnd, macdrv_event_queue queue)
1189 __block WineWindow* window;
1192 window = [[WineWindow createWindowWithFeatures:wf
1193 windowFrame:NSRectFromCGRect(frame)
1195 queue:(WineEventQueue*)queue] retain];
1198 return (macdrv_window)window;
1201 /***********************************************************************
1202 * macdrv_destroy_cocoa_window
1204 * Destroy a Cocoa window.
1206 void macdrv_destroy_cocoa_window(macdrv_window w)
1208 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1209 WineWindow* window = (WineWindow*)w;
1211 [window.queue discardEventsMatchingMask:-1 forWindow:window];
1218 /***********************************************************************
1219 * macdrv_get_window_hwnd
1221 * Get the hwnd that was set for the window at creation.
1223 void* macdrv_get_window_hwnd(macdrv_window w)
1225 WineWindow* window = (WineWindow*)w;
1229 /***********************************************************************
1230 * macdrv_set_cocoa_window_features
1232 * Update a Cocoa window's features.
1234 void macdrv_set_cocoa_window_features(macdrv_window w,
1235 const struct macdrv_window_features* wf)
1237 WineWindow* window = (WineWindow*)w;
1240 [window setWindowFeatures:wf];
1244 /***********************************************************************
1245 * macdrv_set_cocoa_window_state
1247 * Update a Cocoa window's state.
1249 void macdrv_set_cocoa_window_state(macdrv_window w,
1250 const struct macdrv_window_state* state)
1252 WineWindow* window = (WineWindow*)w;
1255 [window setMacDrvState:state];
1259 /***********************************************************************
1260 * macdrv_set_cocoa_window_title
1262 * Set a Cocoa window's title.
1264 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1267 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1268 WineWindow* window = (WineWindow*)w;
1269 NSString* titleString;
1272 titleString = [NSString stringWithCharacters:title length:length];
1275 OnMainThreadAsync(^{
1276 [window setTitle:titleString];
1277 if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1278 [NSApp changeWindowsItem:window title:titleString filename:NO];
1284 /***********************************************************************
1285 * macdrv_order_cocoa_window
1287 * Reorder a Cocoa window relative to other windows. If prev is
1288 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
1289 * it is ordered above that window. Otherwise, it is ordered to the
1292 * Returns true if the window has actually been ordered onto the screen
1293 * (i.e. if its frame intersects with a screen). Otherwise, false.
1295 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1298 WineWindow* window = (WineWindow*)w;
1299 __block BOOL on_screen;
1302 on_screen = [window orderBelow:(WineWindow*)prev
1303 orAbove:(WineWindow*)next];
1309 /***********************************************************************
1310 * macdrv_hide_cocoa_window
1312 * Hides a Cocoa window.
1314 void macdrv_hide_cocoa_window(macdrv_window w)
1316 WineWindow* window = (WineWindow*)w;
1319 [window doOrderOut];
1323 /***********************************************************************
1324 * macdrv_set_cocoa_window_frame
1326 * Move a Cocoa window. If the window has been moved out of the bounds
1327 * of the desktop, it is ordered out. (This routine won't ever order a
1328 * window in, though.)
1330 * Returns true if the window is on screen; false otherwise.
1332 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1334 WineWindow* window = (WineWindow*)w;
1335 __block BOOL on_screen;
1338 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1344 /***********************************************************************
1345 * macdrv_get_cocoa_window_frame
1347 * Gets the frame of a Cocoa window.
1349 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1351 WineWindow* window = (WineWindow*)w;
1356 frame = [window contentRectForFrameRect:[window frame]];
1357 [NSApp flipRect:&frame];
1358 *out_frame = NSRectToCGRect(frame);
1362 /***********************************************************************
1363 * macdrv_set_cocoa_parent_window
1365 * Sets the parent window for a Cocoa window. If parent is NULL, clears
1366 * the parent window.
1368 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1370 WineWindow* window = (WineWindow*)w;
1373 [window setMacDrvParentWindow:(WineWindow*)parent];
1377 /***********************************************************************
1378 * macdrv_set_window_surface
1380 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1382 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1383 WineWindow* window = (WineWindow*)w;
1386 window.surface = surface;
1387 window.surface_mutex = mutex;
1393 /***********************************************************************
1394 * macdrv_window_needs_display
1396 * Mark a window as needing display in a specified rect (in non-client
1397 * area coordinates).
1399 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1401 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1402 WineWindow* window = (WineWindow*)w;
1404 OnMainThreadAsync(^{
1405 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1411 /***********************************************************************
1412 * macdrv_set_window_shape
1414 * Sets the shape of a Cocoa window from an array of rectangles. If
1415 * rects is NULL, resets the window's shape to its frame.
1417 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1419 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1420 WineWindow* window = (WineWindow*)w;
1423 if (!rects || !count)
1430 path = [NSBezierPath bezierPath];
1431 for (i = 0; i < count; i++)
1432 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1433 window.shape = path;
1440 /***********************************************************************
1441 * macdrv_set_window_alpha
1443 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1445 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1446 WineWindow* window = (WineWindow*)w;
1448 [window setAlphaValue:alpha];
1453 /***********************************************************************
1454 * macdrv_set_window_color_key
1456 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1459 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1460 WineWindow* window = (WineWindow*)w;
1463 window.colorKeyed = TRUE;
1464 window.colorKeyRed = keyRed;
1465 window.colorKeyGreen = keyGreen;
1466 window.colorKeyBlue = keyBlue;
1467 [window checkTransparency];
1473 /***********************************************************************
1474 * macdrv_clear_window_color_key
1476 void macdrv_clear_window_color_key(macdrv_window w)
1478 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1479 WineWindow* window = (WineWindow*)w;
1482 window.colorKeyed = FALSE;
1483 [window checkTransparency];
1489 /***********************************************************************
1490 * macdrv_window_use_per_pixel_alpha
1492 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1494 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1495 WineWindow* window = (WineWindow*)w;
1498 window.usePerPixelAlpha = use_per_pixel_alpha;
1499 [window checkTransparency];
1505 /***********************************************************************
1506 * macdrv_give_cocoa_window_focus
1508 * Makes the Cocoa window "key" (gives it keyboard focus). This also
1509 * orders it front and, if its frame was not within the desktop bounds,
1510 * Cocoa will typically move it on-screen.
1512 void macdrv_give_cocoa_window_focus(macdrv_window w)
1514 WineWindow* window = (WineWindow*)w;
1517 [window makeFocused];
1521 /***********************************************************************
1522 * macdrv_create_view
1524 * Creates and returns a view in the specified rect of the window. The
1525 * caller is responsible for calling macdrv_dispose_view() on the view
1526 * when it is done with it.
1528 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
1530 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1531 WineWindow* window = (WineWindow*)w;
1532 __block WineContentView* view;
1534 if (CGRectIsNull(rect)) rect = CGRectZero;
1537 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1539 view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
1540 [view setAutoresizesSubviews:NO];
1541 [nc addObserver:view
1542 selector:@selector(updateGLContexts)
1543 name:NSViewGlobalFrameDidChangeNotification
1545 [nc addObserver:view
1546 selector:@selector(updateGLContexts)
1547 name:NSApplicationDidChangeScreenParametersNotification
1549 [[window contentView] addSubview:view];
1553 return (macdrv_view)view;
1556 /***********************************************************************
1557 * macdrv_dispose_view
1559 * Destroys a view previously returned by macdrv_create_view.
1561 void macdrv_dispose_view(macdrv_view v)
1563 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1564 WineContentView* view = (WineContentView*)v;
1567 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1569 [nc removeObserver:view
1570 name:NSViewGlobalFrameDidChangeNotification
1572 [nc removeObserver:view
1573 name:NSApplicationDidChangeScreenParametersNotification
1575 [view removeFromSuperview];
1582 /***********************************************************************
1583 * macdrv_set_view_window_and_frame
1585 * Move a view to a new window and/or position within its window. If w
1586 * is NULL, leave the view in its current window and just change its
1589 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
1591 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1592 WineContentView* view = (WineContentView*)v;
1593 WineWindow* window = (WineWindow*)w;
1595 if (CGRectIsNull(rect)) rect = CGRectZero;
1598 BOOL changedWindow = (window && window != [view window]);
1599 NSRect newFrame = NSRectFromCGRect(rect);
1600 NSRect oldFrame = [view frame];
1604 [view removeFromSuperview];
1605 [[window contentView] addSubview:view];
1608 if (!NSEqualRects(oldFrame, newFrame))
1611 [[view superview] setNeedsDisplayInRect:oldFrame];
1612 if (NSEqualPoints(oldFrame.origin, newFrame.origin))
1613 [view setFrameSize:newFrame.size];
1614 else if (NSEqualSizes(oldFrame.size, newFrame.size))
1615 [view setFrameOrigin:newFrame.origin];
1617 [view setFrame:newFrame];
1618 [view setNeedsDisplay:YES];
1625 /***********************************************************************
1626 * macdrv_add_view_opengl_context
1628 * Add an OpenGL context to the list being tracked for each view.
1630 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1632 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1633 WineContentView* view = (WineContentView*)v;
1634 WineOpenGLContext *context = (WineOpenGLContext*)c;
1636 OnMainThreadAsync(^{
1637 [view addGLContext:context];
1643 /***********************************************************************
1644 * macdrv_remove_view_opengl_context
1646 * Add an OpenGL context to the list being tracked for each view.
1648 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1650 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1651 WineContentView* view = (WineContentView*)v;
1652 WineOpenGLContext *context = (WineOpenGLContext*)c;
1654 OnMainThreadAsync(^{
1655 [view removeGLContext:context];