From 62fae1ddfa23a4a3885f9b55d63dd59491768bad Mon Sep 17 00:00:00 2001
From: Ken Thomases <ken@codeweavers.com>
Date: Mon, 7 Oct 2013 23:53:34 -0500
Subject: winemac: Implement support for Cocoa-style full-screen.
To: wine-patches <wine-patches@winehq.org>

Based in large part on a patch submitted by Kevin Eaves.
---
 dlls/winemac.drv/cocoa_app.m    |   5 ++
 dlls/winemac.drv/cocoa_window.h |   5 ++
 dlls/winemac.drv/cocoa_window.m | 169 +++++++++++++++++++++++++++++++++++-----
 dlls/winemac.drv/event.c        |   2 +-
 dlls/winemac.drv/macdrv.h       |   2 +-
 dlls/winemac.drv/macdrv_cocoa.h |   3 +-
 dlls/winemac.drv/window.c       |   9 ++-
 7 files changed, 171 insertions(+), 24 deletions(-)

diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m
index b0555df..5e8e5d1 100644
--- a/dlls/winemac.drv/cocoa_app.m
+++ b/dlls/winemac.drv/cocoa_app.m
@@ -239,6 +239,11 @@ - (void) transformProcessToForeground
             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
+            if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
+            {
+                item = [submenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
+                [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
+            }
             [submenu addItem:[NSMenuItem separatorItem]];
             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
             item = [[[NSMenuItem alloc] init] autorelease];
diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h
index 210299e..d9130ac 100644
--- a/dlls/winemac.drv/cocoa_window.h
+++ b/dlls/winemac.drv/cocoa_window.h
@@ -58,6 +58,11 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
     NSSize savedContentMinSize;
     NSSize savedContentMaxSize;
 
+    BOOL enteringFullScreen;
+    BOOL exitingFullScreen;
+    NSRect nonFullscreenFrame;
+    NSTimeInterval enteredFullScreenTime;
+
     BOOL ignore_windowDeminiaturize;
     BOOL fakingClose;
 }
diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m
index a48b691..ea636a9 100644
--- a/dlls/winemac.drv/cocoa_window.m
+++ b/dlls/winemac.drv/cocoa_window.m
@@ -28,6 +28,20 @@
 #import "cocoa_opengl.h"
 
 
+#if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
+enum {
+    NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
+    NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8,
+    NSWindowFullScreenButton = 7,
+    NSFullScreenWindowMask = 1 << 14,
+};
+
+@interface NSWindow (WineFullScreenExtensions)
+    - (void) toggleFullScreen:(id)sender;
+@end
+#endif
+
+
 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
 enum {
     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
@@ -565,6 +579,39 @@ - (void) adjustFeaturesForState
             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
         if (style & NSResizableWindowMask)
             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
+        if ([self respondsToSelector:@selector(toggleFullScreen:)])
+        {
+            if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
+                [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
+        }
+    }
+
+    - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
+    {
+        if ([self respondsToSelector:@selector(toggleFullScreen:)])
+        {
+            NSUInteger style = [self styleMask];
+
+            if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
+                style & NSResizableWindowMask && !(style & NSUtilityWindowMask))
+            {
+                behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+                behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
+            }
+            else
+            {
+                behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
+                behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
+                if (style & NSFullScreenWindowMask)
+                    [self toggleFullScreen:nil];
+            }
+        }
+
+        if (behavior != [self collectionBehavior])
+        {
+            [self setCollectionBehavior:behavior];
+            [self adjustFeaturesForState];
+        }
     }
 
     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
@@ -588,6 +635,7 @@ - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
             }
             [self setStyleMask:newStyle];
+            [self adjustFullScreenBehavior:[self collectionBehavior]];
         }
 
         [self adjustFeaturesForState];
@@ -630,6 +678,19 @@ - (NSInteger) minimumLevelForActive:(BOOL)active
         return level;
     }
 
+    - (void) postDidUnminimizeEvent
+    {
+        macdrv_event* event;
+
+        /* Coalesce events by discarding any previous ones still in the queue. */
+        [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
+                               forWindow:self];
+
+        event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
+        [queue postEvent:event];
+        macdrv_release_event(event);
+    }
+
     - (void) setMacDrvState:(const struct macdrv_window_state*)state
     {
         NSWindowCollectionBehavior behavior;
@@ -685,15 +746,25 @@ - (void) setMacDrvState:(const struct macdrv_window_state*)state
             if ([self isOrderedIn])
                 [NSApp addWindowsItem:self title:[self title] filename:NO];
         }
