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 <NSTextInputClient>
123 NSMutableArray* glContexts;
124 NSMutableArray* pendingGlContexts;
126 NSMutableAttributedString* markedText;
127 NSRange markedTextSelection;
130 - (void) addGLContext:(WineOpenGLContext*)context;
131 - (void) removeGLContext:(WineOpenGLContext*)context;
132 - (void) updateGLContexts;
137 @interface WineWindow ()
139 @property (nonatomic) BOOL disabled;
140 @property (nonatomic) BOOL noActivate;
141 @property (readwrite, nonatomic) BOOL floating;
142 @property (retain, nonatomic) NSWindow* latentParentWindow;
144 @property (nonatomic) void* hwnd;
145 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
147 @property (nonatomic) void* surface;
148 @property (nonatomic) pthread_mutex_t* surface_mutex;
150 @property (copy, nonatomic) NSBezierPath* shape;
151 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
152 @property (readonly, nonatomic) BOOL needsTransparency;
154 @property (nonatomic) BOOL colorKeyed;
155 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
156 @property (nonatomic) BOOL usePerPixelAlpha;
158 @property (readwrite, nonatomic) NSInteger levelWhenActive;
160 @property (assign, nonatomic) void* imeData;
161 @property (nonatomic) BOOL commandDone;
166 @implementation WineContentView
170 [markedText release];
171 [glContexts release];
172 [pendingGlContexts release];
181 - (void) drawRect:(NSRect)rect
183 WineWindow* window = (WineWindow*)[self window];
185 for (WineOpenGLContext* context in pendingGlContexts)
186 context.needsUpdate = TRUE;
187 [glContexts addObjectsFromArray:pendingGlContexts];
188 [pendingGlContexts removeAllObjects];
190 if ([window contentView] != self)
193 if (window.surface && window.surface_mutex &&
194 !pthread_mutex_lock(window.surface_mutex))
199 if (get_surface_blit_rects(window.surface, &rects, &count) && count)
201 CGContextRef context;
204 [window.shape addClip];
206 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
207 CGContextSetBlendMode(context, kCGBlendModeCopy);
209 for (i = 0; i < count; i++)
214 imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
215 image = create_surface_image(window.surface, &imageRect, FALSE);
219 if (window.colorKeyed)
221 CGImageRef maskedImage;
222 CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
223 window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
224 window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
225 maskedImage = CGImageCreateWithMaskingColors(image, components);
228 CGImageRelease(image);
233 CGContextDrawImage(context, imageRect, image);
235 CGImageRelease(image);
240 pthread_mutex_unlock(window.surface_mutex);
243 // If the window may be transparent, then we have to invalidate the
244 // shadow every time we draw. Also, if this is the first time we've
245 // drawn since changing from transparent to opaque.
246 if (![window isOpaque] || window.shapeChangedSinceLastDraw)
248 window.shapeChangedSinceLastDraw = FALSE;
249 [window invalidateShadow];
253 /* By default, NSView will swallow right-clicks in an attempt to support contextual
254 menus. We need to bypass that and allow the event to make it to the window. */
255 - (void) rightMouseDown:(NSEvent*)theEvent
257 [[self window] rightMouseDown:theEvent];
260 - (void) addGLContext:(WineOpenGLContext*)context
263 glContexts = [[NSMutableArray alloc] init];
264 if (!pendingGlContexts)
265 pendingGlContexts = [[NSMutableArray alloc] init];
266 [pendingGlContexts addObject:context];
267 [self setNeedsDisplay:YES];
270 - (void) removeGLContext:(WineOpenGLContext*)context
272 [glContexts removeObjectIdenticalTo:context];
273 [pendingGlContexts removeObjectIdenticalTo:context];
276 - (void) updateGLContexts
278 for (WineOpenGLContext* context in glContexts)
279 context.needsUpdate = TRUE;
282 - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
287 - (BOOL) preservesContentDuringLiveResize
289 // Returning YES from this tells Cocoa to keep our view's content during
290 // a Cocoa-driven resize. In theory, we're also supposed to override
291 // -setFrameSize: to mark exposed sections as needing redisplay, but
292 // user32 will take care of that in a roundabout way. This way, we don't
293 // redraw until the window surface is flushed.
295 // This doesn't do anything when we resize the window ourselves.
299 - (BOOL)acceptsFirstResponder
301 return [[self window] contentView] == self;
304 - (void) completeText:(NSString*)text
307 WineWindow* window = (WineWindow*)[self window];
309 event = macdrv_create_event(IM_SET_TEXT, window);
310 event->im_set_text.data = [window imeData];
311 event->im_set_text.text = (CFStringRef)[text copy];
312 event->im_set_text.complete = TRUE;
314 [[window queue] postEvent:event];
316 macdrv_release_event(event);
318 [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
319 markedTextSelection = NSMakeRange(0, 0);
320 [[self inputContext] discardMarkedText];
324 * ---------- NSTextInputClient methods ----------
326 - (NSTextInputContext*) inputContext
329 markedText = [[NSMutableAttributedString alloc] init];
330 return [super inputContext];
333 - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
335 if ([string isKindOfClass:[NSAttributedString class]])
336 string = [string string];
338 if ([string isKindOfClass:[NSString class]])
339 [self completeText:string];
342 - (void) doCommandBySelector:(SEL)aSelector
344 [(WineWindow*)[self window] setCommandDone:TRUE];
347 - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
349 if ([string isKindOfClass:[NSAttributedString class]])
350 string = [string string];
352 if ([string isKindOfClass:[NSString class]])
355 WineWindow* window = (WineWindow*)[self window];
357 if (replacementRange.location == NSNotFound)
358 replacementRange = NSMakeRange(0, [markedText length]);
360 [markedText replaceCharactersInRange:replacementRange withString:string];
361 markedTextSelection = selectedRange;
362 markedTextSelection.location += replacementRange.location;
364 event = macdrv_create_event(IM_SET_TEXT, window);
365 event->im_set_text.data = [window imeData];
366 event->im_set_text.text = (CFStringRef)[[markedText string] copy];
367 event->im_set_text.complete = FALSE;
369 [[window queue] postEvent:event];
371 macdrv_release_event(event);
373 event = macdrv_create_event(IM_SET_CURSOR_POS, window);
374 event->im_set_cursor_pos.data = [window imeData];
375 event->im_set_cursor_pos.pos = markedTextSelection.location;
377 [[window queue] postEvent:event];
379 macdrv_release_event(event);
385 [self completeText:nil];
388 - (NSRange) selectedRange
390 return markedTextSelection;
393 - (NSRange) markedRange
395 NSRange range = NSMakeRange(0, [markedText length]);
397 range.location = NSNotFound;
401 - (BOOL) hasMarkedText
403 return [markedText length] > 0;
406 - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
408 if (aRange.location >= [markedText length])
411 aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
413 *actualRange = aRange;
414 return [markedText attributedSubstringFromRange:aRange];
417 - (NSArray*) validAttributesForMarkedText
419 return [NSArray array];
422 - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
424 aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
426 *actualRange = aRange;
427 return NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
430 - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
435 - (NSInteger) windowLevel
437 return [[self window] level];
443 @implementation WineWindow
445 @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
446 @synthesize surface, surface_mutex;
447 @synthesize shape, shapeChangedSinceLastDraw;
448 @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
449 @synthesize usePerPixelAlpha;
450 @synthesize levelWhenActive;
451 @synthesize imeData, commandDone;
453 + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
454 windowFrame:(NSRect)window_frame
456 queue:(WineEventQueue*)queue
459 WineContentView* contentView;
460 NSTrackingArea* trackingArea;
462 [[WineApplicationController sharedController] flipRect:&window_frame];
464 window = [[[self alloc] initWithContentRect:window_frame
465 styleMask:style_mask_for_features(wf)
466 backing:NSBackingStoreBuffered
467 defer:YES] autorelease];
469 if (!window) return nil;
470 window->normalStyleMask = [window styleMask];
472 /* Standardize windows to eliminate differences between titled and
473 borderless windows and between NSWindow and NSPanel. */
474 [window setHidesOnDeactivate:NO];
475 [window setReleasedWhenClosed:NO];
477 [window disableCursorRects];
478 [window setShowsResizeIndicator:NO];
479 [window setHasShadow:wf->shadow];
480 [window setAcceptsMouseMovedEvents:YES];
481 [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
482 [window setDelegate:window];
484 window.queue = queue;
486 [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
487 (NSString*)kUTTypeContent,
490 contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
493 [contentView setAutoresizesSubviews:NO];
495 /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
496 because they give us mouse moves in the background. */
497 trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
498 options:(NSTrackingMouseMoved |
499 NSTrackingActiveAlways |
500 NSTrackingInVisibleRect)
502 userInfo:nil] autorelease];
505 [contentView addTrackingArea:trackingArea];
507 [window setContentView:contentView];
508 [window setInitialFirstResponder:contentView];
515 [liveResizeDisplayTimer invalidate];
516 [liveResizeDisplayTimer release];
518 [latentParentWindow release];
523 - (void) adjustFeaturesForState
525 NSUInteger style = normalStyleMask;
528 style &= ~NSResizableWindowMask;
529 if (style != [self styleMask])
530 [self setStyleMask:style];
532 if (style & NSClosableWindowMask)
533 [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
534 if (style & NSMiniaturizableWindowMask)
535 [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
538 - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
540 normalStyleMask = style_mask_for_features(wf);
541 [self adjustFeaturesForState];
542 [self setHasShadow:wf->shadow];
545 - (void) adjustWindowLevel
547 WineApplicationController* controller = [WineApplicationController sharedController];
549 BOOL fullscreen, captured;
552 WineWindow* other = nil;
554 screen = screen_covered_by_rect([self frame], [NSScreen screens]);
555 fullscreen = (screen != nil);
556 captured = (screen || [self screen]) && [controller areDisplaysCaptured];
558 if (captured || fullscreen)
561 level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
563 level = NSMainMenuWindowLevel + 1;
568 else if (self.floating)
569 level = NSFloatingWindowLevel;
571 level = NSNormalWindowLevel;
573 index = [[controller orderedWineWindows] indexOfObjectIdenticalTo:self];
574 if (index != NSNotFound && index + 1 < [[controller orderedWineWindows] count])
576 other = [[controller orderedWineWindows] objectAtIndex:index + 1];
577 if (level < [other level])
578 level = [other level];
581 if (level != [self level])
583 [self setLevelWhenActive:level];
585 /* Setting the window level above has moved this window to the front
586 of all other windows at the same level. We need to move it
587 back into its proper place among other windows of that level.
588 Also, any windows which are supposed to be in front of it had
589 better have the same or higher window level. If not, bump them
591 if (index != NSNotFound && [self isVisible])
593 for (; index > 0; index--)
595 other = [[controller orderedWineWindows] objectAtIndex:index - 1];
596 if ([other level] < level)
597 [other setLevelWhenActive:level];
600 [self orderWindow:NSWindowBelow relativeTo:[other windowNumber]];
608 - (void) setMacDrvState:(const struct macdrv_window_state*)state
610 NSWindowCollectionBehavior behavior;
612 self.disabled = state->disabled;
613 self.noActivate = state->no_activate;
615 self.floating = state->floating;
616 [self adjustWindowLevel];
618 behavior = NSWindowCollectionBehaviorDefault;
619 if (state->excluded_by_expose)
620 behavior |= NSWindowCollectionBehaviorTransient;
622 behavior |= NSWindowCollectionBehaviorManaged;
623 if (state->excluded_by_cycle)
625 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
626 if ([self isVisible])
627 [NSApp removeWindowsItem:self];
631 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
632 if ([self isVisible])
633 [NSApp addWindowsItem:self title:[self title] filename:NO];
635 [self setCollectionBehavior:behavior];
637 if (state->minimized && ![self isMiniaturized])
639 ignore_windowMiniaturize = TRUE;
640 [self miniaturize:nil];
642 else if (!state->minimized && [self isMiniaturized])
644 ignore_windowDeminiaturize = TRUE;
645 [self deminiaturize:nil];
648 /* Whatever events regarding minimization might have been in the queue are now stale. */
649 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
650 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
654 /* Returns whether or not the window was ordered in, which depends on if
655 its frame intersects any screen. */
656 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
658 WineApplicationController* controller = [WineApplicationController sharedController];
659 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
662 [controller transformProcessToForeground];
666 /* Make sure that windows that should be above this one really are.
667 This is necessary since a full-screen window gets a boost to its
668 window level to be in front of the menu bar and Dock and that moves
669 it out of the z-order that Win32 would otherwise establish. */
670 if ([prev level] < [self level])
672 NSUInteger index = [[controller orderedWineWindows] indexOfObjectIdenticalTo:prev];
673 if (index != NSNotFound)
675 [prev setLevelWhenActive:[self level]];
676 for (; index > 0; index--)
678 WineWindow* other = [[controller orderedWineWindows] objectAtIndex:index - 1];
679 if ([other level] < [self level])
680 [other setLevelWhenActive:[self level]];
684 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
685 [controller wineWindow:self ordered:NSWindowBelow relativeTo:prev];
689 /* Similarly, make sure this window is really above what it should be. */
690 if (next && [next level] > [self level])
691 [self setLevelWhenActive:[next level]];
692 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
693 [controller wineWindow:self ordered:NSWindowAbove relativeTo:next];
695 if (latentParentWindow)
697 if ([latentParentWindow level] > [self level])
698 [self setLevelWhenActive:[latentParentWindow level]];
699 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
700 [controller wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
701 self.latentParentWindow = nil;
704 /* Cocoa may adjust the frame when the window is ordered onto the screen.
705 Generate a frame-changed event just in case. The back end will ignore
706 it if nothing actually changed. */
707 [self windowDidResize:nil];
709 if (![self isExcludedFromWindowsMenu])
710 [NSApp addWindowsItem:self title:[self title] filename:NO];
718 self.latentParentWindow = [self parentWindow];
719 [latentParentWindow removeChildWindow:self];
721 [[WineApplicationController sharedController] wineWindow:self ordered:NSWindowOut relativeTo:nil];
722 [NSApp removeWindowsItem:self];
725 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
727 NSArray* screens = [NSScreen screens];
728 BOOL on_screen = [self isVisible];
729 NSRect frame, oldFrame;
731 if (![screens count]) return on_screen;
733 /* Origin is (left, top) in a top-down space. Need to convert it to
734 (left, bottom) in a bottom-up space. */
735 [[WineApplicationController sharedController] flipRect:&contentRect];
739 on_screen = frame_intersects_screens(contentRect, screens);
744 if (!NSIsEmptyRect(contentRect))
746 oldFrame = [self frame];
747 frame = [self frameRectForContentRect:contentRect];
748 if (!NSEqualRects(frame, oldFrame))
750 if (NSEqualSizes(frame.size, oldFrame.size))
751 [self setFrameOrigin:frame.origin];
753 [self setFrame:frame display:YES];
759 [self adjustWindowLevel];
761 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
762 event. The back end will ignore it if nothing actually changed. */
763 [self windowDidResize:nil];
767 /* The back end is establishing a new window size and position. It's
768 not interested in any stale events regarding those that may be sitting
770 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
777 - (void) setMacDrvParentWindow:(WineWindow*)parent
779 if ([self parentWindow] != parent)
781 [[self parentWindow] removeChildWindow:self];
782 self.latentParentWindow = nil;
783 if ([self isVisible] && parent)
785 if ([parent level] > [self level])
786 [self setLevelWhenActive:[parent level]];
787 [parent addChildWindow:self ordered:NSWindowAbove];
788 [[WineApplicationController sharedController] wineWindow:self ordered:NSWindowAbove relativeTo:parent];
791 self.latentParentWindow = parent;
795 - (void) setDisabled:(BOOL)newValue
797 if (disabled != newValue)
800 [self adjustFeaturesForState];
804 - (BOOL) needsTransparency
806 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
809 - (void) checkTransparency
811 if (![self isOpaque] && !self.needsTransparency)
813 [self setBackgroundColor:[NSColor windowBackgroundColor]];
814 [self setOpaque:YES];
816 else if ([self isOpaque] && self.needsTransparency)
818 [self setBackgroundColor:[NSColor clearColor]];
823 - (void) setShape:(NSBezierPath*)newShape
825 if (shape == newShape) return;
826 if (shape && newShape && [shape isEqual:newShape]) return;
830 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
834 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
836 shape = [newShape copy];
837 self.shapeChangedSinceLastDraw = TRUE;
839 [self checkTransparency];
842 - (void) postMouseButtonEvent:(NSEvent *)theEvent pressed:(int)pressed
844 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
847 event = macdrv_create_event(MOUSE_BUTTON, self);
848 event->mouse_button.button = [theEvent buttonNumber];
849 event->mouse_button.pressed = pressed;
850 event->mouse_button.x = pt.x;
851 event->mouse_button.y = pt.y;
852 event->mouse_button.time_ms = [[WineApplicationController sharedController] ticksForEventTime:[theEvent timestamp]];
854 [queue postEvent:event];
856 macdrv_release_event(event);
859 - (void) makeFocused:(BOOL)activate
861 WineApplicationController* controller = [WineApplicationController sharedController];
864 [controller transformProcessToForeground];
866 /* If a borderless window is offscreen, orderFront: won't move
867 it onscreen like it would for a titled window. Do that ourselves. */
868 screens = [NSScreen screens];
869 if (!([self styleMask] & NSTitledWindowMask) && ![self isVisible] &&
870 !frame_intersects_screens([self frame], screens))
872 NSScreen* primaryScreen = [screens objectAtIndex:0];
873 NSRect frame = [primaryScreen frame];
874 [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
875 frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
876 [self setFrame:frame display:YES];
879 if ([[controller orderedWineWindows] count])
883 front = [[controller orderedWineWindows] objectAtIndex:0];
886 for (front in [controller orderedWineWindows])
887 if (!front.floating) break;
889 if (front && [front levelWhenActive] > [self levelWhenActive])
890 [self setLevelWhenActive:[front levelWhenActive]];
893 [NSApp activateIgnoringOtherApps:YES];
894 [self orderFront:nil];
895 [controller wineWindow:self ordered:NSWindowAbove relativeTo:nil];
896 causing_becomeKeyWindow = TRUE;
897 [self makeKeyWindow];
898 causing_becomeKeyWindow = FALSE;
899 if (latentParentWindow)
901 if ([latentParentWindow level] > [self level])
902 [self setLevelWhenActive:[latentParentWindow level]];
903 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
904 [controller wineWindow:self ordered:NSWindowAbove relativeTo:latentParentWindow];
905 self.latentParentWindow = nil;
907 if (![self isExcludedFromWindowsMenu])
908 [NSApp addWindowsItem:self title:[self title] filename:NO];
910 /* Cocoa may adjust the frame when the window is ordered onto the screen.
911 Generate a frame-changed event just in case. The back end will ignore
912 it if nothing actually changed. */
913 [self windowDidResize:nil];
916 - (void) postKey:(uint16_t)keyCode
917 pressed:(BOOL)pressed
918 modifiers:(NSUInteger)modifiers
919 event:(NSEvent*)theEvent
923 WineApplicationController* controller = [WineApplicationController sharedController];
925 event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
926 event->key.keycode = keyCode;
927 event->key.modifiers = modifiers;
928 event->key.time_ms = [controller ticksForEventTime:[theEvent timestamp]];
930 if ((cgevent = [theEvent CGEvent]))
932 CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
933 kCGKeyboardEventKeyboardType);
934 if (keyboardType != controller.keyboardType)
936 controller.keyboardType = keyboardType;
937 [controller keyboardSelectionDidChange];
941 [queue postEvent:event];
943 macdrv_release_event(event);
946 - (void) postKeyEvent:(NSEvent *)theEvent
948 [self flagsChanged:theEvent];
949 [self postKey:[theEvent keyCode]
950 pressed:[theEvent type] == NSKeyDown
951 modifiers:[theEvent modifierFlags]
955 - (void) postMouseMovedEvent:(NSEvent *)theEvent absolute:(BOOL)absolute
961 CGPoint point = CGEventGetLocation([theEvent CGEvent]);
963 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, self);
964 event->mouse_moved.x = point.x;
965 event->mouse_moved.y = point.y;
972 /* Add event delta to accumulated delta error */
973 /* deltaY is already flipped */
974 mouseMoveDeltaX += [theEvent deltaX];
975 mouseMoveDeltaY += [theEvent deltaY];
977 event = macdrv_create_event(MOUSE_MOVED, self);
978 event->mouse_moved.x = mouseMoveDeltaX;
979 event->mouse_moved.y = mouseMoveDeltaY;
981 /* Keep the remainder after integer truncation. */
982 mouseMoveDeltaX -= event->mouse_moved.x;
983 mouseMoveDeltaY -= event->mouse_moved.y;
986 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
988 event->mouse_moved.time_ms = [[WineApplicationController sharedController] ticksForEventTime:[theEvent timestamp]];
990 [queue postEvent:event];
993 macdrv_release_event(event);
996 - (void) setLevelWhenActive:(NSInteger)level
998 levelWhenActive = level;
999 if (([NSApp isActive] || level <= NSFloatingWindowLevel) &&
1000 level != [self level])
1001 [self setLevel:level];
1006 * ---------- NSWindow method overrides ----------
1008 - (BOOL) canBecomeKeyWindow
1010 if (causing_becomeKeyWindow) return YES;
1011 if (self.disabled || self.noActivate) return NO;
1012 return [self isKeyWindow];
1015 - (BOOL) canBecomeMainWindow
1017 return [self canBecomeKeyWindow];
1020 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1022 // If a window is sized to completely cover a screen, then it's in
1023 // full-screen mode. In that case, we don't allow NSWindow to constrain
1025 NSRect contentRect = [self contentRectForFrameRect:frameRect];
1026 if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
1027 frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1031 - (BOOL) isExcludedFromWindowsMenu
1033 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1036 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1038 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1039 return [self isKeyWindow] || (!self.disabled && !self.noActivate);
1040 return [super validateMenuItem:menuItem];
1043 /* We don't call this. It's the action method of the items in the Window menu. */
1044 - (void) makeKeyAndOrderFront:(id)sender
1046 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1047 [[WineApplicationController sharedController] windowGotFocus:self];
1050 - (void) sendEvent:(NSEvent*)event
1052 WineApplicationController* controller = [WineApplicationController sharedController];
1054 /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1055 interface control. For example, Control-Tab switches focus among
1056 views. We want to bypass that feature, so directly route key-down
1057 events to -keyDown:. */
1058 if ([event type] == NSKeyDown)
1059 [[self firstResponder] keyDown:event];
1062 if ([event type] == NSLeftMouseDown)
1064 NSWindowButton windowButton;
1065 BOOL broughtWindowForward = TRUE;
1067 /* Since our windows generally claim they can't be made key, clicks
1068 in their title bars are swallowed by the theme frame stuff. So,
1069 we hook directly into the event stream and assume that any click
1070 in the window will activate it, if Wine and the Win32 program
1072 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1073 [controller windowGotFocus:self];
1075 /* Any left-click on our window anyplace other than the close or
1076 minimize buttons will bring it forward. */
1077 for (windowButton = NSWindowCloseButton;
1078 windowButton <= NSWindowMiniaturizeButton;
1081 NSButton* button = [[event window] standardWindowButton:windowButton];
1084 NSPoint point = [button convertPoint:[event locationInWindow] fromView:nil];
1085 if ([button mouse:point inRect:[button bounds]])
1087 broughtWindowForward = FALSE;
1093 if (broughtWindowForward)
1094 [controller wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1097 [super sendEvent:event];
1103 * ---------- NSResponder method overrides ----------
1105 - (void) mouseDown:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:1]; }
1106 - (void) rightMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
1107 - (void) otherMouseDown:(NSEvent *)theEvent { [self mouseDown:theEvent]; }
1109 - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent pressed:0]; }
1110 - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
1111 - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; }
1113 - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1114 - (void) keyUp:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1116 - (void) flagsChanged:(NSEvent *)theEvent
1118 static const struct {
1122 { NX_ALPHASHIFTMASK, kVK_CapsLock },
1123 { NX_DEVICELSHIFTKEYMASK, kVK_Shift },
1124 { NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
1125 { NX_DEVICELCTLKEYMASK, kVK_Control },
1126 { NX_DEVICERCTLKEYMASK, kVK_RightControl },
1127 { NX_DEVICELALTKEYMASK, kVK_Option },
1128 { NX_DEVICERALTKEYMASK, kVK_RightOption },
1129 { NX_DEVICELCMDKEYMASK, kVK_Command },
1130 { NX_DEVICERCMDKEYMASK, kVK_RightCommand },
1133 NSUInteger modifierFlags = [theEvent modifierFlags];
1135 int i, last_changed;
1137 fix_device_modifiers_by_generic(&modifierFlags);
1138 changed = modifierFlags ^ lastModifierFlags;
1141 for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
1142 if (changed & modifiers[i].mask)
1145 for (i = 0; i <= last_changed; i++)
1147 if (changed & modifiers[i].mask)
1149 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
1151 if (i == last_changed)
1152 lastModifierFlags = modifierFlags;
1155 lastModifierFlags ^= modifiers[i].mask;
1156 fix_generic_modifiers_by_device(&lastModifierFlags);
1159 // Caps lock generates one event for each press-release action.
1160 // We need to simulate a pair of events for each actual event.
1161 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1163 [self postKey:modifiers[i].keycode
1165 modifiers:lastModifierFlags
1166 event:(NSEvent*)theEvent];
1170 [self postKey:modifiers[i].keycode
1172 modifiers:lastModifierFlags
1173 event:(NSEvent*)theEvent];
1178 - (void) scrollWheel:(NSEvent *)theEvent
1181 macdrv_event* event;
1184 BOOL continuous = FALSE;
1186 cgevent = [theEvent CGEvent];
1187 pt = CGEventGetLocation(cgevent);
1189 event = macdrv_create_event(MOUSE_SCROLL, self);
1190 event->mouse_scroll.x = pt.x;
1191 event->mouse_scroll.y = pt.y;
1192 event->mouse_scroll.time_ms = [[WineApplicationController sharedController] ticksForEventTime:[theEvent timestamp]];
1194 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1198 /* Continuous scroll wheel events come from high-precision scrolling
1199 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1200 For these, we can get more precise data from the CGEvent API. */
1201 /* Axis 1 is vertical, axis 2 is horizontal. */
1202 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1203 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1207 double pixelsPerLine = 10;
1208 CGEventSourceRef source;
1210 /* The non-continuous values are in units of "lines", not pixels. */
1211 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1213 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1217 x = pixelsPerLine * [theEvent deltaX];
1218 y = pixelsPerLine * [theEvent deltaY];
1221 /* Mac: negative is right or down, positive is left or up.
1222 Win32: negative is left or down, positive is right or up.
1223 So, negate the X scroll value to translate. */
1226 /* The x,y values so far are in pixels. Win32 expects to receive some
1227 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1228 6 times the pixel value. */
1229 event->mouse_scroll.x_scroll = 6 * x;
1230 event->mouse_scroll.y_scroll = 6 * y;
1234 /* For non-continuous "clicky" wheels, if there was any motion, make
1235 sure there was at least WHEEL_DELTA motion. This is so, at slow
1236 speeds where the system's acceleration curve is actually reducing the
1237 scroll distance, the user is sure to get some action out of each click.
1238 For example, this is important for rotating though weapons in a
1239 first-person shooter. */
1240 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1241 event->mouse_scroll.x_scroll = 120;
1242 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1243 event->mouse_scroll.x_scroll = -120;
1245 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1246 event->mouse_scroll.y_scroll = 120;
1247 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1248 event->mouse_scroll.y_scroll = -120;
1251 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1252 [queue postEvent:event];
1254 macdrv_release_event(event);
1259 * ---------- NSWindowDelegate methods ----------
1261 - (void)windowDidBecomeKey:(NSNotification *)notification
1263 WineApplicationController* controller = [WineApplicationController sharedController];
1264 NSEvent* event = [controller lastFlagsChanged];
1266 [self flagsChanged:event];
1268 if (causing_becomeKeyWindow) return;
1270 [controller windowGotFocus:self];
1273 - (void)windowDidDeminiaturize:(NSNotification *)notification
1275 if (!ignore_windowDeminiaturize)
1277 macdrv_event* event;
1279 /* Coalesce events by discarding any previous ones still in the queue. */
1280 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1281 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1284 event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1285 [queue postEvent:event];
1286 macdrv_release_event(event);
1289 ignore_windowDeminiaturize = FALSE;
1291 [[WineApplicationController sharedController] wineWindow:self ordered:NSWindowAbove relativeTo:nil];
1294 - (void) windowDidEndLiveResize:(NSNotification *)notification
1296 [liveResizeDisplayTimer invalidate];
1297 [liveResizeDisplayTimer release];
1298 liveResizeDisplayTimer = nil;
1301 - (void)windowDidMove:(NSNotification *)notification
1303 [self windowDidResize:notification];
1306 - (void)windowDidResignKey:(NSNotification *)notification
1308 macdrv_event* event;
1310 if (causing_becomeKeyWindow) return;
1312 event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1313 [queue postEvent:event];
1314 macdrv_release_event(event);
1317 - (void)windowDidResize:(NSNotification *)notification
1319 macdrv_event* event;
1320 NSRect frame = [self contentRectForFrameRect:[self frame]];
1322 [[WineApplicationController sharedController] flipRect:&frame];
1324 /* Coalesce events by discarding any previous ones still in the queue. */
1325 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1328 event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1329 event->window_frame_changed.frame = NSRectToCGRect(frame);
1330 [queue postEvent:event];
1331 macdrv_release_event(event);
1334 - (BOOL)windowShouldClose:(id)sender
1336 macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1337 [queue postEvent:event];
1338 macdrv_release_event(event);
1342 - (void)windowWillMiniaturize:(NSNotification *)notification
1344 if (!ignore_windowMiniaturize)
1346 macdrv_event* event;
1348 /* Coalesce events by discarding any previous ones still in the queue. */
1349 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1350 event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1353 event = macdrv_create_event(WINDOW_DID_MINIMIZE, self);
1354 [queue postEvent:event];
1355 macdrv_release_event(event);
1358 ignore_windowMiniaturize = FALSE;
1361 - (void) windowWillStartLiveResize:(NSNotification *)notification
1363 // There's a strange restriction in window redrawing during Cocoa-
1364 // managed window resizing. Only calls to -[NSView setNeedsDisplay...]
1365 // that happen synchronously when Cocoa tells us that our window size
1366 // has changed or asynchronously in a short interval thereafter provoke
1367 // the window to redraw. Calls to those methods that happen asynchronously
1368 // a half second or more after the last change of the window size aren't
1369 // heeded until the next resize-related user event (e.g. mouse movement).
1371 // Wine often has a significant delay between when it's been told that
1372 // the window has changed size and when it can flush completed drawing.
1373 // So, our windows would get stuck with incomplete drawing for as long
1374 // as the user holds the mouse button down and doesn't move it.
1376 // We address this by "manually" asking our windows to check if they need
1377 // redrawing every so often (during live resize only).
1378 [self windowDidEndLiveResize:nil];
1379 liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
1381 selector:@selector(displayIfNeeded)
1384 [liveResizeDisplayTimer retain];
1385 [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
1386 forMode:NSRunLoopCommonModes];
1391 * ---------- NSPasteboardOwner methods ----------
1393 - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
1395 macdrv_query* query = macdrv_create_query();
1396 query->type = QUERY_PASTEBOARD_DATA;
1397 query->window = (macdrv_window)[self retain];
1398 query->pasteboard_data.type = (CFStringRef)[type copy];
1400 [self.queue query:query timeout:3];
1401 macdrv_release_query(query);
1406 * ---------- NSDraggingDestination methods ----------
1408 - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
1410 return [self draggingUpdated:sender];
1413 - (void) draggingExited:(id <NSDraggingInfo>)sender
1415 // This isn't really a query. We don't need any response. However, it
1416 // has to be processed in a similar manner as the other drag-and-drop
1417 // queries in order to maintain the proper order of operations.
1418 macdrv_query* query = macdrv_create_query();
1419 query->type = QUERY_DRAG_EXITED;
1420 query->window = (macdrv_window)[self retain];
1422 [self.queue query:query timeout:0.1];
1423 macdrv_release_query(query);
1426 - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
1428 NSDragOperation ret;
1429 NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1430 NSPasteboard* pb = [sender draggingPasteboard];
1432 macdrv_query* query = macdrv_create_query();
1433 query->type = QUERY_DRAG_OPERATION;
1434 query->window = (macdrv_window)[self retain];
1435 query->drag_operation.x = pt.x;
1436 query->drag_operation.y = pt.y;
1437 query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
1438 query->drag_operation.accepted_op = NSDragOperationNone;
1439 query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
1441 [self.queue query:query timeout:3];
1442 ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
1443 macdrv_release_query(query);
1448 - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
1451 NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1452 NSPasteboard* pb = [sender draggingPasteboard];
1454 macdrv_query* query = macdrv_create_query();
1455 query->type = QUERY_DRAG_DROP;
1456 query->window = (macdrv_window)[self retain];
1457 query->drag_drop.x = pt.x;
1458 query->drag_drop.y = pt.y;
1459 query->drag_drop.op = [sender draggingSourceOperationMask];
1460 query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
1462 [self.queue query:query timeout:3 * 60 processEvents:YES];
1463 ret = query->status;
1464 macdrv_release_query(query);
1469 - (BOOL) wantsPeriodicDraggingUpdates
1477 /***********************************************************************
1478 * macdrv_create_cocoa_window
1480 * Create a Cocoa window with the given content frame and features (e.g.
1481 * title bar, close box, etc.).
1483 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1484 CGRect frame, void* hwnd, macdrv_event_queue queue)
1486 __block WineWindow* window;
1489 window = [[WineWindow createWindowWithFeatures:wf
1490 windowFrame:NSRectFromCGRect(frame)
1492 queue:(WineEventQueue*)queue] retain];
1495 return (macdrv_window)window;
1498 /***********************************************************************
1499 * macdrv_destroy_cocoa_window
1501 * Destroy a Cocoa window.
1503 void macdrv_destroy_cocoa_window(macdrv_window w)
1505 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1506 WineWindow* window = (WineWindow*)w;
1508 [window.queue discardEventsMatchingMask:-1 forWindow:window];
1515 /***********************************************************************
1516 * macdrv_get_window_hwnd
1518 * Get the hwnd that was set for the window at creation.
1520 void* macdrv_get_window_hwnd(macdrv_window w)
1522 WineWindow* window = (WineWindow*)w;
1526 /***********************************************************************
1527 * macdrv_set_cocoa_window_features
1529 * Update a Cocoa window's features.
1531 void macdrv_set_cocoa_window_features(macdrv_window w,
1532 const struct macdrv_window_features* wf)
1534 WineWindow* window = (WineWindow*)w;
1537 [window setWindowFeatures:wf];
1541 /***********************************************************************
1542 * macdrv_set_cocoa_window_state
1544 * Update a Cocoa window's state.
1546 void macdrv_set_cocoa_window_state(macdrv_window w,
1547 const struct macdrv_window_state* state)
1549 WineWindow* window = (WineWindow*)w;
1552 [window setMacDrvState:state];
1556 /***********************************************************************
1557 * macdrv_set_cocoa_window_title
1559 * Set a Cocoa window's title.
1561 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1564 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1565 WineWindow* window = (WineWindow*)w;
1566 NSString* titleString;
1569 titleString = [NSString stringWithCharacters:title length:length];
1572 OnMainThreadAsync(^{
1573 [window setTitle:titleString];
1574 if ([window isVisible] && ![window isExcludedFromWindowsMenu])
1575 [NSApp changeWindowsItem:window title:titleString filename:NO];
1581 /***********************************************************************
1582 * macdrv_order_cocoa_window
1584 * Reorder a Cocoa window relative to other windows. If prev is
1585 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
1586 * it is ordered above that window. Otherwise, it is ordered to the
1589 * Returns true if the window has actually been ordered onto the screen
1590 * (i.e. if its frame intersects with a screen). Otherwise, false.
1592 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1595 WineWindow* window = (WineWindow*)w;
1596 __block BOOL on_screen;
1599 on_screen = [window orderBelow:(WineWindow*)prev
1600 orAbove:(WineWindow*)next];
1606 /***********************************************************************
1607 * macdrv_hide_cocoa_window
1609 * Hides a Cocoa window.
1611 void macdrv_hide_cocoa_window(macdrv_window w)
1613 WineWindow* window = (WineWindow*)w;
1616 [window doOrderOut];
1620 /***********************************************************************
1621 * macdrv_set_cocoa_window_frame
1623 * Move a Cocoa window. If the window has been moved out of the bounds
1624 * of the desktop, it is ordered out. (This routine won't ever order a
1625 * window in, though.)
1627 * Returns true if the window is on screen; false otherwise.
1629 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1631 WineWindow* window = (WineWindow*)w;
1632 __block BOOL on_screen;
1635 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1641 /***********************************************************************
1642 * macdrv_get_cocoa_window_frame
1644 * Gets the frame of a Cocoa window.
1646 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1648 WineWindow* window = (WineWindow*)w;
1653 frame = [window contentRectForFrameRect:[window frame]];
1654 [[WineApplicationController sharedController] flipRect:&frame];
1655 *out_frame = NSRectToCGRect(frame);
1659 /***********************************************************************
1660 * macdrv_set_cocoa_parent_window
1662 * Sets the parent window for a Cocoa window. If parent is NULL, clears
1663 * the parent window.
1665 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1667 WineWindow* window = (WineWindow*)w;
1670 [window setMacDrvParentWindow:(WineWindow*)parent];
1674 /***********************************************************************
1675 * macdrv_set_window_surface
1677 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1679 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1680 WineWindow* window = (WineWindow*)w;
1683 window.surface = surface;
1684 window.surface_mutex = mutex;
1690 /***********************************************************************
1691 * macdrv_window_needs_display
1693 * Mark a window as needing display in a specified rect (in non-client
1694 * area coordinates).
1696 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1698 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1699 WineWindow* window = (WineWindow*)w;
1701 OnMainThreadAsync(^{
1702 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1708 /***********************************************************************
1709 * macdrv_set_window_shape
1711 * Sets the shape of a Cocoa window from an array of rectangles. If
1712 * rects is NULL, resets the window's shape to its frame.
1714 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1716 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1717 WineWindow* window = (WineWindow*)w;
1720 if (!rects || !count)
1727 path = [NSBezierPath bezierPath];
1728 for (i = 0; i < count; i++)
1729 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1730 window.shape = path;
1737 /***********************************************************************
1738 * macdrv_set_window_alpha
1740 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1742 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1743 WineWindow* window = (WineWindow*)w;
1745 [window setAlphaValue:alpha];
1750 /***********************************************************************
1751 * macdrv_set_window_color_key
1753 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1756 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1757 WineWindow* window = (WineWindow*)w;
1760 window.colorKeyed = TRUE;
1761 window.colorKeyRed = keyRed;
1762 window.colorKeyGreen = keyGreen;
1763 window.colorKeyBlue = keyBlue;
1764 [window checkTransparency];
1770 /***********************************************************************
1771 * macdrv_clear_window_color_key
1773 void macdrv_clear_window_color_key(macdrv_window w)
1775 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1776 WineWindow* window = (WineWindow*)w;
1779 window.colorKeyed = FALSE;
1780 [window checkTransparency];
1786 /***********************************************************************
1787 * macdrv_window_use_per_pixel_alpha
1789 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1791 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1792 WineWindow* window = (WineWindow*)w;
1795 window.usePerPixelAlpha = use_per_pixel_alpha;
1796 [window checkTransparency];
1802 /***********************************************************************
1803 * macdrv_give_cocoa_window_focus
1805 * Makes the Cocoa window "key" (gives it keyboard focus). This also
1806 * orders it front and, if its frame was not within the desktop bounds,
1807 * Cocoa will typically move it on-screen.
1809 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
1811 WineWindow* window = (WineWindow*)w;
1814 [window makeFocused:activate];
1818 /***********************************************************************
1819 * macdrv_create_view
1821 * Creates and returns a view in the specified rect of the window. The
1822 * caller is responsible for calling macdrv_dispose_view() on the view
1823 * when it is done with it.
1825 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
1827 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1828 WineWindow* window = (WineWindow*)w;
1829 __block WineContentView* view;
1831 if (CGRectIsNull(rect)) rect = CGRectZero;
1834 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1836 view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
1837 [view setAutoresizesSubviews:NO];
1838 [nc addObserver:view
1839 selector:@selector(updateGLContexts)
1840 name:NSViewGlobalFrameDidChangeNotification
1842 [nc addObserver:view
1843 selector:@selector(updateGLContexts)
1844 name:NSApplicationDidChangeScreenParametersNotification
1846 [[window contentView] addSubview:view];
1850 return (macdrv_view)view;
1853 /***********************************************************************
1854 * macdrv_dispose_view
1856 * Destroys a view previously returned by macdrv_create_view.
1858 void macdrv_dispose_view(macdrv_view v)
1860 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1861 WineContentView* view = (WineContentView*)v;
1864 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1866 [nc removeObserver:view
1867 name:NSViewGlobalFrameDidChangeNotification
1869 [nc removeObserver:view
1870 name:NSApplicationDidChangeScreenParametersNotification
1872 [view removeFromSuperview];
1879 /***********************************************************************
1880 * macdrv_set_view_window_and_frame
1882 * Move a view to a new window and/or position within its window. If w
1883 * is NULL, leave the view in its current window and just change its
1886 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
1888 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1889 WineContentView* view = (WineContentView*)v;
1890 WineWindow* window = (WineWindow*)w;
1892 if (CGRectIsNull(rect)) rect = CGRectZero;
1895 BOOL changedWindow = (window && window != [view window]);
1896 NSRect newFrame = NSRectFromCGRect(rect);
1897 NSRect oldFrame = [view frame];
1901 [view removeFromSuperview];
1902 [[window contentView] addSubview:view];
1905 if (!NSEqualRects(oldFrame, newFrame))
1908 [[view superview] setNeedsDisplayInRect:oldFrame];
1909 if (NSEqualPoints(oldFrame.origin, newFrame.origin))
1910 [view setFrameSize:newFrame.size];
1911 else if (NSEqualSizes(oldFrame.size, newFrame.size))
1912 [view setFrameOrigin:newFrame.origin];
1914 [view setFrame:newFrame];
1915 [view setNeedsDisplay:YES];
1922 /***********************************************************************
1923 * macdrv_add_view_opengl_context
1925 * Add an OpenGL context to the list being tracked for each view.
1927 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1929 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1930 WineContentView* view = (WineContentView*)v;
1931 WineOpenGLContext *context = (WineOpenGLContext*)c;
1933 OnMainThreadAsync(^{
1934 [view addGLContext:context];
1940 /***********************************************************************
1941 * macdrv_remove_view_opengl_context
1943 * Add an OpenGL context to the list being tracked for each view.
1945 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1947 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1948 WineContentView* view = (WineContentView*)v;
1949 WineOpenGLContext *context = (WineOpenGLContext*)c;
1951 OnMainThreadAsync(^{
1952 [view removeGLContext:context];
1958 /***********************************************************************
1959 * macdrv_window_background_color
1961 * Returns the standard Mac window background color as a 32-bit value of
1962 * the form 0x00rrggbb.
1964 uint32_t macdrv_window_background_color(void)
1966 static uint32_t result;
1967 static dispatch_once_t once;
1969 // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
1970 // color spaces (RGB or grayscale). So, the only way to get RGB values out
1971 // of it is to draw with it.
1972 dispatch_once(&once, ^{
1974 unsigned char rgbx[4];
1975 unsigned char *planes = rgbx;
1976 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
1983 colorSpaceName:NSCalibratedRGBColorSpace
1987 [NSGraphicsContext saveGraphicsState];
1988 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
1989 [[NSColor windowBackgroundColor] set];
1990 NSRectFill(NSMakeRect(0, 0, 1, 1));
1991 [NSGraphicsContext restoreGraphicsState];
1993 result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
2000 /***********************************************************************
2001 * macdrv_send_text_input_event
2003 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
2008 WineWindow* window = (WineWindow*)[NSApp keyWindow];
2009 if (![window isKindOfClass:[WineWindow class]])
2011 window = (WineWindow*)[NSApp mainWindow];
2012 if (![window isKindOfClass:[WineWindow class]] && [[NSApp orderedWineWindows] count])
2014 window = [[NSApp orderedWineWindows] objectAtIndex:0];
2015 if (![window isKindOfClass:[WineWindow class]])
2022 NSUInteger localFlags = flags;
2026 window.imeData = data;
2027 fix_device_modifiers_by_generic(&localFlags);
2029 // An NSEvent created with +keyEventWithType:... is internally marked
2030 // as synthetic and doesn't get sent through input methods. But one
2031 // created from a CGEvent doesn't have that problem.
2032 c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
2033 CGEventSetFlags(c, localFlags);
2034 CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
2035 event = [NSEvent eventWithCGEvent:c];
2038 window.commandDone = FALSE;
2039 ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;