1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" 6 7#include "base/logging.h" 8#import "chrome/browser/ui/cocoa/browser_command_executor.h" 9#import "chrome/browser/ui/cocoa/browser_window_controller_private.h" 10#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 11#include "chrome/browser/global_keyboard_shortcuts_mac.h" 12#import "content/public/browser/render_widget_host_view_mac_base.h" 13 14typedef int (*KeyToCommandMapper)(bool, bool, bool, bool, int, unichar); 15 16@interface ChromeEventProcessingWindow () 17// Duplicate the given key event, but changing the associated window. 18- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event; 19@end 20 21@implementation ChromeEventProcessingWindow 22 23- (BOOL)handleExtraKeyboardShortcut:(NSEvent*)event fromTable: 24 (KeyToCommandMapper)commandForKeyboardShortcut { 25 // Extract info from |event|. 26 NSUInteger modifers = [event modifierFlags]; 27 const bool cmdKey = modifers & NSCommandKeyMask; 28 const bool shiftKey = modifers & NSShiftKeyMask; 29 const bool cntrlKey = modifers & NSControlKeyMask; 30 const bool optKey = modifers & NSAlternateKeyMask; 31 const unichar keyCode = [event keyCode]; 32 const unichar keyChar = KeyCharacterForEvent(event); 33 34 int cmdNum = commandForKeyboardShortcut(cmdKey, shiftKey, cntrlKey, optKey, 35 keyCode, keyChar); 36 37 if (cmdNum != -1) { 38 id executor = [self delegate]; 39 // A bit of sanity. 40 DCHECK([executor conformsToProtocol:@protocol(BrowserCommandExecutor)]); 41 DCHECK([executor respondsToSelector:@selector(executeCommand:)]); 42 [executor executeCommand:cmdNum]; 43 return YES; 44 } 45 return NO; 46} 47 48- (BOOL)handleExtraWindowKeyboardShortcut:(NSEvent*)event { 49 return [self handleExtraKeyboardShortcut:event 50 fromTable:CommandForWindowKeyboardShortcut]; 51} 52 53- (BOOL)handleDelayedWindowKeyboardShortcut:(NSEvent*)event { 54 return [self handleExtraKeyboardShortcut:event 55 fromTable:CommandForDelayedWindowKeyboardShortcut]; 56} 57 58- (BOOL)handleExtraBrowserKeyboardShortcut:(NSEvent*)event { 59 return [self handleExtraKeyboardShortcut:event 60 fromTable:CommandForBrowserKeyboardShortcut]; 61} 62 63- (BOOL)performKeyEquivalent:(NSEvent*)event { 64 // Some extension commands have higher priority than web content, and some 65 // have lower priority. Regardless of whether the event is being 66 // redispatched, let the extension system try to handle the event. 67 NSWindow* window = event.window; 68 if (window) { 69 BrowserWindowController* controller = [window windowController]; 70 if ([controller respondsToSelector:@selector(handledByExtensionCommand: 71 priority:)]) { 72 ui::AcceleratorManager::HandlerPriority priority = 73 redispatchingEvent_ ? ui::AcceleratorManager::kNormalPriority 74 : ui::AcceleratorManager::kHighPriority; 75 if ([controller handledByExtensionCommand:event priority:priority]) 76 return YES; 77 } 78 } 79 80 if (redispatchingEvent_) 81 return NO; 82 83 // Give the web site a chance to handle the event. If it doesn't want to 84 // handle it, it will call us back with one of the |handle*| methods above. 85 NSResponder* r = [self firstResponder]; 86 if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)]) 87 return [r performKeyEquivalent:event]; 88 89 // If the delegate does not implement the BrowserCommandExecutor protocol, 90 // then we don't need to handle browser specific shortcut keys. 91 if (![[self delegate] conformsToProtocol:@protocol(BrowserCommandExecutor)]) 92 return [super performKeyEquivalent:event]; 93 94 // Handle per-window shortcuts like cmd-1, but do not handle browser-level 95 // shortcuts like cmd-left (else, cmd-left would do history navigation even 96 // if e.g. the Omnibox has focus). 97 if ([self handleExtraWindowKeyboardShortcut:event]) 98 return YES; 99 100 if ([super performKeyEquivalent:event]) 101 return YES; 102 103 // Handle per-window shortcuts like Esc after giving everybody else a chance 104 // to handle them 105 return [self handleDelayedWindowKeyboardShortcut:event]; 106} 107 108- (BOOL)redispatchKeyEvent:(NSEvent*)event { 109 DCHECK(event); 110 NSEventType eventType = [event type]; 111 if (eventType != NSKeyDown && 112 eventType != NSKeyUp && 113 eventType != NSFlagsChanged) { 114 NOTREACHED(); 115 return YES; // Pretend it's been handled in an effort to limit damage. 116 } 117 118 // Ordinarily, the event's window should be this window. However, when 119 // switching between normal and fullscreen mode, we switch out the window, and 120 // the event's window might be the previous window (or even an earlier one if 121 // the renderer is running slowly and several mode switches occur). In this 122 // rare case, we synthesize a new key event so that its associate window 123 // (number) is our own. 124 if ([event window] != self) 125 event = [self keyEventForWindow:self fromKeyEvent:event]; 126 127 // Redispatch the event. 128 eventHandled_ = YES; 129 redispatchingEvent_ = YES; 130 [NSApp sendEvent:event]; 131 redispatchingEvent_ = NO; 132 133 // If the event was not handled by [NSApp sendEvent:], the sendEvent: 134 // method below will be called, and because |redispatchingEvent_| is YES, 135 // |eventHandled_| will be set to NO. 136 return eventHandled_; 137} 138 139- (void)sendEvent:(NSEvent*)event { 140 if (!redispatchingEvent_) 141 [super sendEvent:event]; 142 else 143 eventHandled_ = NO; 144} 145 146- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event { 147 NSEventType eventType = [event type]; 148 149 // Convert the event's location from the original window's coordinates into 150 // our own. 151 NSPoint eventLoc = [event locationInWindow]; 152 eventLoc = [[event window] convertBaseToScreen:eventLoc]; 153 eventLoc = [self convertScreenToBase:eventLoc]; 154 155 // Various things *only* apply to key down/up. 156 BOOL eventIsARepeat = NO; 157 NSString* eventCharacters = nil; 158 NSString* eventUnmodCharacters = nil; 159 if (eventType == NSKeyDown || eventType == NSKeyUp) { 160 eventIsARepeat = [event isARepeat]; 161 eventCharacters = [event characters]; 162 eventUnmodCharacters = [event charactersIgnoringModifiers]; 163 } 164 165 // This synthesis may be slightly imperfect: we provide nil for the context, 166 // since I (viettrungluu) am sceptical that putting in the original context 167 // (if one is given) is valid. 168 return [NSEvent keyEventWithType:eventType 169 location:eventLoc 170 modifierFlags:[event modifierFlags] 171 timestamp:[event timestamp] 172 windowNumber:[window windowNumber] 173 context:nil 174 characters:eventCharacters 175 charactersIgnoringModifiers:eventUnmodCharacters 176 isARepeat:eventIsARepeat 177 keyCode:[event keyCode]]; 178} 179 180@end // ChromeEventProcessingWindow 181