-        [self setCollectionBehavior:behavior];
+        [self adjustFullScreenBehavior:behavior];
 
         if (state->minimized_valid)
         {
+            BOOL discardUnminimize = TRUE;
+
             pendingMinimize = FALSE;
             if (state->minimized && ![self isMiniaturized])
             {
                 if ([self isVisible])
-                    [super miniaturize:nil];
+                {
+                    if ([self styleMask] & NSFullScreenWindowMask)
+                    {
+                        [self postDidUnminimizeEvent];
+                        discardUnminimize = FALSE;
+                    }
+                    else
+                        [super miniaturize:nil];
+                }
                 else
                     pendingMinimize = TRUE;
             }
@@ -703,9 +774,12 @@ - (void) setMacDrvState:(const struct macdrv_window_state*)state
                 [self deminiaturize:nil];
             }
 
-            /* Whatever events regarding minimization might have been in the queue are now stale. */
-            [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
-                                   forWindow:self];
+            if (discardUnminimize)
+            {
+                /* Whatever events regarding minimization might have been in the queue are now stale. */
+                [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
+                                       forWindow:self];
+            }
         }
     }
 
@@ -1058,7 +1132,7 @@ - (void) doOrderOut
     - (void) updateFullscreen
     {
         NSRect contentRect = [self contentRectForFrameRect:[self frame]];
-        BOOL nowFullscreen = (screen_covered_by_rect(contentRect, [NSScreen screens]) != nil);
+        BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
 
         if (nowFullscreen != fullscreen)
         {
@@ -1112,6 +1186,10 @@ - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
                     [self updateColorSpace];
                 }
 
+                if (!enteringFullScreen &&
+                    [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
+                    nonFullscreenFrame = frame;
+
                 [self updateFullscreen];
 
                 if (on_screen)
@@ -1327,6 +1405,8 @@ - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
 
         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
+        if ([menuItem action] == @selector(toggleFullScreen:) && self.disabled)
+            ret = NO;
 
         return ret;
     }
@@ -1362,6 +1442,12 @@ - (void) miniaturize:(id)sender
         macdrv_release_event(event);
     }
 
+    - (void) toggleFullScreen:(id)sender
+    {
+        if (!self.disabled)
+            [super toggleFullScreen:sender];
+    }
+
     // We normally use the generic/calibrated RGB color space for the window,
     // rather than the device color space, to avoid expensive color conversion
     // which slows down drawing.  However, for windows displaying OpenGL, having
@@ -1465,6 +1551,25 @@ - (void) flagsChanged:(NSEvent *)theEvent
     /*
      * ---------- NSWindowDelegate methods ----------
      */
+    - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
+    {
+        macdrv_query* query;
+        NSSize size;
+
+        query = macdrv_create_query();
+        query->type = QUERY_MIN_MAX_INFO;
+        query->window = (macdrv_window)[self retain];
+        [self.queue query:query timeout:0.5];
+        macdrv_release_query(query);
+
+        size = [self contentMaxSize];
+        if (proposedSize.width < size.width)
+            size.width = proposedSize.width;
+        if (proposedSize.height < size.height)
+            size.height = proposedSize.height;
+        return size;
+    }
+
     - (void)windowDidBecomeKey:(NSNotification *)notification
     {
         WineApplicationController* controller = [WineApplicationController sharedController];
@@ -1482,18 +1587,7 @@ - (void)windowDidDeminiaturize:(NSNotification *)notification
         WineApplicationController* controller = [WineApplicationController sharedController];
 
         if (!ignore_windowDeminiaturize)
-        {
-            macdrv_event* event;
-
-            /* Coalesce events by discarding any previous ones still in the queue. */
-            [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
-                                   forWindow:self];
-
-            event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
-            [queue postEvent:event];
-            macdrv_release_event(event);
-        }
-
+            [self postDidUnminimizeEvent];
         ignore_windowDeminiaturize = FALSE;
 
         [self becameEligibleParentOrChild];
@@ -1528,6 +1622,31 @@ - (void) windowDidEndLiveResize:(NSNotification *)notification
         self.liveResizeDisplayTimer = nil;
     }
 
+    - (void) windowDidEnterFullScreen:(NSNotification*)notification
+    {
+        enteringFullScreen = FALSE;
+        enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
+    }
+
+    - (void) windowDidExitFullScreen:(NSNotification*)notification
+    {
+        exitingFullScreen = FALSE;
+        [self setFrame:nonFullscreenFrame display:YES animate:NO];
+        [self windowDidResize:nil];
+    }
+
+    - (void) windowDidFailToEnterFullScreen:(NSWindow*)window
+    {
+        enteringFullScreen = FALSE;
+        enteredFullScreenTime = 0;
+    }
+
+    - (void) windowDidFailToExitFullScreen:(NSWindow*)window
+    {
+        exitingFullScreen = FALSE;
+        [self windowDidResize:nil];
+    }
+
     - (void)windowDidMiniaturize:(NSNotification *)notification
     {
         if (fullscreen && [self isOnActiveSpace])
@@ -1555,6 +1674,8 @@ - (void)windowDidResize:(NSNotification *)notification
         macdrv_event* event;
         NSRect frame = [self contentRectForFrameRect:[self frame]];
 
+        if (exitingFullScreen) return;
+
         if (self.disabled)
         {
             [self setContentMinSize:frame.size];
@@ -1569,6 +1690,7 @@ - (void)windowDidResize:(NSNotification *)notification
 
         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
         event->window_frame_changed.frame = NSRectToCGRect(frame);
+        event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0;
         [queue postEvent:event];
         macdrv_release_event(event);
 
@@ -1603,6 +1725,17 @@ - (void) windowWillClose:(NSNotification*)notification
         [latentChildWindows removeAllObjects];
     }
 
+    - (void) windowWillEnterFullScreen:(NSNotification*)notification
+    {
+        enteringFullScreen = TRUE;
+        nonFullscreenFrame = [self frame];
+    }
+
+    - (void) windowWillExitFullScreen:(NSNotification*)notification
+    {
+        exitingFullScreen = TRUE;
+    }
+
     - (void)windowWillMiniaturize:(NSNotification *)notification
     {
         [self becameIneligibleParentOrChild];
diff --git a/dlls/winemac.drv/event.c b/dlls/winemac.drv/event.c
index 2b7adb0..5803ac7 100644
--- a/dlls/winemac.drv/event.c
+++ b/dlls/winemac.drv/event.c
@@ -246,7 +246,7 @@ void macdrv_handle_event(const macdrv_event *event)
         macdrv_window_did_unminimize(hwnd);
         break;
     case WINDOW_FRAME_CHANGED:
-        macdrv_window_frame_changed(hwnd, event->window_frame_changed.frame);
+        macdrv_window_frame_changed(hwnd, event);
         break;
     case WINDOW_GOT_FOCUS:
         macdrv_window_got_focus(hwnd, event);
diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h
index 3c56c35..860d29d 100644
--- a/dlls/winemac.drv/macdrv.h
+++ b/dlls/winemac.drv/macdrv.h
@@ -155,7 +155,7 @@ static inline RECT rect_from_cgrect(CGRect cgrect)
 extern void macdrv_handle_event(const macdrv_event *event) DECLSPEC_HIDDEN;
 
 extern void macdrv_window_close_requested(HWND hwnd) DECLSPEC_HIDDEN;
-extern void macdrv_window_frame_changed(HWND hwnd, CGRect frame) DECLSPEC_HIDDEN;
+extern void macdrv_window_frame_changed(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
 extern void macdrv_window_got_focus(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
 extern void macdrv_window_lost_focus(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
 extern void macdrv_app_deactivated(void) DECLSPEC_HIDDEN;
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h
index 1a38b56..60bd04d 100644
--- a/dlls/winemac.drv/macdrv_cocoa.h
+++ b/dlls/winemac.drv/macdrv_cocoa.h
@@ -271,7 +271,8 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
             macdrv_status_item  item;
         }                                           status_item_mouse_move;
         struct {
-            CGRect frame;
+            CGRect  frame;
+            int     fullscreen;
         }                                           window_frame_changed;
         struct {
             unsigned long   serial;
diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c
index b49e347..007d5b0 100644
--- a/dlls/winemac.drv/window.c
+++ b/dlls/winemac.drv/window.c
@@ -1654,7 +1654,7 @@ void macdrv_window_close_requested(HWND hwnd)
  *
  * Handler for WINDOW_FRAME_CHANGED events.
  */
-void macdrv_window_frame_changed(HWND hwnd, CGRect frame)
+void macdrv_window_frame_changed(HWND hwnd, const macdrv_event *event)
 {
     struct macdrv_win_data *data;
     RECT rect;
@@ -1674,9 +1674,10 @@ void macdrv_window_frame_changed(HWND hwnd, CGRect frame)
 
     parent = GetAncestor(hwnd, GA_PARENT);
 
-    TRACE("win %p/%p new Cocoa frame %s\n", hwnd, data->cocoa_window, wine_dbgstr_cgrect(frame));
+    TRACE("win %p/%p new Cocoa frame %s\n", hwnd, data->cocoa_window,
+          wine_dbgstr_cgrect(event->window_frame_changed.frame));
 
-    rect = rect_from_cgrect(frame);
+    rect = rect_from_cgrect(event->window_frame_changed.frame);
     macdrv_mac_to_window_rect(data, &rect);
     MapWindowPoints(0, parent, (POINT *)&rect, 2);
 
@@ -1699,6 +1700,8 @@ void macdrv_window_frame_changed(HWND hwnd, CGRect frame)
 
     release_win_data(data);
 
+    if (event->window_frame_changed.fullscreen)
+        flags |= SWP_NOSENDCHANGING;
     if (!(flags & SWP_NOSIZE) || !(flags & SWP_NOMOVE))
         SetWindowPos(hwnd, 0, rect.left, rect.top, width, height, flags);
 }
-- 
1.8.3.4

