From 6e922b8005a880433851747c43fa608ddfb753bf Mon Sep 17 00:00:00 2001
From: ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>
Date: Thu, 30 Jan 2025 11:14:17 +0100
Subject: [PATCH] macOS: Fix "Full screen broken on macOS in FLTK 1.3.10
 (regression)" (#1192)

FLTK now supports 2 types of fullscreen modes under macOS:
1) normal macOS fullscreen mode where the menubar and titlebar appear when mouse is moved to top of screen
and the window covers one screen only;
2) multiple-screen mode that requires calling Fl_Window::fullscreen_screens() before.

This commit fixes all possible transitions between these fullscreen modes and normal window mode.
---
 src/Fl_Window_Driver.H                     |  3 +-
 src/Fl_Window_fullscreen.cxx               |  4 +-
 src/Fl_cocoa.mm                            | 72 ++++++++++++++++++++--
 src/drivers/Cocoa/Fl_Cocoa_Window_Driver.H |  3 +-
 4 files changed, 73 insertions(+), 9 deletions(-)

diff --git a/src/Fl_Window_Driver.H b/src/Fl_Window_Driver.H
index cec60a2da..865c57af9 100644
--- a/src/Fl_Window_Driver.H
+++ b/src/Fl_Window_Driver.H
@@ -2,7 +2,7 @@
 // A base class for platform specific window handling code
 // for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 2010-2024 by Bill Spitzak and others.
+// Copyright 2010-2025 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -152,6 +152,7 @@ public:
   virtual void unmap() {}
   virtual void fullscreen_on() {}
   virtual void fullscreen_off(int /*X*/, int /*Y*/, int /*W*/, int /*H*/) {}
+  virtual void fullscreen_screens(bool /*on_off*/) {}
   virtual void maximize();
   virtual void un_maximize();
   virtual bool maximize_needs_hide() { return false; }
diff --git a/src/Fl_Window_fullscreen.cxx b/src/Fl_Window_fullscreen.cxx
index e464a9fba..79e57a0c8 100644
--- a/src/Fl_Window_fullscreen.cxx
+++ b/src/Fl_Window_fullscreen.cxx
@@ -1,7 +1,7 @@
 //
 // Fullscreen window support for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2015 by Bill Spitzak and others.
+// Copyright 1998-2025 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -75,11 +75,13 @@ void Fl_Window::fullscreen_screens(int top, int bottom, int left, int right) {
     fullscreen_screen_bottom = -1;
     fullscreen_screen_left = -1;
     fullscreen_screen_right = -1;
+    pWindowDriver->fullscreen_screens(false);
   } else {
     fullscreen_screen_top = top;
     fullscreen_screen_bottom = bottom;
     fullscreen_screen_left = left;
     fullscreen_screen_right = right;
+    pWindowDriver->fullscreen_screens(true);
   }
 
   if (shown() && fullscreen_active())
diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm
index fcd691aa0..8095447a8 100644
--- a/src/Fl_cocoa.mm
+++ b/src/Fl_cocoa.mm
@@ -1,7 +1,7 @@
 //
 // macOS-Cocoa specific code for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2024 by Bill Spitzak and others.
+// Copyright 1998-2025 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -557,6 +557,7 @@ void Fl_Cocoa_Screen_Driver::breakMacEventLoop()
 - (NSPoint)convertBaseToScreen:(NSPoint)aPoint;
 #endif
 - (NSBitmapImageRep*)rect_to_NSBitmapImageRep:(Fl_Rect*)r;
+- (void)makeKeyWindow;
 @end
 
 
@@ -795,6 +796,11 @@ void Fl_Cocoa_Screen_Driver::breakMacEventLoop()
 - (NSBitmapImageRep*)rect_to_NSBitmapImageRep:(Fl_Rect*)r {
   return rect_to_NSBitmapImageRep(w, r->x(), r->y(), r->w(), r->h());
 }
+- (void)makeKeyWindow {
+  // Necessary in this scenario at least:
+  // transition of a subwindow-containing window from multiscreen-fullscreen mode to normal mode.
+  if ([self canBecomeKeyWindow]) [super makeKeyWindow];
+}
 @end
 
 @interface FLApplication : NSObject
@@ -1437,7 +1443,11 @@ static FLWindowDelegate *flwindowdelegate_instance = nil;
   FLWindow *nsw = (FLWindow*)[notif object];
   Fl_Window *w = [nsw getFl_Window];
   /* Restore previous fullscreen level */
-  if (w->fullscreen_active() && fl_mac_os_version < 100700) {
+  if (w->fullscreen_active() && (fl_mac_os_version < 100700
+#  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+                                 || !(nsw.styleMask & NSWindowStyleMaskFullScreen)
+#endif
+      )) {
     [nsw setLevel:NSStatusWindowLevel];
     fixup_window_levels();
   }
@@ -3278,10 +3288,23 @@ void Fl_Cocoa_Window_Driver::makeWindow()
   [pool release];
 }
 
