Index: chrome/browser/ui/views/ash/key_rewriter.cc |
diff --git a/chrome/browser/ui/views/ash/key_rewriter.cc b/chrome/browser/ui/views/ash/key_rewriter.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b5872399f71631a0b100cb0b1415eb953f203874 |
--- /dev/null |
+++ b/chrome/browser/ui/views/ash/key_rewriter.cc |
@@ -0,0 +1,233 @@ |
+// 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. |
+ |
+#include "chrome/browser/ui/views/ash/key_rewriter.h" |
+ |
+#include <vector> |
+ |
+#include "ash/shell.h" |
+#include "base/logging.h" |
+#include "base/string_util.h" |
+#include "ui/aura/event.h" |
+#include "ui/aura/root_window.h" |
+#include "ui/base/keycodes/keyboard_code_conversion.h" |
+#include "ui/base/keycodes/keyboard_codes.h" |
+ |
+#if defined(OS_CHROMEOS) |
+#include <X11/extensions/XInput2.h> |
+#include <X11/keysym.h> |
+#include <X11/Xlib.h> |
+ |
+#include "base/chromeos/chromeos_version.h" |
+#include "chrome/browser/chromeos/xinput_hierarchy_changed_event_listener.h" |
+#include "ui/base/keycodes/keyboard_code_conversion_x.h" |
+#include "ui/base/x/x11_util.h" |
+#endif |
+ |
+namespace { |
+ |
+const int kBadDeviceId = -1; |
+ |
+} // namespace |
+ |
+KeyRewriter::KeyRewriter() : last_device_id_(kBadDeviceId) { |
+ // The ash shell isn't instantiated for our unit tests. |
+ if (ash::Shell::HasInstance()) |
+ ash::Shell::GetInstance()->GetRootWindow()->AddRootWindowObserver(this); |
+#if defined(OS_CHROMEOS) |
+ if (base::chromeos::IsRunningOnChromeOS()) { |
+ chromeos::XInputHierarchyChangedEventListener::GetInstance() |
+ ->AddObserver(this); |
+ } |
+ RefreshKeycodes(); |
+#endif |
+} |
+ |
+KeyRewriter::~KeyRewriter() { |
+ if (ash::Shell::HasInstance()) |
+ ash::Shell::GetInstance()->GetRootWindow()->RemoveRootWindowObserver(this); |
+#if defined(OS_CHROMEOS) |
+ if (base::chromeos::IsRunningOnChromeOS()) { |
+ chromeos::XInputHierarchyChangedEventListener::GetInstance() |
+ ->RemoveObserver(this); |
+ } |
+#endif |
+} |
+ |
+KeyRewriter::DeviceType KeyRewriter::DeviceAddedForTesting( |
+ int device_id, |
+ const std::string& device_name) { |
+ return DeviceAddedInternal(device_id, device_name); |
+} |
+ |
+// static |
+KeyRewriter::DeviceType KeyRewriter::GetDeviceType( |
+ const std::string& device_name) { |
+ std::vector<std::string> tokens; |
+ Tokenize(device_name, " .", &tokens); |
+ |
+ // If the |device_name| contains the two words, "apple" and "keyboard", treat |
+ // it as an Apple keyboard. |
+ bool found_apple = false; |
+ bool found_keyboard = false; |
+ for (size_t i = 0; i < tokens.size(); ++i) { |
+ if (!found_apple && LowerCaseEqualsASCII(tokens[i], "apple")) |
+ found_apple = true; |
+ if (!found_keyboard && LowerCaseEqualsASCII(tokens[i], "keyboard")) |
+ found_keyboard = true; |
+ if (found_apple && found_keyboard) |
+ return kDeviceAppleKeyboard; |
+ } |
+ |
+ return kDeviceUnknown; |
+} |
+ |
+void KeyRewriter::RewriteCommandToControlForTesting(aura::KeyEvent* event) { |
+ RewriteCommandToControl(event); |
+} |
+ |
+ash::KeyRewriterDelegate::Action KeyRewriter::RewriteOrFilterKeyEvent( |
+ aura::KeyEvent* event) { |
+ const ash::KeyRewriterDelegate::Action kActionRewrite = |
+ ash::KeyRewriterDelegate::ACTION_REWRITE_EVENT; |
+ if (!event->HasNativeEvent()) { |
+ // Do not handle a fabricated event generated by tests. |
+ return kActionRewrite; |
+ } |
+ |
+ RewriteCommandToControl(event); |
+ // TODO(yusukes): Implement crbug.com/115112 (Search/Ctrl/Alt remapping) and |
+ // crosbug.com/27167 (allow sending function keys) here. |
+ |
+ return kActionRewrite; // Do not drop the event. |
+} |
+ |
+void KeyRewriter::OnKeyboardMappingChanged(const aura::RootWindow* root) { |
+#if defined(OS_CHROMEOS) |
+ RefreshKeycodes(); |
+#endif |
+} |
+ |
+#if defined(OS_CHROMEOS) |
+void KeyRewriter::DeviceAdded(int device_id) { |
+ DCHECK_NE(XIAllDevices, device_id); |
+ DCHECK_NE(XIAllMasterDevices, device_id); |
+ if (device_id == XIAllDevices || device_id == XIAllMasterDevices) { |
+ LOG(ERROR) << "Unexpected device_id passed: " << device_id; |
+ return; |
+ } |
+ |
+ int ndevices_return = 0; |
+ XIDeviceInfo* device_info = XIQueryDevice(ui::GetXDisplay(), |
+ device_id, |
+ &ndevices_return); |
+ |
+ // Since |device_id| is neither XIAllDevices nor XIAllMasterDevices, |
+ // the number of devices found should be either 0 (not found) or 1. |
+ if (!device_info) { |
+ LOG(ERROR) << "XIQueryDevice: Device ID " << device_id << " is unknown."; |
+ return; |
+ } |
+ |
+ DCHECK_EQ(1, ndevices_return); |
+ for (int i = 0; i < ndevices_return; ++i) { |
+ DCHECK_EQ(device_id, device_info[i].deviceid); // see the comment above. |
+ DCHECK(device_info[i].name); |
+ DeviceAddedInternal(device_info[i].deviceid, device_info[i].name); |
+ } |
+ |
+ XIFreeDeviceInfo(device_info); |
+} |
+ |
+void KeyRewriter::DeviceRemoved(int device_id) { |
+ device_id_to_type_.erase(device_id); |
+} |
+ |
+void KeyRewriter::DeviceKeyPressedOrReleased(int device_id) { |
+ std::map<int, DeviceType>::const_iterator iter = |
+ device_id_to_type_.find(device_id); |
+ if (iter == device_id_to_type_.end()) { |
+ // |device_id| is unknown. This means the device was connected before |
+ // booting the OS. Query the name of the device and add it to the map. |
+ DeviceAdded(device_id); |
+ } |
+ |
+ last_device_id_ = device_id; |
+} |
+ |
+void KeyRewriter::RefreshKeycodes() { |
+ control_l_xkeycode_ = XKeysymToKeycode(ui::GetXDisplay(), XK_Control_L); |
+ control_r_xkeycode_ = XKeysymToKeycode(ui::GetXDisplay(), XK_Control_R); |
+} |
+#endif |
+ |
+void KeyRewriter::RewriteCommandToControl(aura::KeyEvent* event) { |
+ if (last_device_id_ == kBadDeviceId) |
+ return; |
+ |
+ // Check which device generated |event|. |
+ std::map<int, DeviceType>::const_iterator iter = |
+ device_id_to_type_.find(last_device_id_); |
+ if (iter == device_id_to_type_.end()) { |
+ LOG(ERROR) << "Device ID " << last_device_id_ << " is unknown."; |
+ return; |
+ } |
+ |
+ const DeviceType type = iter->second; |
+ if (type != kDeviceAppleKeyboard) |
+ return; |
+ |
+#if defined(OS_CHROMEOS) |
+ XEvent* xev = event->native_event(); |
+ XKeyEvent* xkey = &(xev->xkey); |
+ |
+ // Mod4 is the Windows key on a PC keyboard or Command key on an Apple |
+ // keyboard. |
+ if (xkey->state & Mod4Mask) { |
+ xkey->state &= ~Mod4Mask; |
+ xkey->state |= ControlMask; |
+ event->set_flags(event->flags() | ui::EF_CONTROL_DOWN); |
+ } |
+ |
+ KeySym keysym = XLookupKeysym(xkey, 0); |
+ switch (keysym) { |
+ case XK_Super_L: |
+ // left Command -> left Control |
+ xkey->keycode = control_l_xkeycode_; |
+ event->set_key_code(ui::VKEY_LCONTROL); |
+ event->set_character(ui::GetCharacterFromKeyCode(event->key_code(), |
+ event->flags())); |
+ break; |
+ case XK_Super_R: |
+ // right Command -> right Control |
+ xkey->keycode = control_r_xkeycode_; |
+ event->set_key_code(ui::VKEY_RCONTROL); |
+ event->set_character(ui::GetCharacterFromKeyCode(event->key_code(), |
+ event->flags())); |
+ break; |
+ default: |
+ break; |
+ } |
+ |
+ DCHECK_NE(ui::VKEY_LWIN, ui::KeyboardCodeFromXKeyEvent(xev)); |
+ DCHECK_NE(ui::VKEY_RWIN, ui::KeyboardCodeFromXKeyEvent(xev)); |
+#else |
+ // TODO(yusukes): Support Ash on other platforms if needed. |
+ NOTIMPLEMENTED(); |
+#endif |
+} |
+ |
+KeyRewriter::DeviceType KeyRewriter::DeviceAddedInternal( |
+ int device_id, |
+ const std::string& device_name) { |
+ const DeviceType type = KeyRewriter::GetDeviceType(device_name); |
+ if (type == kDeviceAppleKeyboard) { |
+ VLOG(1) << "Apple keyboard '" << device_name << "' connected: " |
+ << "id=" << device_id; |
+ } |
+ // Always overwrite the existing device_id since the X server may reuse a |
+ // device id for an unattached device. |
+ device_id_to_type_[device_id] = type; |
+ return type; |
+} |