| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/ui/cocoa/fullscreen/fullscreen_menubar_tracker.h" |
| |
| #include <Carbon/Carbon.h> |
| #include <QuartzCore/QuartzCore.h> |
| |
| #include "base/mac/mac_util.h" |
| #import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller.h" |
| #include "ui/base/cocoa/appkit_utils.h" |
| |
| namespace { |
| |
| // The event kind value for a undocumented menubar show/hide Carbon event. |
| const CGFloat kMenuBarRevealEventKind = 2004; |
| |
| // TODO(crbug.com/40123289): Replace this with something that works |
| // on modern macOS versions. |
| OSStatus MenuBarRevealHandler(EventHandlerCallRef handler, |
| EventRef event, |
| void* context) { |
| FullscreenMenubarTracker* self = (__bridge FullscreenMenubarTracker*)context; |
| |
| // If Chrome has multiple fullscreen windows in their own space, the Handler |
| // becomes flaky and might start receiving kMenuBarRevealEventKind events |
| // from another space. Since the menubar in the another space is in either a |
| // shown or hidden state, it will give us a reveal fraction of 0.0 or 1.0. |
| // As such, we should ignore the kMenuBarRevealEventKind event if it gives |
| // us a fraction of 0.0 or 1.0, and rely on kEventMenuBarShown and |
| // kEventMenuBarHidden to set these values. |
| if (GetEventKind(event) == kMenuBarRevealEventKind) { |
| CGFloat revealFraction = 0; |
| GetEventParameter(event, FOUR_CHAR_CODE('rvlf'), typeCGFloat, |
| /*outActualType=*/nullptr, sizeof(CGFloat), |
| /*outActualSize=*/nullptr, &revealFraction); |
| if (revealFraction > 0.0 && revealFraction < 1.0) { |
| [self setMenubarProgress:revealFraction]; |
| } |
| } else if (GetEventKind(event) == kEventMenuBarShown) { |
| [self setMenubarProgress:1.0]; |
| } else { |
| [self setMenubarProgress:0.0]; |
| } |
| |
| return CallNextEventHandler(handler, event); |
| } |
| |
| } // end namespace |
| |
| @interface FullscreenMenubarTracker () |
| |
| // Returns YES if the mouse is on the same screen as the window. |
| - (BOOL)isMouseOnScreen; |
| |
| @end |
| |
| @implementation FullscreenMenubarTracker { |
| FullscreenToolbarController* __weak _controller; |
| |
| // A Carbon event handler that tracks the revealed fraction of the menubar. |
| EventHandlerRef _menubarTrackingHandler; |
| } |
| |
| @synthesize state = _state; |
| @synthesize menubarFraction = _menubarFraction; |
| |
| - (instancetype)initWithFullscreenToolbarController: |
| (FullscreenToolbarController*)controller { |
| if ((self = [super init])) { |
| _controller = controller; |
| _state = FullscreenMenubarState::HIDDEN; |
| |
| // Install the Carbon event handler for the menubar show, hide and |
| // undocumented reveal event. |
| EventTypeSpec eventSpecs[3]; |
| |
| eventSpecs[0].eventClass = kEventClassMenu; |
| eventSpecs[0].eventKind = kMenuBarRevealEventKind; |
| |
| eventSpecs[1].eventClass = kEventClassMenu; |
| eventSpecs[1].eventKind = kEventMenuBarShown; |
| |
| eventSpecs[2].eventClass = kEventClassMenu; |
| eventSpecs[2].eventKind = kEventMenuBarHidden; |
| |
| InstallApplicationEventHandler( |
| NewEventHandlerUPP(&MenuBarRevealHandler), std::size(eventSpecs), |
| eventSpecs, (__bridge void*)self, &_menubarTrackingHandler); |
| |
| // Register for Active Space change notifications. |
| [NSWorkspace.sharedWorkspace.notificationCenter |
| addObserver:self |
| selector:@selector(activeSpaceDidChange:) |
| name:NSWorkspaceActiveSpaceDidChangeNotification |
| object:nil]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| RemoveEventHandler(_menubarTrackingHandler); |
| [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self]; |
| } |
| |
| - (CGFloat)menubarFraction { |
| return _menubarFraction; |
| } |
| |
| - (void)setMenubarProgress:(CGFloat)progress { |
| if (![_controller isInAnyFullscreenMode] || |
| [_controller isFullscreenTransitionInProgress]) { |
| return; |
| } |
| |
| // If the menubarFraction increases, check if we are in the right screen |
| // so that the toolbar is not revealed on the wrong screen. |
| if (![self isMouseOnScreen] && progress > _menubarFraction) { |
| return; |
| } |
| |
| // Ignore the menubarFraction changes if the Space is inactive. |
| if (!_controller.window.onActiveSpace) { |
| return; |
| } |
| |
| if (ui::IsCGFloatEqual(progress, 1.0)) { |
| _state = FullscreenMenubarState::SHOWN; |
| } else if (ui::IsCGFloatEqual(progress, 0.0)) { |
| _state = FullscreenMenubarState::HIDDEN; |
| } else if (progress < _menubarFraction) { |
| _state = FullscreenMenubarState::HIDING; |
| } else if (progress > _menubarFraction) { |
| _state = FullscreenMenubarState::SHOWING; |
| } |
| |
| _menubarFraction = progress; |
| [_controller layoutToolbar]; |
| // AppKit drives the menu bar animation from a nested run loop. Flush |
| // explicitly so that Chrome's UI updates during the animation. |
| [CATransaction flush]; |
| } |
| |
| - (BOOL)isMouseOnScreen { |
| return NSMouseInRect(NSEvent.mouseLocation, _controller.window.screen.frame, |
| false); |
| } |
| |
| - (void)activeSpaceDidChange:(NSNotification*)notification { |
| _menubarFraction = 0.0; |
| _state = FullscreenMenubarState::HIDDEN; |
| [_controller layoutToolbar]; |
| } |
| |
| @end |