From 222505da05a4112b69f5abf97889e08f7cfca0ec Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Mon, 20 Jul 2026 05:33:03 +0000
Subject: [PATCH 62/71] feat(macos): traditional full screen (View > Toggle
 Full Screen)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add borderless-window full screen, the 10.6-compatible approach (iTerm2 2014
does the same) — NOT 10.7 toggleFullScreen: nor NSView enterFullScreenMode:.

TerminalView toggleTraditionalFullScreen:
- enter: save the window frame + style mask, set NSBorderlessWindowMask, size
  the window to [[window screen] frame], and hide the dock + menu bar via
  [NSApp setPresentationOptions:AutoHideDock|AutoHideMenuBar]; make the window
  key and the view first responder.
- exit: restore presentation options (Default), the saved style mask, and the
  saved frame.
Backend-agnostic: the window resize drives setFrameSize:, which already updates
the GL context; the CPU blit follows the view bounds.

A ContourWindow (NSWindow subclass) returns YES from canBecomeKeyWindow/
canBecomeMainWindow so the borderless full-screen window can still take focus
(a plain NSWindow refuses key status when borderless). openTerminalWindow uses
it. Named toggleTraditionalFullScreen: (not toggleFullScreen:) to avoid the
10.7 NSWindow selector on newer systems. Menu: View > Toggle Full Screen,
Cmd+Ctrl+F, targeting the first responder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeG2jwYMX5gdvvtUnx3PJa
---
 src/contour_macos/TerminalView.h  |  6 +++++
 src/contour_macos/TerminalView.mm | 39 +++++++++++++++++++++++++++++++
 src/contour_macos/main.mm         | 33 ++++++++++++++++++++++----
 3 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/src/contour_macos/TerminalView.h b/src/contour_macos/TerminalView.h
index b99d65b9..fcfced9d 100644
--- a/src/contour_macos/TerminalView.h
+++ b/src/contour_macos/TerminalView.h
@@ -18,6 +18,9 @@
     NSOpenGLContext* _glContext; // non-nil only under the OpenGL backend
     int _renderMode;       // BridgeRenderMode, cached from the session
     BOOL _glInitialized;   // GL program/atlas created (once the context is current)
+    BOOL _fullScreen;      // in traditional (borderless) full-screen
+    NSRect _savedWindowFrame;    // window frame to restore on exit
+    NSUInteger _savedStyleMask;  // window style mask to restore on exit
 }
 
 // Creates the session (spawning a shell) sized to the given frame, using the given render backend
@@ -35,4 +38,7 @@
 // Requests a repaint on the main thread (safe to call from any thread).
 - (void)requestRedraw;
 
+// Toggles traditional (borderless-window) full screen. Menu action (View > Toggle Full Screen).
+- (void)toggleTraditionalFullScreen:(id)sender;
+
 @end
diff --git a/src/contour_macos/TerminalView.mm b/src/contour_macos/TerminalView.mm
index 08f2870f..c82a1acd 100644
--- a/src/contour_macos/TerminalView.mm
+++ b/src/contour_macos/TerminalView.mm
@@ -755,4 +755,43 @@ int cbVerifySshHostkey(void* userData, char const* host, int port, char const* f
     return NO;
 }
 
+// --- full screen (traditional / borderless-window, 10.6-compatible) ---
+
+// The iTerm2 (2014) approach, minimized: no 10.7 toggleTraditionalFullScreen:, no NSView enterFullScreenMode:.
+// Enter: save the window frame + style mask, switch the window to a borderless mask sized to the
+// whole screen, and auto-hide the dock + menu bar via presentation options. Exit: restore all three.
+// Backend-agnostic — the window resize drives setFrameSize:, which already updates the GL context.
+- (void)toggleTraditionalFullScreen:(id)sender
+{
+    (void) sender;
+    NSWindow* window = [self window];
+    if (!window)
+        return;
+
+    if (!_fullScreen)
+    {
+        _savedWindowFrame = [window frame];
+        _savedStyleMask = [window styleMask];
+
+        [window setStyleMask:NSBorderlessWindowMask];
+        [window setFrame:[[window screen] frame] display:YES];
+        [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock
+                                       | NSApplicationPresentationAutoHideMenuBar)];
+        // A borderless window is not key by default; make ours key and the view first responder so
+        // input keeps working.
+        [window makeKeyAndOrderFront:nil];
+        [window makeFirstResponder:self];
+        _fullScreen = YES;
+    }
+    else
+    {
+        [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+        [window setStyleMask:_savedStyleMask];
+        [window setFrame:_savedWindowFrame display:YES];
+        [window makeKeyAndOrderFront:nil];
+        [window makeFirstResponder:self];
+        _fullScreen = NO;
+    }
+}
+
 @end
diff --git a/src/contour_macos/main.mm b/src/contour_macos/main.mm
index 17aefcf2..696ed66f 100644
--- a/src/contour_macos/main.mm
+++ b/src/contour_macos/main.mm
@@ -11,6 +11,17 @@
 #include <stdlib.h>
 #include <string.h>
 
+// A window that can still become key/main while borderless, which is required for traditional
+// full screen (the window switches to NSBorderlessWindowMask). A plain NSWindow refuses key status
+// when borderless, which would make the full-screen window unfocusable.
+@interface ContourWindow: NSWindow
+@end
+
+@implementation ContourWindow
+- (BOOL)canBecomeKeyWindow { return YES; }
+- (BOOL)canBecomeMainWindow { return YES; }
+@end
+
 // Application delegate: owns the terminal windows and the menu bar, quits on last-window-close.
 @interface ContourAppDelegate: NSObject <NSApplicationDelegate>
 - (void)newWindow:(id)sender;
@@ -24,10 +35,10 @@
     NSRect const frame = NSMakeRect(0, 0, 720, 432);
     NSUInteger const style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
                              | NSResizableWindowMask;
-    NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
-                                                   styleMask:style
-                                                     backing:NSBackingStoreBuffered
-                                                       defer:NO];
+    NSWindow* window = [[ContourWindow alloc] initWithContentRect:frame
+                                                        styleMask:style
+                                                          backing:NSBackingStoreBuffered
+                                                            defer:NO];
     // The initial title is set by the view from the working directory once it is placed in the
     // window (viewDidMoveToWindow); this is just a placeholder for the brief moment before that.
     [window setTitle:@"contour"];
@@ -200,6 +211,20 @@ static void installMainMenu(NSApplication* app, ContourAppDelegate* delegate)
     [editMenu release];
     [editItem release];
 
+    // View menu. Toggle Full Screen targets the first responder (the focused TerminalView, which
+    // implements toggleTraditionalFullScreen:). Cmd+Ctrl+F is the conventional macOS shortcut; it is set as
+    // Cmd+F with the Control modifier mask.
+    NSMenuItem* viewItem = [[NSMenuItem alloc] init];
+    [menubar addItem:viewItem];
+    NSMenu* viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
+    [viewItem setSubmenu:viewMenu];
+    NSMenuItem* fsItem = [viewMenu addItemWithTitle:@"Toggle Full Screen"
+                                             action:@selector(toggleTraditionalFullScreen:)
+                                      keyEquivalent:@"f"];
+    [fsItem setKeyEquivalentModifierMask:(NSCommandKeyMask | NSControlKeyMask)];
+    [viewMenu release];
+    [viewItem release];
+
     // Window menu. setWindowsMenu: hands AppKit ownership: it appends an item per open window,
     // keeps the list and its checkmark in sync as windows open/close/gain focus, and orders them —
     // we only provide the standard Minimize/Zoom actions (first-responder/window selectors).
