Merge pull request #1525 from ychin/fullscreen-restore-window-size

Fix full screen window restore / multi-screen / misc issues
This commit is contained in:
Yee Cheng Chin
2025-01-18 15:40:26 +09:00
committed by GitHub
4 changed files with 345 additions and 110 deletions
+1 -4
View File
@@ -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;
}
+84 -72
View File
@@ -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)
+63 -16
View File
@@ -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
+197 -18
View File
@@ -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