Sweden-Number/dlls/winemac.drv/cocoa_window.m

3272 lines
111 KiB
Mathematica
Raw Normal View History

/*
* MACDRV Cocoa window code
*
* Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#import <Carbon/Carbon.h>
#import <CoreVideo/CoreVideo.h>
#import "cocoa_window.h"
#include "macdrv_cocoa.h"
#import "cocoa_app.h"
#import "cocoa_event.h"
2013-03-06 11:59:07 +01:00
#import "cocoa_opengl.h"
#if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
enum {
NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8,
NSWindowFullScreenButton = 7,
NSFullScreenWindowMask = 1 << 14,
};
@interface NSWindow (WineFullScreenExtensions)
- (void) toggleFullScreen:(id)sender;
@end
#endif
/* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
enum {
kVK_RightCommand = 0x36, /* Invented for Wine; was unused */
};
static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
{
NSUInteger style_mask;
if (wf->title_bar)
{
style_mask = NSTitledWindowMask;
if (wf->close_button) style_mask |= NSClosableWindowMask;
if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
if (wf->resizable || wf->maximize_button) style_mask |= NSResizableWindowMask;
if (wf->utility) style_mask |= NSUtilityWindowMask;
}
else style_mask = NSBorderlessWindowMask;
return style_mask;
}
static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
{
NSScreen* screen;
for (screen in screens)
{
if (NSIntersectsRect(frame, [screen frame]))
return TRUE;
}
return FALSE;
}
static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
{
for (NSScreen* screen in screens)
{
if (NSContainsRect(rect, [screen frame]))
return screen;
}
return nil;
}
/* We rely on the supposedly device-dependent modifier flags to distinguish the
keys on the left side of the keyboard from those on the right. Some event
sources don't set those device-depdendent flags. If we see a device-independent
flag for a modifier without either corresponding device-dependent flag, assume
the left one. */
static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
{
if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
*modifiers |= NX_DEVICELCMDKEYMASK;
if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
*modifiers |= NX_DEVICELSHIFTKEYMASK;
if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
*modifiers |= NX_DEVICELCTLKEYMASK;
if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
*modifiers |= NX_DEVICELALTKEYMASK;
}
/* As we manipulate individual bits of a modifier mask, we can end up with
inconsistent sets of flags. In particular, we might set or clear one of the
left/right-specific bits, but not the corresponding non-side-specific bit.
Fix that. If either side-specific bit is set, set the non-side-specific bit,
otherwise clear it. */
static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
{
if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
*modifiers |= NX_COMMANDMASK;
else
*modifiers &= ~NX_COMMANDMASK;
if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
*modifiers |= NX_SHIFTMASK;
else
*modifiers &= ~NX_SHIFTMASK;
if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
*modifiers |= NX_CONTROLMASK;
else
*modifiers &= ~NX_CONTROLMASK;
if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
*modifiers |= NX_ALTERNATEMASK;
else
*modifiers &= ~NX_ALTERNATEMASK;
}
static inline NSUInteger adjusted_modifiers_for_option_behavior(NSUInteger modifiers)
{
fix_device_modifiers_by_generic(&modifiers);
if (left_option_is_alt && (modifiers & NX_DEVICELALTKEYMASK))
{
modifiers |= NX_DEVICELCMDKEYMASK;
modifiers &= ~NX_DEVICELALTKEYMASK;
}
if (right_option_is_alt && (modifiers & NX_DEVICERALTKEYMASK))
{
modifiers |= NX_DEVICERCMDKEYMASK;
modifiers &= ~NX_DEVICERALTKEYMASK;
}
fix_generic_modifiers_by_device(&modifiers);
return modifiers;
}
@interface NSWindow (WineAccessPrivateMethods)
- (id) _displayChanged;
@end
@interface WineDisplayLink : NSObject
{
CGDirectDisplayID _displayID;
CVDisplayLinkRef _link;
NSMutableSet* _windows;
NSTimeInterval _actualRefreshPeriod;
NSTimeInterval _nominalRefreshPeriod;
}
- (id) initWithDisplayID:(CGDirectDisplayID)displayID;
- (void) addWindow:(WineWindow*)window;
- (void) removeWindow:(WineWindow*)window;
- (NSTimeInterval) refreshPeriod;
- (void) start;
@end
@implementation WineDisplayLink
static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext);
- (id) initWithDisplayID:(CGDirectDisplayID)displayID
{
self = [super init];
if (self)
{
CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &_link);
if (status == kCVReturnSuccess && !_link)
status = kCVReturnError;
if (status == kCVReturnSuccess)
status = CVDisplayLinkSetOutputCallback(_link, WineDisplayLinkCallback, self);
if (status != kCVReturnSuccess)
{
[self release];
return nil;
}
_displayID = displayID;
_windows = [[NSMutableSet alloc] init];
}
return self;
}
- (void) dealloc
{
if (_link)
{
CVDisplayLinkStop(_link);
CVDisplayLinkRelease(_link);
}
[_windows release];
[super dealloc];
}
- (void) addWindow:(WineWindow*)window
{
@synchronized(self) {
BOOL needsStart = !_windows.count;
[_windows addObject:window];
if (needsStart)
CVDisplayLinkStart(_link);
}
}
- (void) removeWindow:(WineWindow*)window
{
@synchronized(self) {
BOOL wasRunning = _windows.count > 0;
[_windows removeObject:window];
if (wasRunning && !_windows.count)
CVDisplayLinkStop(_link);
}
}
- (void) fire
{
NSSet* windows;
@synchronized(self) {
windows = [_windows copy];
}
dispatch_async(dispatch_get_main_queue(), ^{
BOOL anyDisplayed = FALSE;
for (WineWindow* window in windows)
{
if ([window viewsNeedDisplay])
{
[window displayIfNeeded];
anyDisplayed = YES;
}
}
if (!anyDisplayed)
CVDisplayLinkStop(_link);
});
[windows release];
}
- (NSTimeInterval) refreshPeriod
{
if (_actualRefreshPeriod || (_actualRefreshPeriod = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_link)))
return _actualRefreshPeriod;
if (_nominalRefreshPeriod)
return _nominalRefreshPeriod;
CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(_link);
if (time.flags & kCVTimeIsIndefinite)
return 1.0 / 60.0;
_nominalRefreshPeriod = time.timeValue / (double)time.timeScale;
return _nominalRefreshPeriod;
}
- (void) start
{
CVDisplayLinkStart(_link);
}
static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
WineDisplayLink* link = displayLinkContext;
[link fire];
return kCVReturnSuccess;
}
@end
@interface WineContentView : NSView <NSTextInputClient>
2013-03-06 11:59:07 +01:00
{
NSMutableArray* glContexts;
NSMutableArray* pendingGlContexts;
BOOL clearedGlSurface;
NSMutableAttributedString* markedText;
NSRange markedTextSelection;
2013-03-06 11:59:07 +01:00
}
- (void) addGLContext:(WineOpenGLContext*)context;
- (void) removeGLContext:(WineOpenGLContext*)context;
- (void) updateGLContexts;
@end
@interface WineWindow ()
@property (readwrite, nonatomic) BOOL disabled;
@property (readwrite, nonatomic) BOOL noActivate;
@property (readwrite, nonatomic) BOOL floating;
@property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose;
@property (retain, nonatomic) NSWindow* latentParentWindow;
@property (nonatomic) void* hwnd;
@property (retain, readwrite, nonatomic) WineEventQueue* queue;
@property (nonatomic) void* surface;
@property (nonatomic) pthread_mutex_t* surface_mutex;
2013-01-15 03:23:58 +01:00
@property (copy, nonatomic) NSBezierPath* shape;
@property (copy, nonatomic) NSData* shapeData;
2013-01-15 03:23:58 +01:00
@property (nonatomic) BOOL shapeChangedSinceLastDraw;
@property (readonly, nonatomic) BOOL needsTransparency;
@property (nonatomic) BOOL colorKeyed;
@property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
@property (nonatomic) BOOL usePerPixelAlpha;
@property (assign, nonatomic) void* imeData;
@property (nonatomic) BOOL commandDone;
@property (readonly, copy, nonatomic) NSArray* childWineWindows;
- (void) updateColorSpace;
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
- (void) updateForGLSubviews;
- (BOOL) becameEligibleParentOrChild;
- (void) becameIneligibleChild;
@end
@implementation WineContentView
2013-03-06 11:59:07 +01:00
- (void) dealloc
{
[markedText release];
2013-03-06 11:59:07 +01:00
[glContexts release];
[pendingGlContexts release];
[super dealloc];
}
- (BOOL) isFlipped
{
return YES;
}
- (void) drawRect:(NSRect)rect
{
WineWindow* window = (WineWindow*)[self window];
2013-03-06 11:59:07 +01:00
for (WineOpenGLContext* context in pendingGlContexts)
{
if (!clearedGlSurface)
{
context.shouldClearToBlack = TRUE;
clearedGlSurface = TRUE;
}
2013-03-06 11:59:07 +01:00
context.needsUpdate = TRUE;
}
2013-03-06 11:59:07 +01:00
[glContexts addObjectsFromArray:pendingGlContexts];
[pendingGlContexts removeAllObjects];
if ([window contentView] != self)
return;
if (window.shapeChangedSinceLastDraw && window.shape && !window.colorKeyed && !window.usePerPixelAlpha)
{
[[NSColor clearColor] setFill];
NSRectFill(rect);
[window.shape addClip];
[[NSColor windowBackgroundColor] setFill];
NSRectFill(rect);
}
if (window.surface && window.surface_mutex &&
!pthread_mutex_lock(window.surface_mutex))
{
const CGRect* rects;
int count;
if (get_surface_blit_rects(window.surface, &rects, &count) && count)
{
CGContextRef context;
int i;
[window.shape addClip];
context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
for (i = 0; i < count; i++)
{
CGRect imageRect;
CGImageRef image;
imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
image = create_surface_image(window.surface, &imageRect, FALSE);
2013-01-15 03:23:58 +01:00
if (image)
{
if (window.colorKeyed)
{
CGImageRef maskedImage;
CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
maskedImage = CGImageCreateWithMaskingColors(image, components);
if (maskedImage)
{
CGImageRelease(image);
image = maskedImage;
}
}
CGContextDrawImage(context, imageRect, image);
CGImageRelease(image);
}
}
}
pthread_mutex_unlock(window.surface_mutex);
}
// If the window may be transparent, then we have to invalidate the
// shadow every time we draw. Also, if this is the first time we've
// drawn since changing from transparent to opaque.
if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw)
{
window.shapeChangedSinceLastDraw = FALSE;
[window invalidateShadow];
}
}
2013-03-06 11:59:07 +01:00
- (void) addGLContext:(WineOpenGLContext*)context
{
if (!glContexts)
glContexts = [[NSMutableArray alloc] init];
if (!pendingGlContexts)
pendingGlContexts = [[NSMutableArray alloc] init];
if ([[self window] windowNumber] > 0 && !NSIsEmptyRect([self visibleRect]))
{
[glContexts addObject:context];
if (!clearedGlSurface)
{
context.shouldClearToBlack = TRUE;
clearedGlSurface = TRUE;
}
context.needsUpdate = TRUE;
}
else
{
[pendingGlContexts addObject:context];
[self setNeedsDisplay:YES];
}
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
[(WineWindow*)[self window] updateForGLSubviews];
2013-03-06 11:59:07 +01:00
}
- (void) removeGLContext:(WineOpenGLContext*)context
{
[glContexts removeObjectIdenticalTo:context];
[pendingGlContexts removeObjectIdenticalTo:context];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
[(WineWindow*)[self window] updateForGLSubviews];
2013-03-06 11:59:07 +01:00
}
- (void) updateGLContexts
{
for (WineOpenGLContext* context in glContexts)
context.needsUpdate = TRUE;
}
- (BOOL) hasGLContext
{
return [glContexts count] || [pendingGlContexts count];
}
- (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
{
return YES;
}
- (BOOL) preservesContentDuringLiveResize
{
// Returning YES from this tells Cocoa to keep our view's content during
// a Cocoa-driven resize. In theory, we're also supposed to override
// -setFrameSize: to mark exposed sections as needing redisplay, but
// user32 will take care of that in a roundabout way. This way, we don't
// redraw until the window surface is flushed.
//
// This doesn't do anything when we resize the window ourselves.
return YES;
}
- (BOOL)acceptsFirstResponder
{
return [[self window] contentView] == self;
}
- (BOOL) mouseDownCanMoveWindow
{
return NO;
}
- (void) completeText:(NSString*)text
{
macdrv_event* event;
WineWindow* window = (WineWindow*)[self window];
event = macdrv_create_event(IM_SET_TEXT, window);
event->im_set_text.data = [window imeData];
event->im_set_text.text = (CFStringRef)[text copy];
event->im_set_text.complete = TRUE;
[[window queue] postEvent:event];
macdrv_release_event(event);
[markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
markedTextSelection = NSMakeRange(0, 0);
[[self inputContext] discardMarkedText];
}
- (NSFocusRingType) focusRingType
{
return NSFocusRingTypeNone;
}
/*
* ---------- NSTextInputClient methods ----------
*/
- (NSTextInputContext*) inputContext
{
if (!markedText)
markedText = [[NSMutableAttributedString alloc] init];
return [super inputContext];
}
- (void) insertText:(id)string replacementRange:(NSRange)replacementRange
{
if ([string isKindOfClass:[NSAttributedString class]])
string = [string string];
if ([string isKindOfClass:[NSString class]])
[self completeText:string];
}
- (void) doCommandBySelector:(SEL)aSelector
{
[(WineWindow*)[self window] setCommandDone:TRUE];
}
- (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
if ([string isKindOfClass:[NSAttributedString class]])
string = [string string];
if ([string isKindOfClass:[NSString class]])
{
macdrv_event* event;
WineWindow* window = (WineWindow*)[self window];
if (replacementRange.location == NSNotFound)
replacementRange = NSMakeRange(0, [markedText length]);
[markedText replaceCharactersInRange:replacementRange withString:string];
markedTextSelection = selectedRange;
markedTextSelection.location += replacementRange.location;
event = macdrv_create_event(IM_SET_TEXT, window);
event->im_set_text.data = [window imeData];
event->im_set_text.text = (CFStringRef)[[markedText string] copy];
event->im_set_text.complete = FALSE;
event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length;
[[window queue] postEvent:event];
macdrv_release_event(event);
[[self inputContext] invalidateCharacterCoordinates];
}
}
- (void) unmarkText
{
[self completeText:nil];
}
- (NSRange) selectedRange
{
return markedTextSelection;
}
- (NSRange) markedRange
{
NSRange range = NSMakeRange(0, [markedText length]);
if (!range.length)
range.location = NSNotFound;
return range;
}
- (BOOL) hasMarkedText
{
return [markedText length] > 0;
}
- (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
if (aRange.location >= [markedText length])
return nil;
aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
if (actualRange)
*actualRange = aRange;
return [markedText attributedSubstringFromRange:aRange];
}
- (NSArray*) validAttributesForMarkedText
{
return [NSArray array];
}
- (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
macdrv_query* query;
WineWindow* window = (WineWindow*)[self window];
NSRect ret;
aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
query = macdrv_create_query();
query->type = QUERY_IME_CHAR_RECT;
query->window = (macdrv_window)[window retain];
query->ime_char_rect.data = [window imeData];
query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
if ([window.queue query:query timeout:1])
{
aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
ret = NSRectFromCGRect(query->ime_char_rect.rect);
[[WineApplicationController sharedController] flipRect:&ret];
}
else
ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
macdrv_release_query(query);
if (actualRange)
*actualRange = aRange;
return ret;
}
- (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
{
return NSNotFound;
}
- (NSInteger) windowLevel
{
return [[self window] level];
}
@end
@implementation WineWindow
static WineWindow* causing_becomeKeyWindow;
@synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue;
@synthesize surface, surface_mutex;
@synthesize shape, shapeData, shapeChangedSinceLastDraw;
@synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
@synthesize usePerPixelAlpha;
@synthesize imeData, commandDone;
+ (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
windowFrame:(NSRect)window_frame
hwnd:(void*)hwnd
queue:(WineEventQueue*)queue
{
WineWindow* window;
WineContentView* contentView;
NSTrackingArea* trackingArea;
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[[WineApplicationController sharedController] flipRect:&window_frame];
window = [[[self alloc] initWithContentRect:window_frame
styleMask:style_mask_for_features(wf)
backing:NSBackingStoreBuffered
defer:YES] autorelease];
if (!window) return nil;
/* Standardize windows to eliminate differences between titled and
borderless windows and between NSWindow and NSPanel. */
[window setHidesOnDeactivate:NO];
[window setReleasedWhenClosed:NO];
[window setOneShot:YES];
[window disableCursorRects];
[window setShowsResizeIndicator:NO];
[window setHasShadow:wf->shadow];
[window setAcceptsMouseMovedEvents:YES];
[window setColorSpace:[NSColorSpace genericRGBColorSpace]];
[window setDelegate:window];
[window setAutodisplay:NO];
window.hwnd = hwnd;
window.queue = queue;
window->savedContentMinSize = NSZeroSize;
window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
window->resizable = wf->resizable;
window->_lastDisplayTime = [[NSDate distantPast] timeIntervalSinceReferenceDate];
[window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
(NSString*)kUTTypeContent,
nil]];
contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
if (!contentView)
return nil;
[contentView setAutoresizesSubviews:NO];
/* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
because they give us mouse moves in the background. */
trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
options:(NSTrackingMouseMoved |
NSTrackingActiveAlways |
NSTrackingInVisibleRect)
owner:window
userInfo:nil] autorelease];
if (!trackingArea)
return nil;
[contentView addTrackingArea:trackingArea];
[window setContentView:contentView];
[window setInitialFirstResponder:contentView];
[nc addObserver:window
selector:@selector(updateFullscreen)
name:NSApplicationDidChangeScreenParametersNotification
object:NSApp];
[window updateFullscreen];
[nc addObserver:window
selector:@selector(applicationWillHide)
name:NSApplicationWillHideNotification
object:NSApp];
[nc addObserver:window
selector:@selector(applicationDidUnhide)
name:NSApplicationDidUnhideNotification
object:NSApp];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:window
selector:@selector(checkWineDisplayLink)
name:NSWorkspaceActiveSpaceDidChangeNotification
object:[NSWorkspace sharedWorkspace]];
return window;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[queue release];
[latentChildWindows release];
[latentParentWindow release];
2013-01-15 03:23:58 +01:00
[shape release];
[shapeData release];
[super dealloc];
}
- (BOOL) preventResizing
{
BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor];
return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping);
}
- (BOOL) allowsMovingWithMaximized:(BOOL)inMaximized
{
if (allow_immovable_windows && (disabled || inMaximized))
return NO;
else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor])
return NO;
else
return YES;
}
- (void) adjustFeaturesForState
{
NSUInteger style = [self styleMask];
if (style & NSClosableWindowMask)
[[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
if (style & NSMiniaturizableWindowMask)
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
if (style & NSResizableWindowMask)
[[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
if ([self respondsToSelector:@selector(toggleFullScreen:)])
{
if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
[[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
}
if ([self preventResizing])
{
NSSize size = [self contentRectForFrameRect:[self frame]].size;
[self setContentMinSize:size];
[self setContentMaxSize:size];
}
else
{
[self setContentMaxSize:savedContentMaxSize];
[self setContentMinSize:savedContentMinSize];
}
if (allow_immovable_windows || cursor_clipping_locks_windows)
[self setMovable:[self allowsMovingWithMaximized:maximized]];
}
- (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
{
if ([self respondsToSelector:@selector(toggleFullScreen:)])
{
NSUInteger style = [self styleMask];
if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized)
{
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
}
else
{
behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
if (style & NSFullScreenWindowMask)
[super toggleFullScreen:nil];
}
}
if (behavior != [self collectionBehavior])
{
[self setCollectionBehavior:behavior];
[self adjustFeaturesForState];
}
}
- (void) setWindowFeatures:(const struct macdrv_window_features*)wf
{
static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
NSUInteger currentStyle = [self styleMask];
NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
if (newStyle != currentStyle)
{
NSString* title = [[[self title] copy] autorelease];
BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
{
// -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
// If transitioning from NSTitledWindowMask | NSResizableWindowMask to
// just NSTitledWindowMask, the window buttons should disappear rather
// than just being disabled. But they don't. Similarly in reverse.
// The workaround is to also toggle NSClosableWindowMask at the same time.
[self setStyleMask:newStyle ^ NSClosableWindowMask];
}
[self setStyleMask:newStyle];
// -setStyleMask: resets the firstResponder to the window. Set it
// back to the content view.
if ([[self contentView] acceptsFirstResponder])
[self makeFirstResponder:[self contentView]];
[self adjustFullScreenBehavior:[self collectionBehavior]];
if ([[self title] length] == 0 && [title length] > 0)
[self setTitle:title];
}
resizable = wf->resizable;
[self adjustFeaturesForState];
[self setHasShadow:wf->shadow];
}
// Indicates if the window would be visible if the app were not hidden.
- (BOOL) wouldBeVisible
{
return [NSApp isHidden] ? savedVisibleState : [self isVisible];
}
- (BOOL) isOrderedIn
{
return [self wouldBeVisible] || [self isMiniaturized];
}
- (NSInteger) minimumLevelForActive:(BOOL)active
{
NSInteger level;
if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
(topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
level = NSFloatingWindowLevel;
else
level = NSNormalWindowLevel;
if (active)
{
BOOL captured;
captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
if (captured || fullscreen)
{
if (captured)
level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
else
level = NSStatusWindowLevel + 1;
if (self.floating)
level++;
}
}
return level;
}
- (void) postDidUnminimizeEvent
{
macdrv_event* event;
/* Coalesce events by discarding any previous ones still in the queue. */
[queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
forWindow:self];
event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
[queue postEvent:event];
macdrv_release_event(event);
}
- (void) sendResizeStartQuery
{
macdrv_query* query = macdrv_create_query();
query->type = QUERY_RESIZE_START;
query->window = (macdrv_window)[self retain];
[self.queue query:query timeout:0.3];
macdrv_release_query(query);
}
- (void) setMacDrvState:(const struct macdrv_window_state*)state
{
NSWindowCollectionBehavior behavior;
self.disabled = state->disabled;
self.noActivate = state->no_activate;
if (self.floating != state->floating)
{
self.floating = state->floating;
if (state->floating)
{
// Became floating. If child of non-floating window, make that
// relationship latent.
WineWindow* parent = (WineWindow*)[self parentWindow];
if (parent && !parent.floating)
[self becameIneligibleChild];
}
else
{
// Became non-floating. If parent of floating children, make that
// relationship latent.
WineWindow* child;
for (child in [self childWineWindows])
{
if (child.floating)
[child becameIneligibleChild];
}
}
// Check our latent relationships. If floating status was the only
// reason they were latent, then make them active.
if ([self isVisible])
[self becameEligibleParentOrChild];
[[WineApplicationController sharedController] adjustWindowLevels];
}
if (state->minimized_valid)
{
macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE);
pendingMinimize = FALSE;
if (state->minimized && ![self isMiniaturized])
{
if ([self wouldBeVisible])
{
if ([self styleMask] & NSFullScreenWindowMask)
{
[self postDidUnminimizeEvent];
discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE);
}
else
{
[super miniaturize:nil];
discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
event_mask_for_type(WINDOW_GOT_FOCUS) |
event_mask_for_type(WINDOW_LOST_FOCUS);
}
}
else
pendingMinimize = TRUE;
}
else if (!state->minimized && [self isMiniaturized])
{
ignore_windowDeminiaturize = TRUE;
[self deminiaturize:nil];
discard |= event_mask_for_type(WINDOW_LOST_FOCUS);
}
if (discard)
[queue discardEventsMatchingMask:discard forWindow:self];
}
if (state->maximized != maximized)
{
maximized = state->maximized;
[self adjustFeaturesForState];
if (!maximized && [self inLiveResize])
[self sendResizeStartQuery];
}
behavior = NSWindowCollectionBehaviorDefault;
if (state->excluded_by_expose)
behavior |= NSWindowCollectionBehaviorTransient;
else
behavior |= NSWindowCollectionBehaviorManaged;
if (state->excluded_by_cycle)
{
behavior |= NSWindowCollectionBehaviorIgnoresCycle;
if ([self isOrderedIn])
[NSApp removeWindowsItem:self];
}
else
{
behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
if ([self isOrderedIn])
[NSApp addWindowsItem:self title:[self title] filename:NO];
}
[self adjustFullScreenBehavior:behavior];
}
- (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
{
BOOL reordered = FALSE;
if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
{
if ([self level] > [child level])
[child setLevel:[self level]];
[self addChildWindow:child ordered:NSWindowAbove];
[child checkWineDisplayLink];
[latentChildWindows removeObjectIdenticalTo:child];
child.latentParentWindow = nil;
reordered = TRUE;
}
else
{
if (!latentChildWindows)
latentChildWindows = [[NSMutableArray alloc] init];
if (![latentChildWindows containsObject:child])
[latentChildWindows addObject:child];
child.latentParentWindow = self;
}
return reordered;
}
- (BOOL) addChildWineWindow:(WineWindow*)child
{
return [self addChildWineWindow:child assumeVisible:FALSE];
}
- (void) removeChildWineWindow:(WineWindow*)child
{
[self removeChildWindow:child];
if (child.latentParentWindow == self)
child.latentParentWindow = nil;
[latentChildWindows removeObjectIdenticalTo:child];
}
- (BOOL) becameEligibleParentOrChild
{
BOOL reordered = FALSE;
NSUInteger count;
if (latentParentWindow.floating || !self.floating)
{
// If we aren't visible currently, we assume that we should be and soon
// will be. So, if the latent parent is visible that's enough to assume
// we can establish the parent-child relationship in Cocoa. That will
// actually make us visible, which is fine.
if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
reordered = TRUE;
}
// Here, though, we may not actually be visible yet and adding a child
// won't make us visible. The caller will have to call this method
// again after actually making us visible.
if ([self isVisible] && (count = [latentChildWindows count]))
{
NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
NSUInteger i;
for (i = 0; i < count; i++)
{
WineWindow* child = [latentChildWindows objectAtIndex:i];
if ([child isVisible] && (self.floating || !child.floating))
{
if (child.latentParentWindow == self)
{
if ([self level] > [child level])
[child setLevel:[self level]];
[self addChildWindow:child ordered:NSWindowAbove];
child.latentParentWindow = nil;
reordered = TRUE;
}
else
ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
[indexesToRemove addIndex:i];
}
}
[latentChildWindows removeObjectsAtIndexes:indexesToRemove];
}
return reordered;
}
- (void) becameIneligibleChild
{
WineWindow* parent = (WineWindow*)[self parentWindow];
if (parent)
{
if (!parent->latentChildWindows)
parent->latentChildWindows = [[NSMutableArray alloc] init];
[parent->latentChildWindows insertObject:self atIndex:0];
self.latentParentWindow = parent;
[parent removeChildWindow:self];
}
}
- (void) becameIneligibleParentOrChild
{
NSArray* childWindows = [self childWineWindows];
[self becameIneligibleChild];
if ([childWindows count])
{
WineWindow* child;
for (child in childWindows)
{
child.latentParentWindow = self;
[self removeChildWindow:child];
}
if (latentChildWindows)
[latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
else
latentChildWindows = [childWindows mutableCopy];
}
}
// Determine if, among Wine windows, this window is directly above or below
// a given other Wine window with no other Wine window intervening.
// Intervening non-Wine windows are ignored.
- (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
{
NSNumber* windowNumber;
NSNumber* otherWindowNumber;
NSArray* windowNumbers;
NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
if (![self isVisible] || ![otherWindow isVisible])
return FALSE;
windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
windowNumbers = [[self class] windowNumbersWithOptions:0];
windowIndex = [windowNumbers indexOfObject:windowNumber];
otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
return FALSE;
if (orderingMode == NSWindowAbove)
{
lowIndex = windowIndex;
highIndex = otherWindowIndex;
}
else if (orderingMode == NSWindowBelow)
{
lowIndex = otherWindowIndex;
highIndex = windowIndex;
}
else
return FALSE;
if (highIndex <= lowIndex)
return FALSE;
for (i = lowIndex + 1; i < highIndex; i++)
{
NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
if ([interveningWindow isKindOfClass:[WineWindow class]])
return FALSE;
}
return TRUE;
}
- (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
{
NSMutableArray* windowNumbers;
NSNumber* childWindowNumber;
NSUInteger otherIndex, limit;
NSArray* origChildren;
NSMutableArray* children;
// Get the z-order from the window server and modify it to reflect the
// requested window ordering.
windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
[windowNumbers removeObject:childWindowNumber];
otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
[windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
// Get our child windows and sort them in the reverse of the desired
// z-order (back-to-front).
origChildren = [self childWineWindows];
children = [[origChildren mutableCopy] autorelease];
[children sortWithOptions:NSSortStable
usingComparator:^NSComparisonResult(id obj1, id obj2){
NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]];
NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]];
NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
if (index1 == NSNotFound)
{
if (index2 == NSNotFound)
return NSOrderedSame;
else
return NSOrderedAscending;
}
else if (index2 == NSNotFound)
return NSOrderedDescending;
else if (index1 < index2)
return NSOrderedDescending;
else if (index2 < index1)
return NSOrderedAscending;
return NSOrderedSame;
}];
// If the current and desired children arrays match up to a point, leave
// those matching children alone.
limit = MIN([origChildren count], [children count]);
for (otherIndex = 0; otherIndex < limit; otherIndex++)
{
if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex])
break;
}
[children removeObjectsInRange:NSMakeRange(0, otherIndex)];
// Remove all of the child windows and re-add them back-to-front so they
// are in the desired order.
for (other in children)
[self removeChildWindow:other];
for (other in children)
[self addChildWindow:other ordered:NSWindowAbove];
}
/* Returns whether or not the window was ordered in, which depends on if
its frame intersects any screen. */
- (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
{
WineApplicationController* controller = [WineApplicationController sharedController];
if (![self isMiniaturized])
{
BOOL needAdjustWindowLevels = FALSE;
BOOL wasVisible;
[controller transformProcessToForeground];
[NSApp unhide:nil];
wasVisible = [self isVisible];
if (activate)
[NSApp activateIgnoringOtherApps:YES];
NSDisableScreenUpdates();
if ([self becameEligibleParentOrChild])
needAdjustWindowLevels = TRUE;
if (prev || next)
{
WineWindow* other = [prev isVisible] ? prev : next;
NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
if (![self isOrdered:orderingMode relativeTo:other])
{
WineWindow* parent = (WineWindow*)[self parentWindow];
WineWindow* otherParent = (WineWindow*)[other parentWindow];
// This window level may not be right for this window based
// on floating-ness, fullscreen-ness, etc. But we set it
// temporarily to allow us to order the windows properly.
// Then the levels get fixed by -adjustWindowLevels.
if ([self level] != [other level])
[self setLevel:[other level]];
[self orderWindow:orderingMode relativeTo:[other windowNumber]];
[self checkWineDisplayLink];
// The above call to -[NSWindow orderWindow:relativeTo:] won't
// reorder windows which are both children of the same parent
// relative to each other, so do that separately.
if (parent && parent == otherParent)
[parent order:orderingMode childWindow:self relativeTo:other];
needAdjustWindowLevels = TRUE;
}
}
else
{
// Again, temporarily set level to make sure we can order to
// the right place.
next = [controller frontWineWindow];
if (next && [self level] < [next level])
[self setLevel:[next level]];
[self orderFront:nil];
[self checkWineDisplayLink];
needAdjustWindowLevels = TRUE;
}
if ([self becameEligibleParentOrChild])
needAdjustWindowLevels = TRUE;
if (needAdjustWindowLevels)
{
if (!wasVisible && fullscreen && [self isOnActiveSpace])
[controller updateFullscreenWindows];
[controller adjustWindowLevels];
}
if (pendingMinimize)
{
[super miniaturize:nil];
pendingMinimize = FALSE;
}
NSEnableScreenUpdates();
/* Cocoa may adjust the frame when the window is ordered onto the screen.
Generate a frame-changed event just in case. The back end will ignore
it if nothing actually changed. */
[self windowDidResize:nil];
if (![self isExcludedFromWindowsMenu])
[NSApp addWindowsItem:self title:[self title] filename:NO];
}
}
- (void) doOrderOut
{
WineApplicationController* controller = [WineApplicationController sharedController];
BOOL wasVisible = [self isVisible];
BOOL wasOnActiveSpace = [self isOnActiveSpace];
if ([self isMiniaturized])
pendingMinimize = TRUE;
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
WineWindow* parent = (WineWindow*)self.parentWindow;
if ([parent isKindOfClass:[WineWindow class]])
[parent grabDockIconSnapshotFromWindow:self force:NO];
[self becameIneligibleParentOrChild];
if ([self isMiniaturized])
{
fakingClose = TRUE;
[self close];
fakingClose = FALSE;
}
else
[self orderOut:nil];
[self checkWineDisplayLink];
savedVisibleState = FALSE;
if (wasVisible && wasOnActiveSpace && fullscreen)
[controller updateFullscreenWindows];
[controller adjustWindowLevels];
[NSApp removeWindowsItem:self];
[queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
event_mask_for_type(WINDOW_GOT_FOCUS) |
event_mask_for_type(WINDOW_LOST_FOCUS) |
event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
event_mask_for_type(WINDOW_RESTORE_REQUESTED)
forWindow:self];
}
- (void) updateFullscreen
{
NSRect contentRect = [self contentRectForFrameRect:[self frame]];
BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
if (nowFullscreen != fullscreen)
{
WineApplicationController* controller = [WineApplicationController sharedController];
fullscreen = nowFullscreen;
if ([self isVisible] && [self isOnActiveSpace])
[controller updateFullscreenWindows];
[controller adjustWindowLevels];
}
}
- (void) setFrameFromWine:(NSRect)contentRect
{
/* Origin is (left, top) in a top-down space. Need to convert it to
(left, bottom) in a bottom-up space. */
[[WineApplicationController sharedController] flipRect:&contentRect];
/* The back end is establishing a new window size and position. It's
not interested in any stale events regarding those that may be sitting
in the queue. */
[queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
forWindow:self];
if (!NSIsEmptyRect(contentRect))
{
NSRect frame, oldFrame;
oldFrame = [self frame];
frame = [self frameRectForContentRect:contentRect];
if (!NSEqualRects(frame, oldFrame))
{
BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size);
BOOL needEnableScreenUpdates = FALSE;
if ([self preventResizing])
{
// Allow the following calls to -setFrame:display: to work even
// if they would violate the content size constraints. This
// shouldn't be necessary since the content size constraints are
// documented to not constrain that method, but it seems to be.
[self setContentMinSize:NSZeroSize];
[self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
}
if (equalSizes && [[self childWineWindows] count])
{
// If we change the window frame such that the origin moves
// but the size doesn't change, then Cocoa moves child
// windows with the parent. We don't want that so we fake
// a change of the size and then change it back.
NSRect bogusFrame = frame;
bogusFrame.size.width++;
NSDisableScreenUpdates();
needEnableScreenUpdates = TRUE;
ignore_windowResize = TRUE;
[self setFrame:bogusFrame display:NO];
ignore_windowResize = FALSE;
}
[self setFrame:frame display:YES];
if ([self preventResizing])
{
[self setContentMinSize:contentRect.size];
[self setContentMaxSize:contentRect.size];
}
if (needEnableScreenUpdates)
NSEnableScreenUpdates();
if (!equalSizes)
[self updateColorSpace];
if (!enteringFullScreen &&
[[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
nonFullscreenFrame = frame;
[self updateFullscreen];
if ([self isOrderedIn])
{
/* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
event. The back end will ignore it if nothing actually changed. */
[self windowDidResize:nil];
}
}
}
}
- (void) setMacDrvParentWindow:(WineWindow*)parent
{
WineWindow* oldParent = (WineWindow*)[self parentWindow];
if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
{
[oldParent removeChildWineWindow:self];
[latentParentWindow removeChildWineWindow:self];
if ([parent addChildWineWindow:self])
[[WineApplicationController sharedController] adjustWindowLevels];
}
}
- (void) setDisabled:(BOOL)newValue
{
if (disabled != newValue)
{
disabled = newValue;
[self adjustFeaturesForState];
}
}
2013-01-15 03:23:58 +01:00
- (BOOL) needsTransparency
{
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
return self.shape || self.colorKeyed || self.usePerPixelAlpha ||
(gl_surface_mode == GL_SURFACE_BEHIND && [[self.contentView valueForKeyPath:@"subviews.@max.hasGLContext"] boolValue]);
2013-01-15 03:23:58 +01:00
}
- (void) checkTransparency
{
if (![self isOpaque] && !self.needsTransparency)
{
self.shapeChangedSinceLastDraw = TRUE;
[[self contentView] setNeedsDisplay:YES];
2013-01-15 03:23:58 +01:00
[self setBackgroundColor:[NSColor windowBackgroundColor]];
[self setOpaque:YES];
}
else if ([self isOpaque] && self.needsTransparency)
{
self.shapeChangedSinceLastDraw = TRUE;
[[self contentView] setNeedsDisplay:YES];
2013-01-15 03:23:58 +01:00
[self setBackgroundColor:[NSColor clearColor]];
[self setOpaque:NO];
}
}
- (void) setShape:(NSBezierPath*)newShape
{
if (shape == newShape) return;
if (shape)
{
[[self contentView] setNeedsDisplayInRect:[shape bounds]];
[shape release];
}
if (newShape)
[[self contentView] setNeedsDisplayInRect:[newShape bounds]];
shape = [newShape copy];
self.shapeChangedSinceLastDraw = TRUE;
[self checkTransparency];
}
- (void) makeFocused:(BOOL)activate
{
if (activate)
{
[[WineApplicationController sharedController] transformProcessToForeground];
[NSApp activateIgnoringOtherApps:YES];
}
causing_becomeKeyWindow = self;
[self makeKeyWindow];
causing_becomeKeyWindow = nil;
[queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
event_mask_for_type(WINDOW_LOST_FOCUS)
forWindow:self];
}
- (void) postKey:(uint16_t)keyCode
pressed:(BOOL)pressed
modifiers:(NSUInteger)modifiers
event:(NSEvent*)theEvent
{
macdrv_event* event;
CGEventRef cgevent;
WineApplicationController* controller = [WineApplicationController sharedController];
event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
event->key.keycode = keyCode;
event->key.modifiers = modifiers;
event->key.time_ms = [controller ticksForEventTime:[theEvent timestamp]];
if ((cgevent = [theEvent CGEvent]))
{
CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
kCGKeyboardEventKeyboardType);
if (keyboardType != controller.keyboardType)
{
controller.keyboardType = keyboardType;
[controller keyboardSelectionDidChange];
}
}
[queue postEvent:event];
macdrv_release_event(event);
[controller noteKey:keyCode pressed:pressed];
}
- (void) postKeyEvent:(NSEvent *)theEvent
{
[self flagsChanged:theEvent];
[self postKey:[theEvent keyCode]
pressed:[theEvent type] == NSKeyDown
modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags])
event:theEvent];
}
- (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
{
savedContentMinSize = minSize;
savedContentMaxSize = maxSize;
if (![self preventResizing])
{
[self setContentMinSize:minSize];
[self setContentMaxSize:maxSize];
}
}
- (WineWindow*) ancestorWineWindow
{
WineWindow* ancestor = self;
for (;;)
{
WineWindow* parent = (WineWindow*)[ancestor parentWindow];
if ([parent isKindOfClass:[WineWindow class]])
ancestor = parent;
else
break;
}
return ancestor;
}
- (void) postBroughtForwardEvent
{
macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
[queue postEvent:event];
macdrv_release_event(event);
}
- (void) updateForCursorClipping
{
[self adjustFeaturesForState];
}
- (void) endWindowDragging
{
if (draggingPhase)
{
if (draggingPhase == 3)
{
macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, self);
[queue postEvent:event];
macdrv_release_event(event);
}
draggingPhase = 0;
[[WineApplicationController sharedController] window:self isBeingDragged:NO];
}
}
- (NSMutableDictionary*) displayIDToDisplayLinkMap
{
static NSMutableDictionary* displayIDToDisplayLinkMap;
if (!displayIDToDisplayLinkMap)
{
displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init];
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
object:NSApp
queue:nil
usingBlock:^(NSNotification *note){
NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys];
NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]];
[badDisplayIDs minusSet:validDisplayIDs];
[displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]];
}];
}
return displayIDToDisplayLinkMap;
}
- (WineDisplayLink*) wineDisplayLink
{
if (!_lastDisplayID)
return nil;
NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
return [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
}
- (void) checkWineDisplayLink
{
NSScreen* screen = self.screen;
if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped])
screen = nil;
#if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible))
screen = nil;
#endif
NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"];
CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue];
if (displayID == _lastDisplayID)
return;
NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
if (_lastDisplayID)
{
WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
[link removeWindow:self];
}
if (displayID)
{
WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber];
if (!link)
{
link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease];
[displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber];
}
[link addWindow:self];
[self displayIfNeeded];
}
_lastDisplayID = displayID;
}
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
- (BOOL) isEmptyShaped
{
return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
}
- (BOOL) canProvideSnapshot
{
return (self.windowNumber > 0 && ![self isEmptyShaped]);
}
- (void) grabDockIconSnapshotFromWindow:(WineWindow*)window force:(BOOL)force
{
if (![self isEmptyShaped])
return;
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if (!force && now < lastDockIconSnapshot + 1)
return;
if (window)
{
if (![window canProvideSnapshot])
return;
}
else
{
CGFloat bestArea;
for (WineWindow* childWindow in self.childWindows)
{
if (![childWindow isKindOfClass:[WineWindow class]] || ![childWindow canProvideSnapshot])
continue;
NSSize size = childWindow.frame.size;
CGFloat area = size.width * size.height;
if (!window || area > bestArea)
{
window = childWindow;
bestArea = area;
}
}
if (!window)
return;
}
const void* windowID = (const void*)(CGWindowID)window.windowNumber;
CFArrayRef windowIDs = CFArrayCreate(NULL, &windowID, 1, NULL);
CGImageRef windowImage = CGWindowListCreateImageFromArray(CGRectNull, windowIDs, kCGWindowImageBoundsIgnoreFraming);
CFRelease(windowIDs);
if (!windowImage)
return;
NSImage* appImage = [NSApp applicationIconImage];
if (!appImage)
appImage = [NSImage imageNamed:NSImageNameApplicationIcon];
NSImage* dockIcon = [[[NSImage alloc] initWithSize:NSMakeSize(256, 256)] autorelease];
[dockIcon lockFocus];
CGContextRef cgcontext = [[NSGraphicsContext currentContext] graphicsPort];
CGRect rect = CGRectMake(8, 8, 240, 240);
size_t width = CGImageGetWidth(windowImage);
size_t height = CGImageGetHeight(windowImage);
if (width > height)
{
rect.size.height *= height / (double)width;
rect.origin.y += (CGRectGetWidth(rect) - CGRectGetHeight(rect)) / 2;
}
else if (width != height)
{
rect.size.width *= width / (double)height;
rect.origin.x += (CGRectGetHeight(rect) - CGRectGetWidth(rect)) / 2;
}
CGContextDrawImage(cgcontext, rect, windowImage);
[appImage drawInRect:NSMakeRect(156, 4, 96, 96)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1
respectFlipped:YES
hints:nil];
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
[dockIcon unlockFocus];
CGImageRelease(windowImage);
NSImageView* imageView = (NSImageView*)self.dockTile.contentView;
if (![imageView isKindOfClass:[NSImageView class]])
{
imageView = [[[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)] autorelease];
imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
self.dockTile.contentView = imageView;
}
imageView.image = dockIcon;
[self.dockTile display];
lastDockIconSnapshot = now;
}
- (void) checkEmptyShaped
{
if (self.dockTile.contentView && ![self isEmptyShaped])
{
self.dockTile.contentView = nil;
lastDockIconSnapshot = 0;
}
[self checkWineDisplayLink];
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
}
/*
* ---------- NSWindow method overrides ----------
*/
- (BOOL) canBecomeKeyWindow
{
if (causing_becomeKeyWindow == self) return YES;
if (self.disabled || self.noActivate) return NO;
return [self isKeyWindow];
}
- (BOOL) canBecomeMainWindow
{
return [self canBecomeKeyWindow];
}
- (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
{
// If a window is sized to completely cover a screen, then it's in
// full-screen mode. In that case, we don't allow NSWindow to constrain
// it.
NSArray* screens = [NSScreen screens];
NSRect contentRect = [self contentRectForFrameRect:frameRect];
if (!screen_covered_by_rect(contentRect, screens) &&
frame_intersects_screens(frameRect, screens))
frameRect = [super constrainFrameRect:frameRect toScreen:screen];
return frameRect;
}
// This private method of NSWindow is called as Cocoa reacts to the display
// configuration changing. Among other things, it adjusts the window's
// frame based on how the screen(s) changed size. That tells Wine that the
// window has been moved. We don't want that. Rather, we want to make
// sure that the WinAPI notion of the window position is maintained/
// restored, possibly undoing or overriding Cocoa's adjustment.
//
// So, we queue a REASSERT_WINDOW_POSITION event to the back end before
// Cocoa has a chance to adjust the frame, thus preceding any resulting
// WINDOW_FRAME_CHANGED event that may get queued. The back end will
// reassert its notion of the position. That call won't get processed
// until after this method returns, so it will override whatever this
// method does to the window position. It will also discard any pending
// WINDOW_FRAME_CHANGED events.
//
// Unfortunately, the only way I've found to know when Cocoa is _about to_
// adjust the window's position due to a display change is to hook into
// this private method. This private method has remained stable from 10.6
// through 10.11. If it does change, the most likely thing is that it
// will be removed and no longer called and this fix will simply stop
// working. The only real danger would be if Apple changed the return type
// to a struct or floating-point type, which would change the calling
// convention.
- (id) _displayChanged
{
macdrv_event* event = macdrv_create_event(REASSERT_WINDOW_POSITION, self);
[queue postEvent:event];
macdrv_release_event(event);
return [super _displayChanged];
}
- (BOOL) isExcludedFromWindowsMenu
{
return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
}
- (BOOL) validateMenuItem:(NSMenuItem *)menuItem
{
BOOL ret = [super validateMenuItem:menuItem];
if ([menuItem action] == @selector(makeKeyAndOrderFront:))
ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized))
ret = NO;
return ret;
}
/* We don't call this. It's the action method of the items in the Window menu. */
- (void) makeKeyAndOrderFront:(id)sender
{
if ([self isMiniaturized])
[self deminiaturize:nil];
[self orderBelow:nil orAbove:nil activate:NO];
[[self ancestorWineWindow] postBroughtForwardEvent];
if (![self isKeyWindow] && !self.disabled && !self.noActivate)
[[WineApplicationController sharedController] windowGotFocus:self];
}
- (void) sendEvent:(NSEvent*)event
{
NSEventType type = event.type;
/* NSWindow consumes certain key-down events as part of Cocoa's keyboard
interface control. For example, Control-Tab switches focus among
views. We want to bypass that feature, so directly route key-down
events to -keyDown:. */
if (type == NSKeyDown)
[[self firstResponder] keyDown:event];
else
{
if (!draggingPhase && maximized && ![self isMovable] &&
![self allowsMovingWithMaximized:YES] && [self allowsMovingWithMaximized:NO] &&
type == NSLeftMouseDown && (self.styleMask & NSTitledWindowMask))
{
NSRect titleBar = self.frame;
NSRect contentRect = [self contentRectForFrameRect:titleBar];
titleBar.size.height = NSMaxY(titleBar) - NSMaxY(contentRect);
titleBar.origin.y = NSMaxY(contentRect);
dragStartPosition = [self convertBaseToScreen:event.locationInWindow];
if (NSMouseInRect(dragStartPosition, titleBar, NO))
{
static const NSWindowButton buttons[] = {
NSWindowCloseButton,
NSWindowMiniaturizeButton,
NSWindowZoomButton,
NSWindowFullScreenButton,
};
BOOL hitButton = NO;
int i;
for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++)
{
NSButton* button;
if (buttons[i] == NSWindowFullScreenButton && ![self respondsToSelector:@selector(toggleFullScreen:)])
continue;
button = [self standardWindowButton:buttons[i]];
if ([button hitTest:[button.superview convertPoint:event.locationInWindow fromView:nil]])
{
hitButton = YES;
break;
}
}
if (!hitButton)
{
draggingPhase = 1;
dragWindowStartPosition = NSMakePoint(NSMinX(titleBar), NSMaxY(titleBar));
[[WineApplicationController sharedController] window:self isBeingDragged:YES];
}
}
}
else if (draggingPhase && (type == NSLeftMouseDragged || type == NSLeftMouseUp))
{
if ([self isMovable])
{
NSPoint point = [self convertBaseToScreen:event.locationInWindow];
NSPoint newTopLeft = dragWindowStartPosition;
newTopLeft.x += point.x - dragStartPosition.x;
newTopLeft.y += point.y - dragStartPosition.y;
if (draggingPhase == 2)
{
macdrv_event* event = macdrv_create_event(WINDOW_DRAG_BEGIN, self);
[queue postEvent:event];
macdrv_release_event(event);
draggingPhase = 3;
}
[self setFrameTopLeftPoint:newTopLeft];
}
else if (draggingPhase == 1 && type == NSLeftMouseDragged)
{
macdrv_event* event;
NSRect frame = [self contentRectForFrameRect:self.frame];
[[WineApplicationController sharedController] flipRect:&frame];
event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
event->window_restore_requested.keep_frame = TRUE;
event->window_restore_requested.frame = NSRectToCGRect(frame);
[queue postEvent:event];
macdrv_release_event(event);
draggingPhase = 2;
}
if (type == NSLeftMouseUp)
[self endWindowDragging];
}
[super sendEvent:event];
}
}
- (void) miniaturize:(id)sender
{
macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
[queue postEvent:event];
macdrv_release_event(event);
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
WineWindow* parent = (WineWindow*)self.parentWindow;
if ([parent isKindOfClass:[WineWindow class]])
[parent grabDockIconSnapshotFromWindow:self force:YES];
}
- (void) toggleFullScreen:(id)sender
{
if (!self.disabled && !maximized)
[super toggleFullScreen:sender];
}
- (void) setViewsNeedDisplay:(BOOL)value
{
if (value && ![self viewsNeedDisplay])
{
WineDisplayLink* link = [self wineDisplayLink];
if (link)
{
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if (_lastDisplayTime + [link refreshPeriod] < now)
[self setAutodisplay:YES];
else
{
[link start];
_lastDisplayTime = now;
}
}
}
[super setViewsNeedDisplay:value];
}
- (void) display
{
_lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
[super display];
[self setAutodisplay:NO];
}
- (void) displayIfNeeded
{
_lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
[super displayIfNeeded];
[self setAutodisplay:NO];
}
- (NSArray*) childWineWindows
{
NSArray* childWindows = self.childWindows;
NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){
return [child isKindOfClass:[WineWindow class]];
}];
return [childWindows objectsAtIndexes:indexes];
}
// We normally use the generic/calibrated RGB color space for the window,
// rather than the device color space, to avoid expensive color conversion
// which slows down drawing. However, for windows displaying OpenGL, having
// a different color space than the screen greatly reduces frame rates, often
// limiting it to the display refresh rate.
//
// To avoid this, we switch back to the screen color space whenever the
// window is covered by a view with an attached OpenGL context.
- (void) updateColorSpace
{
NSRect contentRect = [[self contentView] frame];
BOOL coveredByGLView = FALSE;
for (WineContentView* view in [[self contentView] subviews])
{
if ([view hasGLContext])
{
NSRect frame = [view convertRect:[view bounds] toView:nil];
if (NSContainsRect(frame, contentRect))
{
coveredByGLView = TRUE;
break;
}
}
}
if (coveredByGLView)
[self setColorSpace:nil];
else
[self setColorSpace:[NSColorSpace genericRGBColorSpace]];
}
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
- (void) updateForGLSubviews
{
[self updateColorSpace];
if (gl_surface_mode == GL_SURFACE_BEHIND)
[self checkTransparency];
}
/*
* ---------- NSResponder method overrides ----------
*/
- (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
- (void) flagsChanged:(NSEvent *)theEvent
{
static const struct {
NSUInteger mask;
uint16_t keycode;
} modifiers[] = {
{ NX_ALPHASHIFTMASK, kVK_CapsLock },
{ NX_DEVICELSHIFTKEYMASK, kVK_Shift },
{ NX_DEVICERSHIFTKEYMASK, kVK_RightShift },
{ NX_DEVICELCTLKEYMASK, kVK_Control },
{ NX_DEVICERCTLKEYMASK, kVK_RightControl },
{ NX_DEVICELALTKEYMASK, kVK_Option },
{ NX_DEVICERALTKEYMASK, kVK_RightOption },
{ NX_DEVICELCMDKEYMASK, kVK_Command },
{ NX_DEVICERCMDKEYMASK, kVK_RightCommand },
};
NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]);
NSUInteger changed;
int i, last_changed;
fix_device_modifiers_by_generic(&modifierFlags);
changed = modifierFlags ^ lastModifierFlags;
last_changed = -1;
for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
if (changed & modifiers[i].mask)
last_changed = i;
for (i = 0; i <= last_changed; i++)
{
if (changed & modifiers[i].mask)
{
BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
if (i == last_changed)
lastModifierFlags = modifierFlags;
else
{
lastModifierFlags ^= modifiers[i].mask;
fix_generic_modifiers_by_device(&lastModifierFlags);
}
// Caps lock generates one event for each press-release action.
// We need to simulate a pair of events for each actual event.
if (modifiers[i].mask == NX_ALPHASHIFTMASK)
{
[self postKey:modifiers[i].keycode
pressed:TRUE
modifiers:lastModifierFlags
event:(NSEvent*)theEvent];
pressed = FALSE;
}
[self postKey:modifiers[i].keycode
pressed:pressed
modifiers:lastModifierFlags
event:(NSEvent*)theEvent];
}
}
}
- (void) applicationWillHide
{
savedVisibleState = [self isVisible];
}
- (void) applicationDidUnhide
{
if ([self isVisible])
[self becameEligibleParentOrChild];
}
/*
* ---------- NSWindowDelegate methods ----------
*/
- (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
{
macdrv_query* query;
NSSize size;
query = macdrv_create_query();
query->type = QUERY_MIN_MAX_INFO;
query->window = (macdrv_window)[self retain];
[self.queue query:query timeout:0.5];
macdrv_release_query(query);
size = [self contentMaxSize];
if (proposedSize.width < size.width)
size.width = proposedSize.width;
if (proposedSize.height < size.height)
size.height = proposedSize.height;
return size;
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
WineApplicationController* controller = [WineApplicationController sharedController];
NSEvent* event = [controller lastFlagsChanged];
if (event)
[self flagsChanged:event];
if (causing_becomeKeyWindow == self) return;
[controller windowGotFocus:self];
}
- (void) windowDidChangeOcclusionState:(NSNotification*)notification
{
[self checkWineDisplayLink];
}
- (void) windowDidChangeScreen:(NSNotification*)notification
{
[self checkWineDisplayLink];
}
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
WineApplicationController* controller = [WineApplicationController sharedController];
if (!ignore_windowDeminiaturize)
[self postDidUnminimizeEvent];
ignore_windowDeminiaturize = FALSE;
[self becameEligibleParentOrChild];
if (fullscreen && [self isOnActiveSpace])
[controller updateFullscreenWindows];
[controller adjustWindowLevels];
if (![self parentWindow])
[self postBroughtForwardEvent];
if (!self.disabled && !self.noActivate)
{
causing_becomeKeyWindow = self;
[self makeKeyWindow];
causing_becomeKeyWindow = nil;
[controller windowGotFocus:self];
}
[self windowDidResize:notification];
[self checkWineDisplayLink];
}
- (void) windowDidEndLiveResize:(NSNotification *)notification
{
if (!maximized)
{
macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
[queue postEvent:event];
macdrv_release_event(event);
}
}
- (void) windowDidEnterFullScreen:(NSNotification*)notification
{
enteringFullScreen = FALSE;
enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
}
- (void) windowDidExitFullScreen:(NSNotification*)notification
{
exitingFullScreen = FALSE;
[self setFrame:nonFullscreenFrame display:YES animate:NO];
[self windowDidResize:nil];
}
- (void) windowDidFailToEnterFullScreen:(NSWindow*)window
{
enteringFullScreen = FALSE;
enteredFullScreenTime = 0;
}
- (void) windowDidFailToExitFullScreen:(NSWindow*)window
{
exitingFullScreen = FALSE;
[self windowDidResize:nil];
}
- (void)windowDidMiniaturize:(NSNotification *)notification
{
if (fullscreen && [self isOnActiveSpace])
[[WineApplicationController sharedController] updateFullscreenWindows];
[self checkWineDisplayLink];
}
- (void)windowDidMove:(NSNotification *)notification
{
[self windowDidResize:notification];
}
- (void)windowDidResignKey:(NSNotification *)notification
{
macdrv_event* event;
if (causing_becomeKeyWindow) return;
event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
[queue postEvent:event];
macdrv_release_event(event);
}
- (void)windowDidResize:(NSNotification *)notification
{
macdrv_event* event;
NSRect frame = [self frame];
if ([self inLiveResize])
{
if (NSMinX(frame) != NSMinX(frameAtResizeStart))
resizingFromLeft = TRUE;
if (NSMaxY(frame) != NSMaxY(frameAtResizeStart))
resizingFromTop = TRUE;
}
frame = [self contentRectForFrameRect:frame];
if (ignore_windowResize || exitingFullScreen) return;
if ([self preventResizing])
{
[self setContentMinSize:frame.size];
[self setContentMaxSize:frame.size];
}
[[WineApplicationController sharedController] flipRect:&frame];
/* Coalesce events by discarding any previous ones still in the queue. */
[queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
forWindow:self];
event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
event->window_frame_changed.frame = NSRectToCGRect(frame);
event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0;
event->window_frame_changed.in_resize = [self inLiveResize];
[queue postEvent:event];
macdrv_release_event(event);
[[[self contentView] inputContext] invalidateCharacterCoordinates];
[self updateFullscreen];
}
- (BOOL)windowShouldClose:(id)sender
{
macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
[queue postEvent:event];
macdrv_release_event(event);
return NO;
}
- (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
{
if (maximized)
{
macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
[queue postEvent:event];
macdrv_release_event(event);
return NO;
}
else if (!resizable)
{
macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self);
[queue postEvent:event];
macdrv_release_event(event);
return NO;
}
return YES;
}
- (void) windowWillClose:(NSNotification*)notification
{
WineWindow* child;
if (fakingClose) return;
if (latentParentWindow)
{
[latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
self.latentParentWindow = nil;
}
for (child in latentChildWindows)
{
if (child.latentParentWindow == self)
child.latentParentWindow = nil;
}
[latentChildWindows removeAllObjects];
}
- (void) windowWillEnterFullScreen:(NSNotification*)notification
{
enteringFullScreen = TRUE;
nonFullscreenFrame = [self frame];
}
- (void) windowWillExitFullScreen:(NSNotification*)notification
{
exitingFullScreen = TRUE;
}
- (void)windowWillMiniaturize:(NSNotification *)notification
{
[self becameIneligibleParentOrChild];
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
[self grabDockIconSnapshotFromWindow:nil force:NO];
}
- (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
{
if ([self inLiveResize])
{
if (maximized)
return self.frame.size;
NSRect rect;
macdrv_query* query;
rect = [self frame];
if (resizingFromLeft)
rect.origin.x = NSMaxX(rect) - frameSize.width;
if (!resizingFromTop)
rect.origin.y = NSMaxY(rect) - frameSize.height;
rect.size = frameSize;
rect = [self contentRectForFrameRect:rect];
[[WineApplicationController sharedController] flipRect:&rect];
query = macdrv_create_query();
query->type = QUERY_RESIZE_SIZE;
query->window = (macdrv_window)[self retain];
query->resize_size.rect = NSRectToCGRect(rect);
query->resize_size.from_left = resizingFromLeft;
query->resize_size.from_top = resizingFromTop;
if ([self.queue query:query timeout:0.1])
{
rect = NSRectFromCGRect(query->resize_size.rect);
rect = [self frameRectForContentRect:rect];
frameSize = rect.size;
}
macdrv_release_query(query);
}
return frameSize;
}
- (void) windowWillStartLiveResize:(NSNotification *)notification
{
[self endWindowDragging];
if (maximized)
{
macdrv_event* event;
NSRect frame = [self contentRectForFrameRect:self.frame];
[[WineApplicationController sharedController] flipRect:&frame];
event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
event->window_restore_requested.keep_frame = TRUE;
event->window_restore_requested.frame = NSRectToCGRect(frame);
[queue postEvent:event];
macdrv_release_event(event);
}
else
[self sendResizeStartQuery];
frameAtResizeStart = [self frame];
resizingFromLeft = resizingFromTop = FALSE;
}
- (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
{
macdrv_query* query;
NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
NSSize maxSize;
query = macdrv_create_query();
query->type = QUERY_MIN_MAX_INFO;
query->window = (macdrv_window)[self retain];
[self.queue query:query timeout:0.5];
macdrv_release_query(query);
currentContentRect = [self contentRectForFrameRect:[self frame]];
proposedContentRect = [self contentRectForFrameRect:proposedFrame];
maxSize = [self contentMaxSize];
newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
// Try to keep the top-left corner where it is.
newContentRect.origin.x = NSMinX(currentContentRect);
newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
// If that pushes the bottom or right off the screen, pull it up and to the left.
screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
if (NSMaxX(newContentRect) > NSMaxX(screenRect))
newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
if (NSMinY(newContentRect) < NSMinY(screenRect))
newContentRect.origin.y = NSMinY(screenRect);
// If that pushes the top or left off the screen, push it down and the right
// again. Do this last because the top-left corner is more important than the
// bottom-right.
if (NSMinX(newContentRect) < NSMinX(screenRect))
newContentRect.origin.x = NSMinX(screenRect);
if (NSMaxY(newContentRect) > NSMaxY(screenRect))
newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
return [self frameRectForContentRect:newContentRect];
}
/*
* ---------- NSPasteboardOwner methods ----------
*/
- (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
{
macdrv_query* query = macdrv_create_query();
query->type = QUERY_PASTEBOARD_DATA;
query->window = (macdrv_window)[self retain];
query->pasteboard_data.type = (CFStringRef)[type copy];
[self.queue query:query timeout:3];
macdrv_release_query(query);
}
/*
* ---------- NSDraggingDestination methods ----------
*/
- (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
{
return [self draggingUpdated:sender];
}
- (void) draggingExited:(id <NSDraggingInfo>)sender
{
// This isn't really a query. We don't need any response. However, it
// has to be processed in a similar manner as the other drag-and-drop
// queries in order to maintain the proper order of operations.
macdrv_query* query = macdrv_create_query();
query->type = QUERY_DRAG_EXITED;
query->window = (macdrv_window)[self retain];
[self.queue query:query timeout:0.1];
macdrv_release_query(query);
}
- (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
{
NSDragOperation ret;
NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
NSPasteboard* pb = [sender draggingPasteboard];
macdrv_query* query = macdrv_create_query();
query->type = QUERY_DRAG_OPERATION;
query->window = (macdrv_window)[self retain];
query->drag_operation.x = pt.x;
query->drag_operation.y = pt.y;
query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
query->drag_operation.accepted_op = NSDragOperationNone;
query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
[self.queue query:query timeout:3];
ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
macdrv_release_query(query);
return ret;
}
- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
BOOL ret;
NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
NSPasteboard* pb = [sender draggingPasteboard];
macdrv_query* query = macdrv_create_query();
query->type = QUERY_DRAG_DROP;
query->window = (macdrv_window)[self retain];
query->drag_drop.x = pt.x;
query->drag_drop.y = pt.y;
query->drag_drop.op = [sender draggingSourceOperationMask];
query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
[self.queue query:query timeout:3 * 60 processEvents:YES];
ret = query->status;
macdrv_release_query(query);
return ret;
}
- (BOOL) wantsPeriodicDraggingUpdates
{
return NO;
}
@end
/***********************************************************************
* macdrv_create_cocoa_window
*
* Create a Cocoa window with the given content frame and features (e.g.
* title bar, close box, etc.).
*/
macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
CGRect frame, void* hwnd, macdrv_event_queue queue)
{
__block WineWindow* window;
OnMainThread(^{
window = [[WineWindow createWindowWithFeatures:wf
windowFrame:NSRectFromCGRect(frame)
hwnd:hwnd
queue:(WineEventQueue*)queue] retain];
});
return (macdrv_window)window;
}
/***********************************************************************
* macdrv_destroy_cocoa_window
*
* Destroy a Cocoa window.
*/
void macdrv_destroy_cocoa_window(macdrv_window w)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window doOrderOut];
[window close];
});
[window.queue discardEventsMatchingMask:-1 forWindow:window];
[window release];
[pool release];
}
/***********************************************************************
* macdrv_get_window_hwnd
*
* Get the hwnd that was set for the window at creation.
*/
void* macdrv_get_window_hwnd(macdrv_window w)
{
WineWindow* window = (WineWindow*)w;
return window.hwnd;
}
/***********************************************************************
* macdrv_set_cocoa_window_features
*
* Update a Cocoa window's features.
*/
void macdrv_set_cocoa_window_features(macdrv_window w,
const struct macdrv_window_features* wf)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window setWindowFeatures:wf];
});
}
/***********************************************************************
* macdrv_set_cocoa_window_state
*
* Update a Cocoa window's state.
*/
void macdrv_set_cocoa_window_state(macdrv_window w,
const struct macdrv_window_state* state)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window setMacDrvState:state];
});
}
/***********************************************************************
* macdrv_set_cocoa_window_title
*
* Set a Cocoa window's title.
*/
void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
size_t length)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
NSString* titleString;
if (title)
titleString = [NSString stringWithCharacters:title length:length];
else
titleString = @"";
OnMainThreadAsync(^{
[window setTitle:titleString];
if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
[NSApp changeWindowsItem:window title:titleString filename:NO];
});
[pool release];
}
/***********************************************************************
* macdrv_order_cocoa_window
*
* Reorder a Cocoa window relative to other windows. If prev is
* non-NULL, it is ordered below that window. Else, if next is non-NULL,
* it is ordered above that window. Otherwise, it is ordered to the
* front.
*/
void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p,
macdrv_window n, int activate)
{
WineWindow* window = (WineWindow*)w;
WineWindow* prev = (WineWindow*)p;
WineWindow* next = (WineWindow*)n;
OnMainThreadAsync(^{
[window orderBelow:prev
orAbove:next
activate:activate];
});
[window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
forWindow:window];
[next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
forWindow:next];
}
/***********************************************************************
* macdrv_hide_cocoa_window
*
* Hides a Cocoa window.
*/
void macdrv_hide_cocoa_window(macdrv_window w)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window doOrderOut];
});
}
/***********************************************************************
* macdrv_set_cocoa_window_frame
*
* Move a Cocoa window.
*/
void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window setFrameFromWine:NSRectFromCGRect(*new_frame)];
});
}
/***********************************************************************
* macdrv_get_cocoa_window_frame
*
* Gets the frame of a Cocoa window.
*/
void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
NSRect frame;
frame = [window contentRectForFrameRect:[window frame]];
[[WineApplicationController sharedController] flipRect:&frame];
*out_frame = NSRectToCGRect(frame);
});
}
/***********************************************************************
* macdrv_set_cocoa_parent_window
*
* Sets the parent window for a Cocoa window. If parent is NULL, clears
* the parent window.
*/
void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window setMacDrvParentWindow:(WineWindow*)parent];
});
}
/***********************************************************************
* macdrv_set_window_surface
*/
void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
window.surface = surface;
window.surface_mutex = mutex;
});
[pool release];
}
/***********************************************************************
* macdrv_window_needs_display
*
* Mark a window as needing display in a specified rect (in non-client
* area coordinates).
*/
void macdrv_window_needs_display(macdrv_window w, CGRect rect)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThreadAsync(^{
[[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
});
[pool release];
}
2013-01-15 03:23:58 +01:00
/***********************************************************************
* macdrv_set_window_shape
*
* Sets the shape of a Cocoa window from an array of rectangles. If
* rects is NULL, resets the window's shape to its frame.
*/
void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
if (!rects || !count)
{
2013-01-15 03:23:58 +01:00
window.shape = nil;
window.shapeData = nil;
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
[window checkEmptyShaped];
}
2013-01-15 03:23:58 +01:00
else
{
size_t length = sizeof(*rects) * count;
if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
{
NSBezierPath* path;
unsigned int i;
2013-01-15 03:23:58 +01:00
path = [NSBezierPath bezierPath];
for (i = 0; i < count; i++)
[path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
window.shape = path;
window.shapeData = [NSData dataWithBytes:rects length:length];
winemac: Use a snapshot of an owned window when a zero-sized owner window is minimized. Some apps create a zero-sized window as their "main" window and then create all of the other top-level windows as owned windows with that main window as the owner. The user interacts with these owned windows. When the user attempts to minimize one of these owned windows, the app instead minimizes the zero-sized owner window. When an owner window is minimized, all of its owned windows are hidden. The Mac driver faithfully carries out these window operations. The only visible windows are hidden and the zero-sized window is minimized. This results in an invisible animation of the window down to a slot in the Dock - a slot which appears mostly empty. The invisible window thumbnail is badged with the app icon, but it still looks strange. On Windows, the Alt-Tab switcher uses the image of the owned window to represent the zero-sized owner. This commit attempts to do something similar. It takes over drawing of the Dock icon for minimized, zero-sized window. It grabs a snapshot of one of the owned windows and draws the app badge onto it. Since the owned windows are hidden before the zero-sized owner is minimized and we can't take snapshots of hidden windows, we use heuristics to guess when it may be useful to grab the snapshot. If the user minimizes an owned window from the Cocoa side, we grab that window's snapshot. If an owned window is being hidden and no snapshot has been taken recently, we grab its snapshot on the theory that this may be the beginning of hiding all of the owned windows before minimizing the owner. Unfortunately, this doesn't address the invisible animations when minimizing and unminimizing the zero-sized owner window. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2015-10-23 09:48:34 +02:00
[window checkEmptyShaped];
}
2013-01-15 03:23:58 +01:00
}
});
[pool release];
}
/***********************************************************************
* macdrv_set_window_alpha
*/
void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
[window setAlphaValue:alpha];
[pool release];
}
/***********************************************************************
* macdrv_set_window_color_key
*/
void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
CGFloat keyBlue)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
window.colorKeyed = TRUE;
window.colorKeyRed = keyRed;
window.colorKeyGreen = keyGreen;
window.colorKeyBlue = keyBlue;
[window checkTransparency];
});
[pool release];
}
/***********************************************************************
* macdrv_clear_window_color_key
*/
void macdrv_clear_window_color_key(macdrv_window w)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
window.colorKeyed = FALSE;
[window checkTransparency];
});
[pool release];
}
/***********************************************************************
* macdrv_window_use_per_pixel_alpha
*/
void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
window.usePerPixelAlpha = use_per_pixel_alpha;
[window checkTransparency];
});
[pool release];
}
/***********************************************************************
* macdrv_give_cocoa_window_focus
*
* Makes the Cocoa window "key" (gives it keyboard focus). This also
* orders it front and, if its frame was not within the desktop bounds,
* Cocoa will typically move it on-screen.
*/
void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window makeFocused:activate];
});
}
2013-03-06 11:59:07 +01:00
/***********************************************************************
* macdrv_set_window_min_max_sizes
*
* Sets the window's minimum and maximum content sizes.
*/
void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
{
WineWindow* window = (WineWindow*)w;
OnMainThread(^{
[window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)];
});
}
2013-03-06 11:59:07 +01:00
/***********************************************************************
* macdrv_create_view
*
* Creates and returns a view in the specified rect of the window. The
* caller is responsible for calling macdrv_dispose_view() on the view
* when it is done with it.
*/
macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineWindow* window = (WineWindow*)w;
__block WineContentView* view;
if (CGRectIsNull(rect)) rect = CGRectZero;
OnMainThread(^{
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
[view setAutoresizesSubviews:NO];
[nc addObserver:view
selector:@selector(updateGLContexts)
name:NSViewGlobalFrameDidChangeNotification
object:view];
[nc addObserver:view
selector:@selector(updateGLContexts)
name:NSApplicationDidChangeScreenParametersNotification
object:NSApp];
[[window contentView] addSubview:view];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
[window updateForGLSubviews];
2013-03-06 11:59:07 +01:00
});
[pool release];
return (macdrv_view)view;
}
/***********************************************************************
* macdrv_dispose_view
*
* Destroys a view previously returned by macdrv_create_view.
*/
void macdrv_dispose_view(macdrv_view v)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineContentView* view = (WineContentView*)v;
OnMainThread(^{
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
WineWindow* window = (WineWindow*)[view window];
2013-03-06 11:59:07 +01:00
[nc removeObserver:view
name:NSViewGlobalFrameDidChangeNotification
object:view];
[nc removeObserver:view
name:NSApplicationDidChangeScreenParametersNotification
object:NSApp];
[view removeFromSuperview];
[view release];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
[window updateForGLSubviews];
2013-03-06 11:59:07 +01:00
});
[pool release];
}
/***********************************************************************
* macdrv_set_view_window_and_frame
*
* Move a view to a new window and/or position within its window. If w
* is NULL, leave the view in its current window and just change its
* frame.
*/
void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineContentView* view = (WineContentView*)v;
WineWindow* window = (WineWindow*)w;
if (CGRectIsNull(rect)) rect = CGRectZero;
OnMainThread(^{
BOOL changedWindow = (window && window != [view window]);
NSRect newFrame = NSRectFromCGRect(rect);
NSRect oldFrame = [view frame];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
BOOL needUpdateWindowForGLSubviews = FALSE;
2013-03-06 11:59:07 +01:00
if (changedWindow)
{
WineWindow* oldWindow = (WineWindow*)[view window];
2013-03-06 11:59:07 +01:00
[view removeFromSuperview];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
[oldWindow updateForGLSubviews];
2013-03-06 11:59:07 +01:00
[[window contentView] addSubview:view];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
needUpdateWindowForGLSubviews = TRUE;
2013-03-06 11:59:07 +01:00
}
if (!NSEqualRects(oldFrame, newFrame))
{
if (!changedWindow)
[[view superview] setNeedsDisplayInRect:oldFrame];
if (NSEqualPoints(oldFrame.origin, newFrame.origin))
[view setFrameSize:newFrame.size];
else if (NSEqualSizes(oldFrame.size, newFrame.size))
[view setFrameOrigin:newFrame.origin];
else
[view setFrame:newFrame];
[view setNeedsDisplay:YES];
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
needUpdateWindowForGLSubviews = TRUE;
2013-03-06 11:59:07 +01:00
}
winemac: Add a new registry setting, OpenGLSurfaceMode, to control how GL surfaces relate to the window. The default behavior is that GL surfaces are on top of all non-GL content in the window. This maximizes the performance for the common case of games, but clipping by parents, siblings, and child windows isn't respected. Setting OpenGLSurfaceMode to "behind" pushes the GL surface to be behind the Mac window. The window has transparent holes punched through it so that the GL surface shows through. USER32 and the wineserver take care of making sure the holes are only where the GL windows would be unclipped and unoccluded. Because the OS X window server has to composite the GL surface with the window, this limits the framerate. Since the Mac driver has no server-side rendering path, GDI rendering to a window which has a GL surface doesn't work. As a partial workaround, mostly for cases where a GL surface is created but never used, setting OpenGLSurfaceMode to "transparent" allows the GDI rendering to show through the transparent parts of the GL surface. The GDI rendering is drawn to the top-level window's surface as normal. (The behavior of user32 to exclude the portion covered by a GL window from GDI rendering is disabled.) The GL surface is in front of the window but potentially wholly or partially transparent. It is composited with the window behind it. The GL surface is initially cleared to be completely transparent. So, if no GL rendering is done, the window will appear as though the GL surface didn't exist.
2015-09-15 03:35:25 +02:00
if (needUpdateWindowForGLSubviews)
[(WineWindow*)[view window] updateForGLSubviews];
2013-03-06 11:59:07 +01:00
});
[pool release];
}
/***********************************************************************
* macdrv_add_view_opengl_context
*
* Add an OpenGL context to the list being tracked for each view.
*/
void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineContentView* view = (WineContentView*)v;
WineOpenGLContext *context = (WineOpenGLContext*)c;
OnMainThread(^{
2013-03-06 11:59:07 +01:00
[view addGLContext:context];
});
[pool release];
}
/***********************************************************************
* macdrv_remove_view_opengl_context
*
* Add an OpenGL context to the list being tracked for each view.
*/
void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
WineContentView* view = (WineContentView*)v;
WineOpenGLContext *context = (WineOpenGLContext*)c;
OnMainThreadAsync(^{
[view removeGLContext:context];
});
[pool release];
}
/***********************************************************************
* macdrv_window_background_color
*
* Returns the standard Mac window background color as a 32-bit value of
* the form 0x00rrggbb.
*/
uint32_t macdrv_window_background_color(void)
{
static uint32_t result;
static dispatch_once_t once;
// Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
// color spaces (RGB or grayscale). So, the only way to get RGB values out
// of it is to draw with it.
dispatch_once(&once, ^{
OnMainThread(^{
unsigned char rgbx[4];
unsigned char *planes = rgbx;
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
pixelsWide:1
pixelsHigh:1
bitsPerSample:8
samplesPerPixel:3
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:0
bytesPerRow:4
bitsPerPixel:32];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
[[NSColor windowBackgroundColor] set];
NSRectFill(NSMakeRect(0, 0, 1, 1));
[NSGraphicsContext restoreGraphicsState];
[bitmap release];
result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
});
});
return result;
}
/***********************************************************************
* macdrv_send_text_input_event
*/
int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
{
__block BOOL ret;
OnMainThread(^{
WineWindow* window = (WineWindow*)[NSApp keyWindow];
if (![window isKindOfClass:[WineWindow class]])
{
window = (WineWindow*)[NSApp mainWindow];
if (![window isKindOfClass:[WineWindow class]])
window = [[WineApplicationController sharedController] frontWineWindow];
}
if (window)
{
NSUInteger localFlags = flags;
CGEventRef c;
NSEvent* event;
window.imeData = data;
fix_device_modifiers_by_generic(&localFlags);
// An NSEvent created with +keyEventWithType:... is internally marked
// as synthetic and doesn't get sent through input methods. But one
// created from a CGEvent doesn't have that problem.
c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
CGEventSetFlags(c, localFlags);
CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
event = [NSEvent eventWithCGEvent:c];
CFRelease(c);
window.commandDone = FALSE;
ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
}
else
ret = FALSE;
});
return ret;
}