+
+static BOOL fullscreen_screen_border = NO; // YES means the multi-screened window had a border before
+
+static NSUInteger calc_win_style(Fl_Window *win);
+
+
 void Fl_Cocoa_Window_Driver::fullscreen_on() {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
+  bool has_border = pWindow->border();
+  if (fl_mac_os_version >= 100700 && fullscreen_screen_top() >= 0 && has_border) {
+    fullscreen_screen_border = YES;
+    has_border = false;
+  }
+#endif
   pWindow->_set_fullscreen();
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
-  if (fl_mac_os_version >= 100700 && pWindow->border()) {
+  if (fl_mac_os_version >= 100700 && has_border) {
 #  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
     NSWindow *nswin = fl_xid(pWindow);
 #    if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
@@ -3290,10 +3313,29 @@ void Fl_Cocoa_Window_Driver::fullscreen_on() {
       if (active_tab) nswin = active_tab;
     }
 #    endif
+    if (fullscreen_screen_border) { // from "All Screens" fullscreen to single-screen fullscreen
+      pWindow->_clear_fullscreen();
+      [nswin setLevel:NSNormalWindowLevel];
+      [nswin setStyleMask:calc_win_style(pWindow)]; //10.6
+      pWindow->_set_fullscreen();
+    }
     [nswin toggleFullScreen:nil];
 #  endif
   } else if (fl_mac_os_version >= 100600) {
     FLWindow *nswin = fl_xid(pWindow);
+#  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+    if (fl_mac_os_version >= 100700 && (nswin.styleMask & NSWindowStyleMaskFullScreen)) {
+      // from single-screen fullscreen to "All Screens" fullscreen
+      [nswin toggleFullScreen:nil];
+      if (*no_fullscreen_w() == 0) {
+        *no_fullscreen_x() = x();
+        *no_fullscreen_y() = y();
+        *no_fullscreen_w() = w();
+        *no_fullscreen_h() = h();
+      }
+      pWindow->_set_fullscreen();
+    }
+#endif
     [nswin setStyleMask:NSWindowStyleMaskBorderless]; // 10.6
     if ([nswin isKeyWindow]) {
       if ([nswin level] != NSStatusWindowLevel) {
@@ -3373,11 +3415,15 @@ static void restore_window_title_and_icon(Fl_Window *pWindow, NSImage *icon) {
 #endif
 
 void Fl_Cocoa_Window_Driver::fullscreen_off(int X, int Y, int W, int H) {
+  NSWindow *nswin = fl_xid(pWindow);
   pWindow->_clear_fullscreen();
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
-  if (fl_mac_os_version >= 100700 && pWindow->border()) {
+  if (fl_mac_os_version >= 100700
+#  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+      && ([nswin styleMask] & NSWindowStyleMaskFullScreen)
+#  endif
+      ) {
 #  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
-    NSWindow *nswin = fl_xid(pWindow);
 #    if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
     if (fl_mac_os_version >= 101300) {
       NSWindow *active_tab = [[nswin tabGroup] selectedWindow];
@@ -3385,9 +3431,10 @@ void Fl_Cocoa_Window_Driver::fullscreen_off(int X, int Y, int W, int H) {
     }
 #    endif
     [nswin toggleFullScreen:nil];
+    pWindow->resize(*no_fullscreen_x(), *no_fullscreen_y(), *no_fullscreen_w(), *no_fullscreen_h());
 #  endif
   } else if (fl_mac_os_version >= 100600) {
-    FLWindow *nswin = fl_xid(pWindow);
+    // Transition from multi-screen fullscreen mode to normal mode
     NSInteger level = NSNormalWindowLevel;
     if (pWindow->modal()) level = modal_window_level();
     else if (pWindow->non_modal()) level = non_modal_window_level();
@@ -3413,8 +3460,21 @@ void Fl_Cocoa_Window_Driver::fullscreen_off(int X, int Y, int W, int H) {
     pWindow->show();
   }
   Fl::handle(FL_FULLSCREEN, pWindow);
+  fullscreen_screen_border = NO;
 }
 
+
+void Fl_Cocoa_Window_Driver::fullscreen_screens(bool on_off) {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+  if (fl_mac_os_version >= 100700) {
+    FLWindow *xid = fl_mac_xid(pWindow);
+    if (on_off) xid.collectionBehavior |= NSWindowCollectionBehaviorFullScreenNone;
+    else xid.collectionBehavior &= ~NSWindowCollectionBehaviorFullScreenNone;
+  }
+#endif
+}
+
+
 void Fl_Cocoa_Window_Driver::use_border() {
   if (!shown() || pWindow->parent()) return;
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
diff --git a/src/drivers/Cocoa/Fl_Cocoa_Window_Driver.H b/src/drivers/Cocoa/Fl_Cocoa_Window_Driver.H
index ef57a4607..c4d4e31da 100644
--- a/src/drivers/Cocoa/Fl_Cocoa_Window_Driver.H
+++ b/src/drivers/Cocoa/Fl_Cocoa_Window_Driver.H
@@ -2,7 +2,7 @@
 // Definition of Apple Cocoa window driver
 // for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 2010-2022 by Bill Spitzak and others.
+// Copyright 2010-2025 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -133,6 +133,7 @@ public:
   void unmap() FL_OVERRIDE;
   void fullscreen_on() FL_OVERRIDE;
   void fullscreen_off(int X, int Y, int W, int H) FL_OVERRIDE;
+  void fullscreen_screens(bool on_off) FL_OVERRIDE;
   void maximize() FL_OVERRIDE;
   void un_maximize() FL_OVERRIDE;
   void use_border() FL_OVERRIDE;
-- 
GitLab