mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-06-11 15:37:29 +02:00
Merge pull request #1525 from ychin/fullscreen-restore-window-size
Fix full screen window restore / multi-screen / misc issues
This commit is contained in:
@@ -17,14 +17,10 @@
|
||||
@interface MMFullScreenWindow : NSWindow {
|
||||
NSWindow *target;
|
||||
MMVimView *view;
|
||||
NSPoint oldPosition;
|
||||
NSString *oldTabBarStyle;
|
||||
int options;
|
||||
int state;
|
||||
|
||||
// These are only valid in full-screen mode and store pre-fu vim size
|
||||
int nonFuRows, nonFuColumns;
|
||||
|
||||
/// The non-full-screen size of the Vim view. Used for non-maxvert/maxhorz options.
|
||||
NSSize nonFuVimViewSize;
|
||||
|
||||
@@ -32,6 +28,7 @@
|
||||
int startFuFlags;
|
||||
|
||||
// Controls the speed of the fade in and out.
|
||||
// This feature is deprecated and off by default.
|
||||
double fadeTime;
|
||||
double fadeReservationTime;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,9 @@ enum {
|
||||
backgroundColor:(NSColor *)back
|
||||
{
|
||||
NSScreen* screen = [t screen];
|
||||
|
||||
// XXX: what if screen == nil?
|
||||
if (screen == nil) {
|
||||
screen = [NSScreen mainScreen];
|
||||
}
|
||||
|
||||
// you can't change the style of an existing window in cocoa. create a new
|
||||
// window and move the MMTextView into it.
|
||||
@@ -81,10 +82,12 @@ enum {
|
||||
view = [v retain];
|
||||
|
||||
[self setHasShadow:NO];
|
||||
[self setShowsResizeIndicator:NO];
|
||||
[self setBackgroundColor:back];
|
||||
[self setReleasedWhenClosed:NO];
|
||||
|
||||
// this disables any menu items for window tiling and for moving to another screen.
|
||||
[self setMovable:NO];
|
||||
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc addObserver:self
|
||||
selector:@selector(windowDidBecomeMain:)
|
||||
@@ -169,7 +172,11 @@ enum {
|
||||
|
||||
// NOTE: The window may have moved to another screen in between init.. and
|
||||
// this call so set the frame again just in case.
|
||||
[self setFrame:[[target screen] frame] display:NO];
|
||||
NSScreen* screen = [target screen];
|
||||
if (screen == nil) {
|
||||
screen = [NSScreen mainScreen];
|
||||
}
|
||||
[self setFrame:[screen frame] display:NO];
|
||||
|
||||
oldTabBarStyle = [[view tabBarControl] styleName];
|
||||
|
||||
@@ -178,8 +185,6 @@ enum {
|
||||
[[view tabBarControl] setStyleNamed:style];
|
||||
|
||||
// add text view
|
||||
oldPosition = [view frame].origin;
|
||||
|
||||
[view removeFromSuperviewWithoutNeedingDisplay];
|
||||
[[self contentView] addSubview:view];
|
||||
[self setInitialFirstResponder:[view textView]];
|
||||
@@ -195,9 +200,18 @@ enum {
|
||||
}
|
||||
|
||||
[self setAppearance:target.appearance];
|
||||
|
||||
[self setOpaque:[target isOpaque]];
|
||||
|
||||
// Copy the collection behavior so it retains the window behavior (e.g. in
|
||||
// Stage Manager). Make sure to set the native full screen flags to "none"
|
||||
// as we want to prevent macOS from being able to take this window full
|
||||
// screen (e.g. via the Window menu or dragging in Mission Control).
|
||||
NSWindowCollectionBehavior wcb = target.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorFullScreenPrimary);
|
||||
wcb &= ~(NSWindowCollectionBehaviorFullScreenAuxiliary);
|
||||
wcb |= NSWindowCollectionBehaviorFullScreenNone;
|
||||
[self setCollectionBehavior:wcb];
|
||||
|
||||
// reassign target's window controller to believe that it's now controlling us
|
||||
// don't set this sooner, so we don't get an additional
|
||||
// focus gained message
|
||||
@@ -206,27 +220,16 @@ enum {
|
||||
|
||||
// Store view dimension used before entering full-screen, then resize the
|
||||
// view to match 'fuopt'.
|
||||
[[view textView] getMaxRows:&nonFuRows columns:&nonFuColumns];
|
||||
nonFuVimViewSize = view.frame.size;
|
||||
|
||||
// Store options used when entering full-screen so that we can restore
|
||||
// dimensions when exiting full-screen.
|
||||
startFuFlags = options;
|
||||
|
||||
// HACK! Put window on all Spaces to avoid Spaces (available on OS X 10.5
|
||||
// and later) from moving the full-screen window to a separate Space from
|
||||
// the one the decorated window is occupying. The collection behavior is
|
||||
// restored further down.
|
||||
NSWindowCollectionBehavior wcb = [self collectionBehavior];
|
||||
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
|
||||
|
||||
// make us visible and target invisible
|
||||
[target orderOut:self];
|
||||
[self makeKeyAndOrderFront:self];
|
||||
|
||||
// Restore collection behavior (see hack above).
|
||||
[self setCollectionBehavior:wcb];
|
||||
|
||||
// fade back in
|
||||
if (didBlend) {
|
||||
[NSAnimationContext currentContext].completionHandler = ^{
|
||||
@@ -252,22 +255,6 @@ enum {
|
||||
}
|
||||
}
|
||||
|
||||
// restore old vim view size
|
||||
int currRows, currColumns;
|
||||
[[view textView] getMaxRows:&currRows columns:&currColumns];
|
||||
int newRows = nonFuRows, newColumns = nonFuColumns;
|
||||
|
||||
// resize vim if necessary
|
||||
if (currRows != newRows || currColumns != newColumns) {
|
||||
int newSize[2] = { newRows, newColumns };
|
||||
NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
|
||||
MMVimController *vimController =
|
||||
[[self windowController] vimController];
|
||||
|
||||
[vimController sendMessage:SetTextDimensionsMsgID data:data];
|
||||
[[view textView] setMaxRows:newRows columns:newColumns];
|
||||
}
|
||||
|
||||
// fix up target controller
|
||||
[self retain]; // NSWindowController releases us once
|
||||
[[self windowController] setWindow:target];
|
||||
@@ -277,7 +264,17 @@ enum {
|
||||
// fix delegate
|
||||
id delegate = [self delegate];
|
||||
[self setDelegate:nil];
|
||||
|
||||
|
||||
// if this window ended up on a different screen, we want to move the
|
||||
// original window to this new screen.
|
||||
if (self.screen != target.screen && self.screen != nil && target.screen != nil) {
|
||||
NSPoint topLeftPos = NSMakePoint(NSMinX(target.frame) - NSMinX(target.screen.visibleFrame),
|
||||
NSMaxY(target.frame) - NSMaxY(target.screen.visibleFrame));
|
||||
NSPoint newTopLeftPos = NSMakePoint(NSMinX(self.screen.visibleFrame) + topLeftPos.x,
|
||||
NSMaxY(self.screen.visibleFrame) + topLeftPos.y);
|
||||
[target setFrameTopLeftPoint:newTopLeftPos];
|
||||
}
|
||||
|
||||
// move text view back to original window, hide fullScreen window,
|
||||
// show original window
|
||||
// do this _after_ resetting delegate and window controller, so the
|
||||
@@ -286,42 +283,50 @@ enum {
|
||||
[view removeFromSuperviewWithoutNeedingDisplay];
|
||||
[[target contentView] addSubview:view];
|
||||
|
||||
[view setFrameOrigin:oldPosition];
|
||||
[self close];
|
||||
|
||||
// Set the text view to initial first responder, otherwise the 'plus'
|
||||
// button on the tabline steals the first responder status.
|
||||
[target setInitialFirstResponder:[view textView]];
|
||||
|
||||
// HACK! Put decorated window on all Spaces (available on OS X 10.5 and
|
||||
// later) so that the decorated window stays on the same Space as the full
|
||||
// screen window (they may occupy different Spaces e.g. if the full-screen
|
||||
// window was dragged to another Space). The collection behavior is
|
||||
// restored further down.
|
||||
NSWindowCollectionBehavior wcb = [target collectionBehavior];
|
||||
[target setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
|
||||
// On Mac OS X 10.7 windows animate when makeKeyAndOrderFront: is called.
|
||||
// This is distracting here, so disable the animation and restore animation
|
||||
// behavior after calling makeKeyAndOrderFront:.
|
||||
NSWindowAnimationBehavior winAnimBehavior = [target animationBehavior];
|
||||
[target setAnimationBehavior:NSWindowAnimationBehaviorNone];
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
// HACK! On Mac OS X 10.7 windows animate when makeKeyAndOrderFront: is
|
||||
// called. This is distracting here, so disable the animation and restore
|
||||
// animation behavior after calling makeKeyAndOrderFront:.
|
||||
NSWindowAnimationBehavior a = NSWindowAnimationBehaviorNone;
|
||||
if ([target respondsToSelector:@selector(animationBehavior)]) {
|
||||
a = [target animationBehavior];
|
||||
[target setAnimationBehavior:NSWindowAnimationBehaviorNone];
|
||||
}
|
||||
#endif
|
||||
// Note: Currently, there is a possibility that the full-screen window is
|
||||
// in a different Space from the original window. This could happen if the
|
||||
// full-screen was manually dragged to another Space in Mission Control.
|
||||
// If that's the case, the original window will be restored to the original
|
||||
// Space it was in, which may not be what the user intended.
|
||||
//
|
||||
// We don't address this for a few reasons:
|
||||
// 1. This is a niche case that wouldn't matter 99% of the time.
|
||||
// 2. macOS does not expose explicit control over Spaces in the public APIs.
|
||||
// We don't have a way to directly determine which space each window is
|
||||
// on, other than just detecting whether it's on the active space. We
|
||||
// also don't have a way to place the window on another Space
|
||||
// programmatically. We could move the window to the active Space by
|
||||
// changing collectionBehavior to CanJoinAllSpace or MoveToActiveSpace,
|
||||
// and after it's moved, unset the collectionBehavior. This is tricky to
|
||||
// do because the move doesn't happen immediately. The window manager
|
||||
// takes a few cycles before it moves the window over to the active
|
||||
// space and we would need to continually check onActiveSpace to know
|
||||
// when that happens. This leads to a fair bit of window management
|
||||
// complexity.
|
||||
// 3. Even if we implement the above, it could still lead to unintended
|
||||
// behaviors. If during the window restore process, the user navigated
|
||||
// to another Space (e.g. a popup dialog box), it's not necessarily the
|
||||
// correct behavior to put the restored window there. What we want is to
|
||||
// query the exact Space the full-screen window is on and place the
|
||||
// original window there, but there's no public APIs to do that.
|
||||
|
||||
[target makeKeyAndOrderFront:self];
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
// HACK! Restore animation behavior.
|
||||
if (NSWindowAnimationBehaviorNone != a)
|
||||
[target setAnimationBehavior:a];
|
||||
#endif
|
||||
|
||||
// Restore collection behavior (see hack above).
|
||||
[target setCollectionBehavior:wcb];
|
||||
// Restore animation behavior.
|
||||
if (NSWindowAnimationBehaviorNone != winAnimBehavior)
|
||||
[target setAnimationBehavior:winAnimBehavior];
|
||||
|
||||
// ...but we don't want a focus gained message either, so don't set this
|
||||
// sooner
|
||||
@@ -365,16 +370,11 @@ enum {
|
||||
// hidden/displayed.
|
||||
ASLogDebug(@"Screen unplugged / resolution changed");
|
||||
|
||||
NSScreen *screen = [target screen];
|
||||
if (!screen) {
|
||||
// Paranoia: if window we originally used for full-screen is gone, try
|
||||
// screen window is on now, and failing that (not sure this can happen)
|
||||
// use main screen.
|
||||
screen = [self screen];
|
||||
if (!screen)
|
||||
screen = [NSScreen mainScreen];
|
||||
NSScreen *screen = [self screen];
|
||||
if (screen == nil) {
|
||||
// See windowDidMove for more explanations.
|
||||
screen = [NSScreen mainScreen];
|
||||
}
|
||||
|
||||
// Ensure the full-screen window is still covering the entire screen and
|
||||
// then resize view according to 'fuopt'.
|
||||
[self setFrame:[screen frame] display:NO];
|
||||
@@ -549,12 +549,24 @@ enum {
|
||||
if (state != InFullScreen)
|
||||
return;
|
||||
|
||||
// Window may move as a result of being dragged between Spaces.
|
||||
// Window may move as a result of being dragged between screens.
|
||||
ASLogDebug(@"Full-screen window moved, ensuring it covers the screen...");
|
||||
|
||||
NSScreen *screen = [self screen];
|
||||
if (screen == nil) {
|
||||
// If for some reason this window got moved to an area not associated
|
||||
// with a screen just fall back to a main one. Otherwise this window
|
||||
// will be stuck on a no-man's land and the user will have no way to
|
||||
// use it. One known way this could happen is when the user has a
|
||||
// larger monitor on the left (where MacVim was started) and a smaller
|
||||
// on the right. The user then drag the full screen window to the right
|
||||
// screen in Mission Control. macOS will refuse to place the window
|
||||
// because it is too big so it gets placed out of bounds.
|
||||
screen = [NSScreen mainScreen];
|
||||
}
|
||||
// Ensure the full-screen window is still covering the entire screen and
|
||||
// then resize view according to 'fuopt'.
|
||||
[self setFrame:[[self screen] frame] display:NO];
|
||||
[self setFrame:[screen frame] display:NO];
|
||||
}
|
||||
|
||||
@end // MMFullScreenWindow (Private)
|
||||
|
||||
@@ -222,21 +222,24 @@
|
||||
if ([win respondsToSelector:@selector(_setContentHasShadow:)])
|
||||
[win _setContentHasShadow:NO];
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
// Building on Mac OS X 10.7 or greater.
|
||||
|
||||
// This puts the full-screen button in the top right of each window
|
||||
if ([win respondsToSelector:@selector(setCollectionBehavior:)])
|
||||
[win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
|
||||
// This adds the title bar full-screen button (which calls
|
||||
// toggleFullScreen:) and also populates the Window menu itmes for full
|
||||
// screen tiling. Even if we are using non-native full screen, we still set
|
||||
// this just so we have that button to override. We also intentionally
|
||||
// don't set the flag NSWindowCollectionBehaviorFullScreenDisallowsTiling
|
||||
// in that case because MacVim still works when macOS tries to do native
|
||||
// full screen tiling so we'll allow it.
|
||||
NSWindowCollectionBehavior wcb = win.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorFullScreenAuxiliary);
|
||||
wcb &= ~(NSWindowCollectionBehaviorFullScreenNone);
|
||||
wcb |= NSWindowCollectionBehaviorFullScreenPrimary;
|
||||
[win setCollectionBehavior:wcb];
|
||||
|
||||
// This makes windows animate when opened
|
||||
if ([win respondsToSelector:@selector(setAnimationBehavior:)]) {
|
||||
if (![[NSUserDefaults standardUserDefaults]
|
||||
boolForKey:MMDisableLaunchAnimationKey]) {
|
||||
[win setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
|
||||
}
|
||||
if (![[NSUserDefaults standardUserDefaults]
|
||||
boolForKey:MMDisableLaunchAnimationKey]) {
|
||||
[win setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
if (@available(macos 11.0, *)) {
|
||||
@@ -1041,8 +1044,14 @@
|
||||
[fullScreenWindow release];
|
||||
fullScreenWindow = nil;
|
||||
|
||||
// The vim view may be too large to fit the screen, so update it.
|
||||
shouldResizeVimView = YES;
|
||||
// View is always at (0,0) except in full screen where it gets set to
|
||||
// [fullScreenWindow getDesiredFrame].
|
||||
[self.vimView setFrameOrigin:NSZeroPoint];
|
||||
|
||||
// Simply resize Vim view to fit within the original window size. Note
|
||||
// that this behavior is similar to guioption-k, even if it's not set
|
||||
// in Vim.
|
||||
[self.vimView setFrameSizeKeepGUISize:[self contentSize]];
|
||||
} else {
|
||||
// Using native full-screen
|
||||
// NOTE: fullScreenEnabled is used to detect if we enter full-screen
|
||||
@@ -1449,7 +1458,19 @@
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
|
||||
if (@available(macos 13.0, *)) {
|
||||
[decoratedWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllApplications];
|
||||
NSWindowCollectionBehavior wcb = decoratedWindow.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorPrimary);
|
||||
wcb &= ~(NSWindowCollectionBehaviorAuxiliary);
|
||||
wcb |= NSWindowCollectionBehaviorCanJoinAllApplications;
|
||||
[decoratedWindow setCollectionBehavior:wcb];
|
||||
|
||||
if (fullScreenWindow) { // non-native full screen has a separate window
|
||||
NSWindowCollectionBehavior wcb = fullScreenWindow.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorPrimary);
|
||||
wcb &= ~(NSWindowCollectionBehaviorAuxiliary);
|
||||
wcb |= NSWindowCollectionBehaviorCanJoinAllApplications;
|
||||
[fullScreenWindow setCollectionBehavior:wcb];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1460,7 +1481,19 @@
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
|
||||
if (@available(macos 13.0, *)) {
|
||||
[decoratedWindow setCollectionBehavior:NSWindowCollectionBehaviorPrimary];
|
||||
NSWindowCollectionBehavior wcb = decoratedWindow.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorCanJoinAllApplications);
|
||||
wcb &= ~(NSWindowCollectionBehaviorAuxiliary);
|
||||
wcb |= NSWindowCollectionBehaviorPrimary;
|
||||
[decoratedWindow setCollectionBehavior:wcb];
|
||||
|
||||
if (fullScreenWindow) { // non-native full screen has a separate window
|
||||
NSWindowCollectionBehavior wcb = fullScreenWindow.collectionBehavior;
|
||||
wcb &= ~(NSWindowCollectionBehaviorCanJoinAllApplications);
|
||||
wcb &= ~(NSWindowCollectionBehaviorAuxiliary);
|
||||
wcb |= NSWindowCollectionBehaviorPrimary;
|
||||
[fullScreenWindow setCollectionBehavior:wcb];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1595,6 +1628,15 @@
|
||||
fullScreenEnabled = NO;
|
||||
[self invFullScreen:self];
|
||||
}
|
||||
|
||||
// If we are using a resize increment (i.e. smooth resize is off), macOS
|
||||
// has a quirk/bug that will use the increment to determine the final size
|
||||
// of the original window as fixed increment from the full screen window,
|
||||
// which could annoyingly not be the original size. This could lead to
|
||||
// enter full screen -> exit full screen leading to the window having
|
||||
// different size. Because of that, just set increment to 1,1 here to
|
||||
// alleviate the issue.
|
||||
[decoratedWindow setContentResizeIncrements:NSMakeSize(1, 1)];
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification
|
||||
@@ -1605,6 +1647,11 @@
|
||||
[vimController sendMessage:BackingPropertiesChangedMsgID data:nil];
|
||||
}
|
||||
|
||||
// We set the resize increment to 1,1 above just to sure window size was
|
||||
// restored properly. We want to set it back to the correct value, which
|
||||
// would not be 1,1 if we are not using smooth resize.
|
||||
[self updateResizeConstraints:NO];
|
||||
|
||||
[self updateTablineSeparator];
|
||||
|
||||
// Sometimes full screen will de-focus the text view. This seems to happen
|
||||
|
||||
@@ -102,7 +102,7 @@ static NSDictionary<NSString *, id> *cachedAppDefaults;
|
||||
[MMAppController.sharedInstance openNewWindow:NewWindowClean activate:YES extraArgs:args];
|
||||
[self waitForVimOpenAndMessages];
|
||||
|
||||
__weak MacVimTests *tests = self;
|
||||
__weak __typeof__(self) self_weak = self;
|
||||
[self addTeardownBlock:^{
|
||||
MMAppController *app = MMAppController.sharedInstance;
|
||||
|
||||
@@ -112,12 +112,12 @@ static NSDictionary<NSString *, id> *cachedAppDefaults;
|
||||
// another native full screen test immediately it will fail.
|
||||
if ([app.keyVimController.windowController fullScreenEnabled] &&
|
||||
app.keyVimController.windowController.window.styleMask & NSWindowStyleMaskFullScreen) {
|
||||
[tests sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[tests waitForFullscreenTransitionIsEnter:NO isNative:YES];
|
||||
[self_weak sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self_weak waitForFullscreenTransitionIsEnter:NO isNative:YES];
|
||||
}
|
||||
|
||||
[[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil];
|
||||
[tests waitForVimClose];
|
||||
[self_weak waitForVimClose];
|
||||
|
||||
XCTAssertEqual(0, [app vimControllers].count);
|
||||
}];
|
||||
@@ -126,11 +126,15 @@ static NSDictionary<NSString *, id> *cachedAppDefaults;
|
||||
/// Creates a file URL in a temporary directory. The file itself is not created.
|
||||
/// The directory will be cleaned up automatically.
|
||||
- (NSURL *)tempFile:(NSString *)name {
|
||||
NSError *error = nil;
|
||||
NSURL *tempDir = [NSFileManager.defaultManager URLForDirectory:NSItemReplacementDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:NSFileManager.defaultManager.homeDirectoryForCurrentUser
|
||||
create:YES
|
||||
error:nil];
|
||||
error:&error];
|
||||
if (tempDir == nil) {
|
||||
@throw error;
|
||||
}
|
||||
[self addTeardownBlock:^{
|
||||
[NSFileManager.defaultManager removeItemAtURL:tempDir error:nil];
|
||||
}];
|
||||
@@ -452,6 +456,9 @@ do { \
|
||||
- (void) testCmdlineRowCalculation {
|
||||
[self createTestVimWindow];
|
||||
|
||||
[self sendStringToVim:@":set lines=10 columns=50\n" withMods:0]; // this test needs a sane window size
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
|
||||
MMAppController *app = MMAppController.sharedInstance;
|
||||
MMTextView *textView = [[[[app keyVimController] windowController] vimView] textView];
|
||||
const int numLines = [textView maxRows];
|
||||
@@ -816,21 +823,83 @@ do { \
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
[self waitForEventHandlingAndVimProcess]; // wait one more cycle to make sure we finished the transition
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject a mouse click at the window border to pretend a user has interacted
|
||||
/// with the window. Currently macOS 14/15 seems to exhibit a bug (only in VMs)
|
||||
/// where full screen restore would restore to the last window frame that a
|
||||
/// user has set manually rather than any programmatically set frames. This bug
|
||||
/// does not occur in a real MacBook however, makes the issue hard to debug.
|
||||
/// This workaround allows tests to pass consistently either in CI (run in a
|
||||
/// VM) or on a developer machine.
|
||||
/// This issue was filed as FB16348262 with Apple.
|
||||
- (void)injectFakeUserWindowInteraction:(NSWindow *)window {
|
||||
NSTimeInterval timestamp = [[NSProcessInfo processInfo] systemUptime];
|
||||
static NSInteger eventNumber = 100000;
|
||||
NSApplication* app = [NSApplication sharedApplication];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
NSEvent *mouseEvent = [NSEvent mouseEventWithType:(i == 0 ? NSEventTypeLeftMouseDown : NSEventTypeLeftMouseUp)
|
||||
location:NSMakePoint(0,0)
|
||||
modifierFlags:0
|
||||
timestamp:timestamp + i * 0.001
|
||||
windowNumber:[window windowNumber]
|
||||
context:0
|
||||
eventNumber:eventNumber++
|
||||
clickCount:1
|
||||
pressure:1];
|
||||
[app postEvent:mouseEvent atStart:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility to test full screen functionality in both non-native/native full
|
||||
/// screen.
|
||||
- (void) fullScreenTestWithNative:(BOOL)native {
|
||||
// Change native full screen setting
|
||||
[self setDefault:MMNativeFullScreenKey toValue:[NSNumber numberWithBool:native]];
|
||||
[self setDefault:MMNativeFullScreenKey toValue:@(native)];
|
||||
|
||||
// The launch animation interferes with setting the frames in quick sequence
|
||||
// and the user action injection below. Disable it.
|
||||
[self setDefault:MMDisableLaunchAnimationKey toValue:@YES];
|
||||
|
||||
// In native full screen, non-smooth resize is more of an edge case due to
|
||||
// macOS's handling of resize constraints. Set this option to exercise that.
|
||||
// Also, when we are setting guifont, we don't cause it to resize the window.
|
||||
[self setDefault:MMSmoothResizeKey toValue:@NO];
|
||||
|
||||
[self createTestVimWindow];
|
||||
|
||||
MMAppController *app = MMAppController.sharedInstance;
|
||||
MMWindowController *winController = app.keyVimController.windowController;
|
||||
MMTextView *textView = [[winController vimView] textView];
|
||||
|
||||
// Enter full screen and check that the states are properly changed.
|
||||
const int numRows = MMMinRows + 10;
|
||||
const int numColumns = MMMinColumns + 10;
|
||||
[self sendStringToVim:@":set guifont=Menlo:h10\n" withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
[self sendStringToVim:[NSString stringWithFormat:@":set lines=%d columns=%d\n", numRows, numColumns] withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
|
||||
XCTAssertEqual(textView.maxRows, numRows);
|
||||
XCTAssertEqual(textView.maxColumns, numColumns);
|
||||
|
||||
// Intentionally nudge the frame size to be not fixed increment of cell size.
|
||||
// This helps to test that we restore the window properly when leaving full
|
||||
// screen later.
|
||||
NSRect newFrame = winController.window.frame;
|
||||
newFrame.size.width += 1;
|
||||
newFrame.size.height += 2;
|
||||
[winController.window setFrame:newFrame display:YES];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
|
||||
NSRect origFrame = winController.window.frame;
|
||||
NSSize origResizeIncrements = winController.window.contentResizeIncrements;
|
||||
|
||||
XCTAssertEqual(textView.maxRows, numRows);
|
||||
XCTAssertEqual(textView.maxColumns, numColumns);
|
||||
|
||||
[self injectFakeUserWindowInteraction:winController.window];
|
||||
|
||||
// 1. Enter full screen. Check that the states are properly changed.
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:native];
|
||||
XCTAssertTrue([winController fullScreenEnabled]);
|
||||
@@ -840,19 +909,30 @@ do { \
|
||||
XCTAssertTrue([winController.window isKindOfClass:[MMFullScreenWindow class]]);
|
||||
}
|
||||
|
||||
// Exit full screen
|
||||
// 2. Exit full screen. Confirm state changes and proper window restore.
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:native];
|
||||
|
||||
XCTAssertFalse([winController fullScreenEnabled]);
|
||||
XCTAssertTrue([winController.window isKindOfClass:[MMWindow class]]);
|
||||
|
||||
// Enter full screen again
|
||||
XCTAssertEqual(textView.maxRows, numRows);
|
||||
XCTAssertEqual(textView.maxColumns, numColumns);
|
||||
XCTAssertTrue(NSEqualRects(origFrame, winController.window.frame),
|
||||
@"Expected frame to be %@, but was %@",
|
||||
NSStringFromRect(origFrame),
|
||||
NSStringFromRect(winController.window.frame));
|
||||
XCTAssertTrue(NSEqualSizes(origResizeIncrements, winController.window.contentResizeIncrements),
|
||||
@"Expected resize increments to be %@, but was %@",
|
||||
NSStringFromSize(origResizeIncrements),
|
||||
NSStringFromSize(winController.window.contentResizeIncrements));
|
||||
|
||||
// 3. Enter full screen again
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:native];
|
||||
XCTAssertTrue([winController fullScreenEnabled]);
|
||||
|
||||
// Test that resizing the vim view does not work when in full screen as we fix the window size instead
|
||||
MMTextView *textView = [[[[app keyVimController] windowController] vimView] textView];
|
||||
// 3.1 Test that resizing the vim view does not work when in full screen as we fix the window size instead
|
||||
const int fuRows = textView.maxRows;
|
||||
const int fuCols = textView.maxColumns;
|
||||
XCTAssertNotEqual(10, fuRows); // just some basic assumptions as full screen should have more rows/cols than this
|
||||
@@ -863,6 +943,54 @@ do { \
|
||||
[self waitForEventHandlingAndVimProcess]; // need to wait twice to allow full screen to force it back
|
||||
XCTAssertEqual(fuRows, textView.maxRows);
|
||||
XCTAssertEqual(fuCols, textView.maxColumns);
|
||||
|
||||
// 3.2 Set font to larger size to test that on restore we properly fit the
|
||||
// content back to the window of same size, but with fewer lines/columns.
|
||||
[self sendStringToVim:@":set guifont=Menlo:h13\n" withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
|
||||
// 4. Exit full screen. Confirm the restored window has fewer lines but same size.
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:native];
|
||||
|
||||
XCTAssertLessThan(textView.maxRows, numRows); // fewer lines/columns due to fitting
|
||||
XCTAssertLessThan(textView.maxColumns, numColumns);
|
||||
XCTAssertTrue(NSEqualRects(winController.window.frame, winController.window.frame),
|
||||
@"Expected frame to be %@, but was %@",
|
||||
NSStringFromRect(origFrame),
|
||||
NSStringFromRect(winController.window.frame));
|
||||
|
||||
// Now, set the rows/columns to minimum allowed by MacVim to test that on
|
||||
// restore we will obey that and resize window if necessary.
|
||||
[self sendStringToVim:@":set guifont=Menlo:h10\n" withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
[self sendStringToVim:[NSString stringWithFormat:@":set lines=%d columns=%d\n", MMMinRows, MMMinColumns] withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
origFrame = winController.window.frame;
|
||||
|
||||
[self injectFakeUserWindowInteraction:winController.window];
|
||||
|
||||
// 5. Enter full screen again.
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:native];
|
||||
|
||||
// 5.1. Set font to larger size. Unlike last time, on restore the window
|
||||
// will be larger this time because we will end up with too few
|
||||
// lines/columns if we try to fit within the content.
|
||||
[self sendStringToVim:@":set guifont=Menlo:h13\n" withMods:0];
|
||||
[self waitForEventHandlingAndVimProcess];
|
||||
|
||||
// 6. Exit full screen. Confirm the restored window has same number of
|
||||
// lines but a larger size due to the need to fit the min lines/columns.
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:native];
|
||||
|
||||
XCTAssertEqual(MMMinRows, textView.maxRows);
|
||||
XCTAssertEqual(MMMinColumns, textView.maxColumns);
|
||||
XCTAssertTrue(winController.window.frame.size.width > origFrame.size.width || winController.window.frame.size.height > origFrame.size.height,
|
||||
@"Expected final frame %@ to be larger than %@",
|
||||
NSStringFromSize(winController.window.frame.size),
|
||||
NSStringFromSize(origFrame.size));
|
||||
}
|
||||
|
||||
- (void) testFullScreenNonNative {
|
||||
@@ -878,12 +1006,7 @@ do { \
|
||||
/// process until the Vim window has been presented.
|
||||
- (void)fullScreenDelayedTestWithNative:(BOOL)native fuoptEmpty:(BOOL)fuoptEmpty {
|
||||
// Change native full screen setting
|
||||
[self setDefault:MMNativeFullScreenKey toValue:[NSNumber numberWithBool:native]];
|
||||
|
||||
// The default non-smooth resize window option results can result in an
|
||||
// inaccurate window restore in native full screen. Temporary fix is to
|
||||
// just use smooth resize for now.
|
||||
[self setDefault:MMSmoothResizeKey toValue:@YES];
|
||||
[self setDefault:MMNativeFullScreenKey toValue:@(native)];
|
||||
|
||||
if (fuoptEmpty)
|
||||
XCTAssertFalse(native);
|
||||
@@ -944,6 +1067,7 @@ do { \
|
||||
[self fullScreenDelayedTestWithNative:YES fuoptEmpty:NO];
|
||||
}
|
||||
|
||||
/// Test setting 'fuoptions' with non-native full screen.
|
||||
- (void) testFullScreenNonNativeOptions {
|
||||
// Change native full screen setting
|
||||
[self setDefault:MMNativeFullScreenKey toValue:@NO];
|
||||
@@ -952,7 +1076,8 @@ do { \
|
||||
|
||||
MMAppController *app = MMAppController.sharedInstance;
|
||||
MMWindowController *winController = app.keyVimController.windowController;
|
||||
MMTextView *textView = [[winController vimView] textView];
|
||||
MMVimView *vimView = [winController vimView];
|
||||
MMTextView *textView = [vimView textView];
|
||||
|
||||
// Test maxvert/maxhorz
|
||||
[self sendStringToVim:@":set lines=10\n" withMods:0];
|
||||
@@ -960,31 +1085,47 @@ do { \
|
||||
[self sendStringToVim:@":set fuoptions=\n" withMods:0];
|
||||
[self waitForVimProcess];
|
||||
|
||||
[self injectFakeUserWindowInteraction:winController.window];
|
||||
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
XCTAssertEqual(textView.maxRows, 10);
|
||||
XCTAssertEqual(textView.maxColumns, 30);
|
||||
XCTAssertGreaterThan(vimView.frame.origin.x, 0);
|
||||
XCTAssertGreaterThan(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:NO];
|
||||
XCTAssertEqual(vimView.frame.origin.x, 0);
|
||||
XCTAssertEqual(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set fuoptions=maxvert\n" withMods:0];
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
XCTAssertGreaterThan(textView.maxRows, 10);
|
||||
XCTAssertEqual(textView.maxColumns, 30);
|
||||
XCTAssertGreaterThan(vimView.frame.origin.x, 0);
|
||||
XCTAssertEqual(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:NO];
|
||||
XCTAssertEqual(vimView.frame.origin.x, 0);
|
||||
XCTAssertEqual(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set fuoptions=maxhorz\n" withMods:0];
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
XCTAssertEqual(textView.maxRows, 10);
|
||||
XCTAssertGreaterThan(textView.maxColumns, 30);
|
||||
XCTAssertEqual(vimView.frame.origin.x, 0);
|
||||
XCTAssertGreaterThan(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:NO];
|
||||
XCTAssertEqual(vimView.frame.origin.x, 0);
|
||||
XCTAssertEqual(vimView.frame.origin.y, 0);
|
||||
[self sendStringToVim:@":set fuoptions=maxhorz,maxvert\n" withMods:0];
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
XCTAssertGreaterThan(textView.maxRows, 10);
|
||||
XCTAssertGreaterThan(textView.maxColumns, 30);
|
||||
XCTAssertEqual(vimView.frame.origin.x, 0);
|
||||
XCTAssertEqual(vimView.frame.origin.y, 0);
|
||||
|
||||
// Test background color
|
||||
XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff000000]); // default is black
|
||||
@@ -1034,4 +1175,42 @@ do { \
|
||||
XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithRed:0 green:0 blue:1 alpha:0.001]);
|
||||
}
|
||||
|
||||
/// Test that non-native full screen can handle multiple screens. This test
|
||||
/// will only run when the machine has 2 monitors and will therefore be skipped
|
||||
/// in CI.
|
||||
- (void) testFullScreenNonNativeMultiScreen {
|
||||
XCTSkipIf(NSScreen.screens.count <= 1);
|
||||
|
||||
// Change native full screen setting
|
||||
[self setDefault:MMNativeFullScreenKey toValue:@NO];
|
||||
|
||||
[self createTestVimWindow];
|
||||
[self sendStringToVim:@":set lines=45 columns=65\n" withMods:0];
|
||||
[self waitForVimProcess];
|
||||
|
||||
MMAppController *app = MMAppController.sharedInstance;
|
||||
MMWindowController *winController = app.keyVimController.windowController;
|
||||
MMVimView *vimView = [winController vimView];
|
||||
MMTextView *textView = [vimView textView];
|
||||
|
||||
// Test that window restore properly moves the original window to the new screen
|
||||
[winController.window setFrameOrigin:NSScreen.screens[0].frame.origin];
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
[winController.window setFrameOrigin:NSScreen.screens[1].frame.origin];
|
||||
[self waitForEventHandling];
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:NO];
|
||||
XCTAssertTrue(NSPointInRect(winController.window.frame.origin, NSScreen.screens[1].frame));
|
||||
XCTAssertEqual(textView.maxRows, 45);
|
||||
XCTAssertEqual(textView.maxColumns, 65);
|
||||
[self sendStringToVim:@":set fu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:YES isNative:NO];
|
||||
[winController.window setFrameOrigin:NSScreen.screens[0].frame.origin];
|
||||
[self waitForEventHandling];
|
||||
[self sendStringToVim:@":set nofu\n" withMods:0];
|
||||
[self waitForFullscreenTransitionIsEnter:NO isNative:NO];
|
||||
XCTAssertTrue(NSPointInRect(winController.window.frame.origin, NSScreen.screens[0].frame));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user