@@ -58,8 +58,9 @@ - (MMFullScreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v
58
58
backgroundColor : (NSColor *)back
59
59
{
60
60
NSScreen * screen = [t screen ];
61
-
62
- // XXX: what if screen == nil?
61
+ if (screen == nil ) {
62
+ screen = [NSScreen mainScreen ];
63
+ }
63
64
64
65
// you can't change the style of an existing window in cocoa. create a new
65
66
// window and move the MMTextView into it.
@@ -81,10 +82,12 @@ - (MMFullScreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v
81
82
view = [v retain ];
82
83
83
84
[self setHasShadow: NO ];
84
- [self setShowsResizeIndicator: NO ];
85
85
[self setBackgroundColor: back];
86
86
[self setReleasedWhenClosed: NO ];
87
87
88
+ // this disables any menu items for window tiling and for moving to another screen.
89
+ [self setMovable: NO ];
90
+
88
91
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter ];
89
92
[nc addObserver: self
90
93
selector: @selector (windowDidBecomeMain: )
@@ -169,7 +172,11 @@ - (void)enterFullScreen
169
172
170
173
// NOTE: The window may have moved to another screen in between init.. and
171
174
// this call so set the frame again just in case.
172
- [self setFrame: [[target screen ] frame ] display: NO ];
175
+ NSScreen * screen = [target screen ];
176
+ if (screen == nil ) {
177
+ screen = [NSScreen mainScreen ];
178
+ }
179
+ [self setFrame: [screen frame ] display: NO ];
173
180
174
181
oldTabBarStyle = [[view tabBarControl ] styleName ];
175
182
@@ -178,8 +185,6 @@ - (void)enterFullScreen
178
185
[[view tabBarControl ] setStyleNamed: style];
179
186
180
187
// add text view
181
- oldPosition = [view frame ].origin ;
182
-
183
188
[view removeFromSuperviewWithoutNeedingDisplay ];
184
189
[[self contentView ] addSubview: view];
185
190
[self setInitialFirstResponder: [view textView ]];
@@ -195,9 +200,18 @@ - (void)enterFullScreen
195
200
}
196
201
197
202
[self setAppearance: target.appearance];
198
-
199
203
[self setOpaque: [target isOpaque ]];
200
204
205
+ // Copy the collection behavior so it retains the window behavior (e.g. in
206
+ // Stage Manager). Make sure to set the native full screen flags to "none"
207
+ // as we want to prevent macOS from being able to take this window full
208
+ // screen (e.g. via the Window menu or dragging in Mission Control).
209
+ NSWindowCollectionBehavior wcb = target.collectionBehavior ;
210
+ wcb &= ~(NSWindowCollectionBehaviorFullScreenPrimary);
211
+ wcb &= ~(NSWindowCollectionBehaviorFullScreenAuxiliary);
212
+ wcb |= NSWindowCollectionBehaviorFullScreenNone;
213
+ [self setCollectionBehavior: wcb];
214
+
201
215
// reassign target's window controller to believe that it's now controlling us
202
216
// don't set this sooner, so we don't get an additional
203
217
// focus gained message
@@ -206,27 +220,16 @@ - (void)enterFullScreen
206
220
207
221
// Store view dimension used before entering full-screen, then resize the
208
222
// view to match 'fuopt'.
209
- [[view textView ] getMaxRows: &nonFuRows columns: &nonFuColumns];
210
223
nonFuVimViewSize = view.frame .size ;
211
224
212
225
// Store options used when entering full-screen so that we can restore
213
226
// dimensions when exiting full-screen.
214
227
startFuFlags = options;
215
228
216
- // HACK! Put window on all Spaces to avoid Spaces (available on OS X 10.5
217
- // and later) from moving the full-screen window to a separate Space from
218
- // the one the decorated window is occupying. The collection behavior is
219
- // restored further down.
220
- NSWindowCollectionBehavior wcb = [self collectionBehavior ];
221
- [self setCollectionBehavior: NSWindowCollectionBehaviorCanJoinAllSpaces];
222
-
223
229
// make us visible and target invisible
224
230
[target orderOut: self ];
225
231
[self makeKeyAndOrderFront: self ];
226
232
227
- // Restore collection behavior (see hack above).
228
- [self setCollectionBehavior: wcb];
229
-
230
233
// fade back in
231
234
if (didBlend) {
232
235
[NSAnimationContext currentContext ].completionHandler = ^{
@@ -252,22 +255,6 @@ - (void)leaveFullScreen
252
255
}
253
256
}
254
257
255
- // restore old vim view size
256
- int currRows, currColumns;
257
- [[view textView ] getMaxRows: &currRows columns: &currColumns];
258
- int newRows = nonFuRows, newColumns = nonFuColumns;
259
-
260
- // resize vim if necessary
261
- if (currRows != newRows || currColumns != newColumns) {
262
- int newSize[2 ] = { newRows, newColumns };
263
- NSData *data = [NSData dataWithBytes: newSize length: 2 *sizeof (int )];
264
- MMVimController *vimController =
265
- [[self windowController ] vimController ];
266
-
267
- [vimController sendMessage: SetTextDimensionsMsgID data: data];
268
- [[view textView ] setMaxRows: newRows columns: newColumns];
269
- }
270
-
271
258
// fix up target controller
272
259
[self retain ]; // NSWindowController releases us once
273
260
[[self windowController ] setWindow: target];
@@ -277,7 +264,17 @@ - (void)leaveFullScreen
277
264
// fix delegate
278
265
id delegate = [self delegate ];
279
266
[self setDelegate: nil ];
280
-
267
+
268
+ // if this window ended up on a different screen, we want to move the
269
+ // original window to this new screen.
270
+ if (self.screen != target.screen && self.screen != nil && target.screen != nil ) {
271
+ NSPoint topLeftPos = NSMakePoint (NSMinX (target.frame ) - NSMinX (target.screen .visibleFrame ),
272
+ NSMaxY (target.frame ) - NSMaxY (target.screen .visibleFrame ));
273
+ NSPoint newTopLeftPos = NSMakePoint (NSMinX (self.screen .visibleFrame ) + topLeftPos.x ,
274
+ NSMaxY (self.screen .visibleFrame ) + topLeftPos.y );
275
+ [target setFrameTopLeftPoint: newTopLeftPos];
276
+ }
277
+
281
278
// move text view back to original window, hide fullScreen window,
282
279
// show original window
283
280
// do this _after_ resetting delegate and window controller, so the
@@ -286,42 +283,50 @@ - (void)leaveFullScreen
286
283
[view removeFromSuperviewWithoutNeedingDisplay ];
287
284
[[target contentView ] addSubview: view];
288
285
289
- [view setFrameOrigin: oldPosition];
290
286
[self close ];
291
287
292
288
// Set the text view to initial first responder, otherwise the 'plus'
293
289
// button on the tabline steals the first responder status.
294
290
[target setInitialFirstResponder: [view textView ]];
295
291
296
- // HACK! Put decorated window on all Spaces (available on OS X 10.5 and
297
- // later) so that the decorated window stays on the same Space as the full
298
- // screen window (they may occupy different Spaces e.g. if the full-screen
299
- // window was dragged to another Space). The collection behavior is
300
- // restored further down.
301
- NSWindowCollectionBehavior wcb = [target collectionBehavior ];
302
- [target setCollectionBehavior: NSWindowCollectionBehaviorCanJoinAllSpaces];
303
-
304
- #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
305
- // HACK! On Mac OS X 10.7 windows animate when makeKeyAndOrderFront: is
306
- // called. This is distracting here, so disable the animation and restore
307
- // animation behavior after calling makeKeyAndOrderFront:.
308
- NSWindowAnimationBehavior a = NSWindowAnimationBehaviorNone;
309
- if ([target respondsToSelector: @selector (animationBehavior )]) {
310
- a = [target animationBehavior ];
311
- [target setAnimationBehavior: NSWindowAnimationBehaviorNone];
312
- }
313
- #endif
292
+ // On Mac OS X 10.7 windows animate when makeKeyAndOrderFront: is called.
293
+ // This is distracting here, so disable the animation and restore animation
294
+ // behavior after calling makeKeyAndOrderFront:.
295
+ NSWindowAnimationBehavior winAnimBehavior = [target animationBehavior ];
296
+ [target setAnimationBehavior: NSWindowAnimationBehaviorNone];
297
+
298
+ // Note: Currently, there is a possibility that the full-screen window is
299
+ // in a different Space from the original window. This could happen if the
300
+ // full-screen was manually dragged to another Space in Mission Control.
301
+ // If that's the case, the original window will be restored to the original
302
+ // Space it was in, which may not be what the user intended.
303
+ //
304
+ // We don't address this for a few reasons:
305
+ // 1. This is a niche case that wouldn't matter 99% of the time.
306
+ // 2. macOS does not expose explicit control over Spaces in the public APIs.
307
+ // We don't have a way to directly determine which space each window is
308
+ // on, other than just detecting whether it's on the active space. We
309
+ // also don't have a way to place the window on another Space
310
+ // programmatically. We could move the window to the active Space by
311
+ // changing collectionBehavior to CanJoinAllSpace or MoveToActiveSpace,
312
+ // and after it's moved, unset the collectionBehavior. This is tricky to
313
+ // do because the move doesn't happen immediately. The window manager
314
+ // takes a few cycles before it moves the window over to the active
315
+ // space and we would need to continually check onActiveSpace to know
316
+ // when that happens. This leads to a fair bit of window management
317
+ // complexity.
318
+ // 3. Even if we implement the above, it could still lead to unintended
319
+ // behaviors. If during the window restore process, the user navigated
320
+ // to another Space (e.g. a popup dialog box), it's not necessarily the
321
+ // correct behavior to put the restored window there. What we want is to
322
+ // query the exact Space the full-screen window is on and place the
323
+ // original window there, but there's no public APIs to do that.
314
324
315
325
[target makeKeyAndOrderFront: self ];
316
326
317
- #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
318
- // HACK! Restore animation behavior.
319
- if (NSWindowAnimationBehaviorNone != a)
320
- [target setAnimationBehavior: a];
321
- #endif
322
-
323
- // Restore collection behavior (see hack above).
324
- [target setCollectionBehavior: wcb];
327
+ // Restore animation behavior.
328
+ if (NSWindowAnimationBehaviorNone != winAnimBehavior)
329
+ [target setAnimationBehavior: winAnimBehavior];
325
330
326
331
// ...but we don't want a focus gained message either, so don't set this
327
332
// sooner
@@ -365,16 +370,11 @@ - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
365
370
// hidden/displayed.
366
371
ASLogDebug (@" Screen unplugged / resolution changed" );
367
372
368
- NSScreen *screen = [target screen ];
369
- if (!screen) {
370
- // Paranoia: if window we originally used for full-screen is gone, try
371
- // screen window is on now, and failing that (not sure this can happen)
372
- // use main screen.
373
- screen = [self screen ];
374
- if (!screen)
375
- screen = [NSScreen mainScreen ];
373
+ NSScreen *screen = [self screen ];
374
+ if (screen == nil ) {
375
+ // See windowDidMove for more explanations.
376
+ screen = [NSScreen mainScreen ];
376
377
}
377
-
378
378
// Ensure the full-screen window is still covering the entire screen and
379
379
// then resize view according to 'fuopt'.
380
380
[self setFrame: [screen frame ] display: NO ];
@@ -549,12 +549,24 @@ - (void)windowDidMove:(NSNotification *)notification
549
549
if (state != InFullScreen)
550
550
return ;
551
551
552
- // Window may move as a result of being dragged between Spaces .
552
+ // Window may move as a result of being dragged between screens .
553
553
ASLogDebug (@" Full-screen window moved, ensuring it covers the screen..." );
554
554
555
+ NSScreen *screen = [self screen ];
556
+ if (screen == nil ) {
557
+ // If for some reason this window got moved to an area not associated
558
+ // with a screen just fall back to a main one. Otherwise this window
559
+ // will be stuck on a no-man's land and the user will have no way to
560
+ // use it. One known way this could happen is when the user has a
561
+ // larger monitor on the left (where MacVim was started) and a smaller
562
+ // on the right. The user then drag the full screen window to the right
563
+ // screen in Mission Control. macOS will refuse to place the window
564
+ // because it is too big so it gets placed out of bounds.
565
+ screen = [NSScreen mainScreen ];
566
+ }
555
567
// Ensure the full-screen window is still covering the entire screen and
556
568
// then resize view according to 'fuopt'.
557
- [self setFrame: [[ self screen ] frame ] display: NO ];
569
+ [self setFrame: [screen frame ] display: NO ];
558
570
}
559
571
560
572
@end // MMFullScreenWindow (Private)
0 commit comments