blob: 293e589795c4f9c52a9d602ba5cdc92b82484d5c [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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"
#include "base/stl_util.h"
#import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller.h"
#import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_visibility_lock_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;
OSStatus MenuBarRevealHandler(EventHandlerCallRef handler,
EventRef event,
void* context) {
FullscreenMenubarTracker* self =
static_cast<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, NULL,
sizeof(CGFloat), NULL, &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 () {
FullscreenToolbarController* controller_; // weak
id<FullscreenToolbarContextDelegate> delegate_; // weak
// A Carbon event handler that tracks the revealed fraction of the menubar.
EventHandlerRef menubarTrackingHandler_;
}
// Returns YES if the mouse is on the same screen as the window.
- (BOOL)isMouseOnScreen;
@end
@implementation FullscreenMenubarTracker
@synthesize state = state_;
@synthesize menubarFraction = menubarFraction_;
- (instancetype)initWithFullscreenToolbarController:
(FullscreenToolbarController*)controller {
if ((self = [super init])) {
controller_ = controller;
delegate_ = [controller delegate];
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),
base::size(eventSpecs), eventSpecs, 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];
[super dealloc];
}
- (CGFloat)menubarFraction {
return menubarFraction_;
}
- (void)setMenubarProgress:(CGFloat)progress {
if (![delegate_ isInAnyFullscreenMode] ||
[delegate_ 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 (![[delegate_ window] isOnActiveSpace])
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],
[[delegate_ window] screen].frame, false);
}
- (void)activeSpaceDidChange:(NSNotification*)notification {
menubarFraction_ = 0.0;
state_ = FullscreenMenubarState::HIDDEN;
[[controller_ visibilityLockController] releaseToolbarVisibilityForOwner:self
withAnimation:NO];
[controller_ layoutToolbar];
}
@end