mirror of
https://github.com/reactos/wine.git
synced 2024-11-24 20:30:01 +00:00
792b47ad3b
OS X doesn't have the same concept of maximized windows as Windows does. There's no mode that prevents a normally-movable window from being moved. If a window is "zoomed", it mostly fills the screen but the user can still move or resize it, at which point it ceases to be in the zoomed state. So, users are confused and frustrated when they can't move a window that's maximized. To get similar behavior while still respecting Win32 semantics, we detect when the user tries to move a maximized window. When they start, a request is submitted to the app to restore the window. Unless and until the window is restored, we don't actually allow the window to move. The user expects to move the window from its current (maximized) position. It should not jump to its normal position upon being restored. So, we set the window's normal position to its current position before restoring it.
2893 lines
98 KiB
Objective-C
2893 lines
98 KiB
Objective-C
/*
|
|
* 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 "cocoa_window.h"
|
|
|
|
#include "macdrv_cocoa.h"
|
|
#import "cocoa_app.h"
|
|
#import "cocoa_event.h"
|
|
#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 WineContentView : NSView <NSTextInputClient>
|
|
{
|
|
NSMutableArray* glContexts;
|
|
NSMutableArray* pendingGlContexts;
|
|
BOOL clearedGlSurface;
|
|
|
|
NSMutableAttributedString* markedText;
|
|
NSRange markedTextSelection;
|
|
}
|
|
|
|
- (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;
|
|
|
|
@property (copy, nonatomic) NSBezierPath* shape;
|
|
@property (copy, nonatomic) NSData* shapeData;
|
|
@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 (retain, nonatomic) NSTimer* liveResizeDisplayTimer;
|
|
|
|
@property (readonly, copy, nonatomic) NSArray* childWineWindows;
|
|
|
|
- (void) updateColorSpace;
|
|
|
|
- (BOOL) becameEligibleParentOrChild;
|
|
- (void) becameIneligibleChild;
|
|
|
|
@end
|
|
|
|
|
|
@implementation WineContentView
|
|
|
|
- (void) dealloc
|
|
{
|
|
[markedText release];
|
|
[glContexts release];
|
|
[pendingGlContexts release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (BOOL) isFlipped
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void) drawRect:(NSRect)rect
|
|
{
|
|
WineWindow* window = (WineWindow*)[self window];
|
|
|
|
for (WineOpenGLContext* context in pendingGlContexts)
|
|
{
|
|
if (!clearedGlSurface)
|
|
{
|
|
context.shouldClearToBlack = TRUE;
|
|
clearedGlSurface = TRUE;
|
|
}
|
|
context.needsUpdate = TRUE;
|
|
}
|
|
[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);
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
[(WineWindow*)[self window] updateColorSpace];
|
|
}
|
|
|
|
- (void) removeGLContext:(WineOpenGLContext*)context
|
|
{
|
|
[glContexts removeObjectIdenticalTo:context];
|
|
[pendingGlContexts removeObjectIdenticalTo:context];
|
|
[(WineWindow*)[self window] updateColorSpace];
|
|
}
|
|
|
|
- (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;
|
|
@synthesize liveResizeDisplayTimer;
|
|
|
|
+ (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.hwnd = hwnd;
|
|
window.queue = queue;
|
|
window->savedContentMinSize = NSZeroSize;
|
|
window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
|
|
window->resizable = wf->resizable;
|
|
|
|
[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];
|
|
|
|
return window;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[liveResizeDisplayTimer invalidate];
|
|
[liveResizeDisplayTimer release];
|
|
[queue release];
|
|
[latentChildWindows release];
|
|
[latentParentWindow release];
|
|
[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];
|
|
[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]];
|
|
|
|
// 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];
|
|
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;
|
|
|
|
[self becameIneligibleParentOrChild];
|
|
if ([self isMiniaturized])
|
|
{
|
|
fakingClose = TRUE;
|
|
[self close];
|
|
fakingClose = FALSE;
|
|
}
|
|
else
|
|
[self orderOut:nil];
|
|
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];
|
|
}
|
|
}
|
|
|
|
- (BOOL) needsTransparency
|
|
{
|
|
return self.shape || self.colorKeyed || self.usePerPixelAlpha;
|
|
}
|
|
|
|
- (void) checkTransparency
|
|
{
|
|
if (![self isOpaque] && !self.needsTransparency)
|
|
{
|
|
self.shapeChangedSinceLastDraw = TRUE;
|
|
[[self contentView] setNeedsDisplay:YES];
|
|
[self setBackgroundColor:[NSColor windowBackgroundColor]];
|
|
[self setOpaque:YES];
|
|
}
|
|
else if ([self isOpaque] && self.needsTransparency)
|
|
{
|
|
self.shapeChangedSinceLastDraw = TRUE;
|
|
[[self contentView] setNeedsDisplay:YES];
|
|
[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) setLiveResizeDisplayTimer:(NSTimer*)newTimer
|
|
{
|
|
if (newTimer != liveResizeDisplayTimer)
|
|
{
|
|
[liveResizeDisplayTimer invalidate];
|
|
[liveResizeDisplayTimer release];
|
|
liveResizeDisplayTimer = [newTimer retain];
|
|
}
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ---------- 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;
|
|
}
|
|
|
|
- (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);
|
|
}
|
|
|
|
- (void) toggleFullScreen:(id)sender
|
|
{
|
|
if (!self.disabled && !maximized)
|
|
[super toggleFullScreen:sender];
|
|
}
|
|
|
|
- (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]];
|
|
}
|
|
|
|
|
|
/*
|
|
* ---------- 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)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];
|
|
}
|
|
|
|
- (void) windowDidEndLiveResize:(NSNotification *)notification
|
|
{
|
|
if (!maximized)
|
|
{
|
|
macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
|
|
[queue postEvent:event];
|
|
macdrv_release_event(event);
|
|
}
|
|
|
|
self.liveResizeDisplayTimer = nil;
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
- (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;
|
|
|
|
// There's a strange restriction in window redrawing during Cocoa-
|
|
// managed window resizing. Only calls to -[NSView setNeedsDisplay...]
|
|
// that happen synchronously when Cocoa tells us that our window size
|
|
// has changed or asynchronously in a short interval thereafter provoke
|
|
// the window to redraw. Calls to those methods that happen asynchronously
|
|
// a half second or more after the last change of the window size aren't
|
|
// heeded until the next resize-related user event (e.g. mouse movement).
|
|
//
|
|
// Wine often has a significant delay between when it's been told that
|
|
// the window has changed size and when it can flush completed drawing.
|
|
// So, our windows would get stuck with incomplete drawing for as long
|
|
// as the user holds the mouse button down and doesn't move it.
|
|
//
|
|
// We address this by "manually" asking our windows to check if they need
|
|
// redrawing every so often (during live resize only).
|
|
self.liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
|
|
target:self
|
|
selector:@selector(displayIfNeeded)
|
|
userInfo:nil
|
|
repeats:YES];
|
|
[[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
|
|
forMode:NSRunLoopCommonModes];
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
/***********************************************************************
|
|
* 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)
|
|
{
|
|
window.shape = nil;
|
|
window.shapeData = nil;
|
|
}
|
|
else
|
|
{
|
|
size_t length = sizeof(*rects) * count;
|
|
if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
|
|
{
|
|
NSBezierPath* path;
|
|
unsigned int i;
|
|
|
|
path = [NSBezierPath bezierPath];
|
|
for (i = 0; i < count; i++)
|
|
[path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
|
|
window.shape = path;
|
|
window.shapeData = [NSData dataWithBytes:rects length:length];
|
|
}
|
|
}
|
|
});
|
|
|
|
[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];
|
|
});
|
|
}
|
|
|
|
/***********************************************************************
|
|
* 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)];
|
|
});
|
|
}
|
|
|
|
/***********************************************************************
|
|
* 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];
|
|
[window updateColorSpace];
|
|
});
|
|
|
|
[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];
|
|
|
|
[nc removeObserver:view
|
|
name:NSViewGlobalFrameDidChangeNotification
|
|
object:view];
|
|
[nc removeObserver:view
|
|
name:NSApplicationDidChangeScreenParametersNotification
|
|
object:NSApp];
|
|
[view removeFromSuperview];
|
|
[view release];
|
|
[window updateColorSpace];
|
|
});
|
|
|
|
[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];
|
|
BOOL needUpdateWindowColorSpace = FALSE;
|
|
|
|
if (changedWindow)
|
|
{
|
|
WineWindow* oldWindow = (WineWindow*)[view window];
|
|
[view removeFromSuperview];
|
|
[oldWindow updateColorSpace];
|
|
[[window contentView] addSubview:view];
|
|
needUpdateWindowColorSpace = TRUE;
|
|
}
|
|
|
|
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];
|
|
needUpdateWindowColorSpace = TRUE;
|
|
}
|
|
|
|
if (needUpdateWindowColorSpace)
|
|
[(WineWindow*)[view window] updateColorSpace];
|
|
});
|
|
|
|
[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(^{
|
|
[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;
|
|
}
|