// Copyright (c) 2012 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/chrome_event_processing_window.h" #include "base/logging.h" #import "chrome/browser/ui/cocoa/browser_command_executor.h" #import "chrome/browser/ui/cocoa/browser_window_controller_private.h" #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" #include "chrome/browser/global_keyboard_shortcuts_mac.h" #import "content/public/browser/render_widget_host_view_mac_base.h" typedef int (*KeyToCommandMapper)(bool, bool, bool, bool, int, unichar); @interface ChromeEventProcessingWindow () // Duplicate the given key event, but changing the associated window. - (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event; @end @implementation ChromeEventProcessingWindow - (BOOL)handleExtraKeyboardShortcut:(NSEvent*)event fromTable: (KeyToCommandMapper)commandForKeyboardShortcut { // Extract info from |event|. NSUInteger modifers = [event modifierFlags]; const bool cmdKey = modifers & NSCommandKeyMask; const bool shiftKey = modifers & NSShiftKeyMask; const bool cntrlKey = modifers & NSControlKeyMask; const bool optKey = modifers & NSAlternateKeyMask; const unichar keyCode = [event keyCode]; const unichar keyChar = KeyCharacterForEvent(event); int cmdNum = commandForKeyboardShortcut(cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar); if (cmdNum != -1) { id executor = [self delegate]; // A bit of sanity. DCHECK([executor conformsToProtocol:@protocol(BrowserCommandExecutor)]); DCHECK([executor respondsToSelector:@selector(executeCommand:)]); [executor executeCommand:cmdNum]; return YES; } return NO; } - (BOOL)handleExtraWindowKeyboardShortcut:(NSEvent*)event { return [self handleExtraKeyboardShortcut:event fromTable:CommandForWindowKeyboardShortcut]; } - (BOOL)handleDelayedWindowKeyboardShortcut:(NSEvent*)event { return [self handleExtraKeyboardShortcut:event fromTable:CommandForDelayedWindowKeyboardShortcut]; } - (BOOL)handleExtraBrowserKeyboardShortcut:(NSEvent*)event { return [self handleExtraKeyboardShortcut:event fromTable:CommandForBrowserKeyboardShortcut]; } - (BOOL)performKeyEquivalent:(NSEvent*)event { if (redispatchingEvent_) return NO; NSWindow* window = event.window; if (window) { BrowserWindowController* controller = [window windowController]; if ([controller respondsToSelector:@selector(handledByExtensionCommand:)]) { if ([controller handledByExtensionCommand:event]) return YES; } } // Give the web site a chance to handle the event. If it doesn't want to // handle it, it will call us back with one of the |handle*| methods above. NSResponder* r = [self firstResponder]; if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)]) return [r performKeyEquivalent:event]; // If the delegate does not implement the BrowserCommandExecutor protocol, // then we don't need to handle browser specific shortcut keys. if (![[self delegate] conformsToProtocol:@protocol(BrowserCommandExecutor)]) return [super performKeyEquivalent:event]; // Handle per-window shortcuts like cmd-1, but do not handle browser-level // shortcuts like cmd-left (else, cmd-left would do history navigation even // if e.g. the Omnibox has focus). if ([self handleExtraWindowKeyboardShortcut:event]) return YES; if ([super performKeyEquivalent:event]) return YES; // Handle per-window shortcuts like Esc after giving everybody else a chance // to handle them return [self handleDelayedWindowKeyboardShortcut:event]; } - (BOOL)redispatchKeyEvent:(NSEvent*)event { DCHECK(event); NSEventType eventType = [event type]; if (eventType != NSKeyDown && eventType != NSKeyUp && eventType != NSFlagsChanged) { NOTREACHED(); return YES; // Pretend it's been handled in an effort to limit damage. } // Ordinarily, the event's window should be this window. However, when // switching between normal and fullscreen mode, we switch out the window, and // the event's window might be the previous window (or even an earlier one if // the renderer is running slowly and several mode switches occur). In this // rare case, we synthesize a new key event so that its associate window // (number) is our own. if ([event window] != self) event = [self keyEventForWindow:self fromKeyEvent:event]; // Redispatch the event. eventHandled_ = YES; redispatchingEvent_ = YES; [NSApp sendEvent:event]; redispatchingEvent_ = NO; // If the event was not handled by [NSApp sendEvent:], the sendEvent: // method below will be called, and because |redispatchingEvent_| is YES, // |eventHandled_| will be set to NO. return eventHandled_; } - (void)sendEvent:(NSEvent*)event { if (!redispatchingEvent_) [super sendEvent:event]; else eventHandled_ = NO; } - (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event { NSEventType eventType = [event type]; // Convert the event's location from the original window's coordinates into // our own. NSPoint eventLoc = [event locationInWindow]; eventLoc = [[event window] convertBaseToScreen:eventLoc]; eventLoc = [self convertScreenToBase:eventLoc]; // Various things *only* apply to key down/up. BOOL eventIsARepeat = NO; NSString* eventCharacters = nil; NSString* eventUnmodCharacters = nil; if (eventType == NSKeyDown || eventType == NSKeyUp) { eventIsARepeat = [event isARepeat]; eventCharacters = [event characters]; eventUnmodCharacters = [event charactersIgnoringModifiers]; } // This synthesis may be slightly imperfect: we provide nil for the context, // since I (viettrungluu) am sceptical that putting in the original context // (if one is given) is valid. return [NSEvent keyEventWithType:eventType location:eventLoc modifierFlags:[event modifierFlags] timestamp:[event timestamp] windowNumber:[window windowNumber] context:nil characters:eventCharacters charactersIgnoringModifiers:eventUnmodCharacters isARepeat:eventIsARepeat keyCode:[event keyCode]]; } @end // ChromeEventProcessingWindow