Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/events/platform/x11/x11_hotplug_event_handler.h" | 5 #include "ui/events/platform/x11/x11_hotplug_event_handler.h" |
| 6 | 6 |
| 7 #include <X11/Xatom.h> | |
| 7 #include <X11/extensions/XInput.h> | 8 #include <X11/extensions/XInput.h> |
| 8 #include <X11/extensions/XInput2.h> | 9 #include <X11/extensions/XInput2.h> |
| 9 | 10 |
| 10 #include <algorithm> | 11 #include <algorithm> |
| 11 #include <cmath> | 12 #include <cmath> |
| 12 #include <set> | 13 #include <set> |
| 13 #include <string> | 14 #include <string> |
| 14 #include <vector> | 15 #include <vector> |
| 15 | 16 |
| 17 #include "base/bind.h" | |
| 16 #include "base/command_line.h" | 18 #include "base/command_line.h" |
| 19 #include "base/location.h" | |
| 17 #include "base/logging.h" | 20 #include "base/logging.h" |
| 18 #include "base/process/launch.h" | 21 #include "base/process/launch.h" |
| 22 #include "base/single_thread_task_runner.h" | |
| 19 #include "base/strings/string_util.h" | 23 #include "base/strings/string_util.h" |
| 20 #include "base/sys_info.h" | 24 #include "base/sys_info.h" |
| 25 #include "base/thread_task_runner_handle.h" | |
| 26 #include "base/threading/worker_pool.h" | |
| 27 #include "ui/events/devices/device_data_manager.h" | |
| 21 #include "ui/events/devices/device_hotplug_event_observer.h" | 28 #include "ui/events/devices/device_hotplug_event_observer.h" |
| 22 #include "ui/events/devices/device_util_linux.h" | 29 #include "ui/events/devices/device_util_linux.h" |
| 23 #include "ui/events/devices/input_device.h" | 30 #include "ui/events/devices/input_device.h" |
| 24 #include "ui/events/devices/keyboard_device.h" | 31 #include "ui/events/devices/keyboard_device.h" |
| 25 #include "ui/events/devices/touchscreen_device.h" | 32 #include "ui/events/devices/touchscreen_device.h" |
| 26 #include "ui/gfx/x/x11_types.h" | 33 #include "ui/gfx/x/x11_types.h" |
| 27 | 34 |
| 28 namespace ui { | 35 namespace ui { |
| 29 | 36 |
| 30 namespace { | 37 namespace { |
| 31 | 38 |
| 32 // The name of the xinput device corresponding to the AT internal keyboard. | 39 // The name of the xinput device corresponding to the AT internal keyboard. |
| 33 const char kATKeyboardName[] = "AT Translated Set 2 keyboard"; | 40 const char kATKeyboardName[] = "AT Translated Set 2 keyboard"; |
| 34 | 41 |
| 35 // The prefix of xinput devices corresponding to CrOS EC internal keyboards. | 42 // The prefix of xinput devices corresponding to CrOS EC internal keyboards. |
| 36 const char kCrosEcKeyboardPrefix[] = "cros-ec"; | 43 const char kCrosEcKeyboardPrefix[] = "cros-ec"; |
| 37 | 44 |
| 45 typedef base::Callback<void(const std::vector<KeyboardDevice>&)> | |
| 46 KeyboardDeviceCallback; | |
| 47 | |
| 48 typedef base::Callback<void(const std::vector<TouchscreenDevice>&)> | |
| 49 TouchscreenDeviceCallback; | |
| 50 | |
| 51 // Used for updating the state on the UI thread once device information is | |
| 52 // parsed on helper threads. | |
| 53 struct UiCallbacks { | |
| 54 KeyboardDeviceCallback keyboard_callback; | |
| 55 TouchscreenDeviceCallback touchscreen_callback; | |
| 56 }; | |
| 57 | |
| 58 struct DeviceInfo { | |
| 59 DeviceInfo(const XIDeviceInfo& device, const base::FilePath& path) | |
| 60 : device(device), path(path) {} | |
| 61 | |
| 62 XIDeviceInfo device; | |
|
sadrul
2014/11/06 21:05:28
Instead of using X11 structs in different threads,
dnicoara
2014/11/10 19:34:52
Done.
Arr, you are right, I should have checked t
| |
| 63 | |
| 64 // Path to the actual device (ie: /dev/input/eventXX) | |
| 65 base::FilePath path; | |
| 66 }; | |
| 67 | |
| 68 // X11 display cache used on worker threads. This is filled on the UI thread and | |
| 69 // passed in to the worker threads. | |
| 70 struct DisplayState { | |
| 71 Atom mt_position_x; | |
| 72 Atom mt_position_y; | |
| 73 }; | |
| 74 | |
| 38 // Returns true if |name| is the name of a known keyboard device. Note, this may | 75 // Returns true if |name| is the name of a known keyboard device. Note, this may |
| 39 // return false negatives. | 76 // return false negatives. |
| 40 bool IsKnownKeyboard(const std::string& name) { | 77 bool IsKnownKeyboard(const std::string& name) { |
| 41 std::string lower = base::StringToLowerASCII(name); | 78 std::string lower = base::StringToLowerASCII(name); |
| 42 return lower.find("keyboard") != std::string::npos; | 79 return lower.find("keyboard") != std::string::npos; |
| 43 } | 80 } |
| 44 | 81 |
| 45 // Returns true if |name| is the name of a known internal keyboard device. Note, | 82 // Returns true if |name| is the name of a known internal keyboard device. Note, |
| 46 // this may return false negatives. | 83 // this may return false negatives. |
| 47 bool IsInternalKeyboard(const std::string& name) { | 84 bool IsInternalKeyboard(const std::string& name) { |
| 48 // TODO(rsadam@): Come up with a more generic way of identifying internal | 85 // TODO(rsadam@): Come up with a more generic way of identifying internal |
| 49 // keyboards. See crbug.com/420728. | 86 // keyboards. See crbug.com/420728. |
| 50 if (name == kATKeyboardName) | 87 if (name == kATKeyboardName) |
| 51 return true; | 88 return true; |
| 52 return name.compare( | 89 return name.compare( |
| 53 0u, strlen(kCrosEcKeyboardPrefix), kCrosEcKeyboardPrefix) == 0; | 90 0u, strlen(kCrosEcKeyboardPrefix), kCrosEcKeyboardPrefix) == 0; |
| 54 } | 91 } |
| 55 | 92 |
| 56 // Returns true if |name| is the name of a known XTEST device. Note, this may | 93 // Returns true if |name| is the name of a known XTEST device. Note, this may |
| 57 // return false negatives. | 94 // return false negatives. |
| 58 bool IsTestKeyboard(const std::string& name) { | 95 bool IsTestKeyboard(const std::string& name) { |
| 59 return name.find("XTEST") != std::string::npos; | 96 return name.find("XTEST") != std::string::npos; |
| 60 } | 97 } |
| 61 | 98 |
| 62 // We consider the touchscreen to be internal if it is an I2c device. | 99 base::FilePath GetDevicePath(XDisplay* dpy, int device_id) { |
| 63 // With the device id, we can query X to get the device's dev input | 100 #if !defined(OS_CHROMEOS) |
| 64 // node eventXXX. Then we search all the dev input nodes registered | 101 return base::FilePath(); |
| 65 // by I2C devices to see if we can find eventXXX. | |
| 66 bool IsTouchscreenInternal(XDisplay* dpy, int device_id) { | |
| 67 #if !defined(CHROMEOS) | |
| 68 return false; | |
| 69 #else | 102 #else |
| 70 if (!base::SysInfo::IsRunningOnChromeOS()) | 103 if (!base::SysInfo::IsRunningOnChromeOS()) |
| 71 return false; | 104 return base::FilePath(); |
| 72 #endif | 105 #endif |
| 73 | 106 |
| 74 // Input device has a property "Device Node" pointing to its dev input node, | 107 // Input device has a property "Device Node" pointing to its dev input node, |
| 75 // e.g. Device Node (250): "/dev/input/event8" | 108 // e.g. Device Node (250): "/dev/input/event8" |
| 76 Atom device_node = XInternAtom(dpy, "Device Node", False); | 109 Atom device_node = XInternAtom(dpy, "Device Node", False); |
| 77 if (device_node == None) | 110 if (device_node == None) |
| 78 return false; | 111 return base::FilePath(); |
| 79 | 112 |
| 80 Atom actual_type; | 113 Atom actual_type; |
| 81 int actual_format; | 114 int actual_format; |
| 82 unsigned long nitems, bytes_after; | 115 unsigned long nitems, bytes_after; |
| 83 unsigned char* data; | 116 unsigned char* data; |
| 84 XDevice* dev = XOpenDevice(dpy, device_id); | 117 XDevice* dev = XOpenDevice(dpy, device_id); |
| 85 if (!dev) | 118 if (!dev) |
| 86 return false; | 119 return base::FilePath(); |
| 87 | 120 |
| 88 if (XGetDeviceProperty(dpy, | 121 if (XGetDeviceProperty(dpy, |
| 89 dev, | 122 dev, |
| 90 device_node, | 123 device_node, |
| 91 0, | 124 0, |
| 92 1000, | 125 1000, |
| 93 False, | 126 False, |
| 94 AnyPropertyType, | 127 AnyPropertyType, |
| 95 &actual_type, | 128 &actual_type, |
| 96 &actual_format, | 129 &actual_format, |
| 97 &nitems, | 130 &nitems, |
| 98 &bytes_after, | 131 &bytes_after, |
| 99 &data) != Success) { | 132 &data) != Success) { |
| 100 XCloseDevice(dpy, dev); | 133 XCloseDevice(dpy, dev); |
| 101 return false; | 134 return base::FilePath(); |
| 102 } | 135 } |
| 103 base::FilePath dev_node_path(reinterpret_cast<char*>(data)); | 136 |
| 137 std::string path; | |
| 138 // Make sure the returned value is a string. | |
| 139 if (actual_type == XA_STRING && actual_format == 8) | |
| 140 path = reinterpret_cast<char*>(data); | |
| 141 | |
| 104 XFree(data); | 142 XFree(data); |
| 105 XCloseDevice(dpy, dev); | 143 XCloseDevice(dpy, dev); |
| 106 | 144 |
| 107 return ui::IsTouchscreenInternal(dev_node_path); | 145 return base::FilePath(path); |
| 108 } | 146 } |
| 109 | 147 |
| 110 } // namespace | 148 // Helper used to parse keyboard information. When it is done it uses |
| 111 | 149 // |reply_runner| and |callback| to update the state on the UI thread. |
| 112 X11HotplugEventHandler::X11HotplugEventHandler( | 150 void HandleKeyboardDevices(const std::vector<DeviceInfo>& device_infos, |
| 113 DeviceHotplugEventObserver* delegate) | 151 scoped_refptr<base::TaskRunner> reply_runner, |
| 114 : delegate_(delegate) { | 152 const KeyboardDeviceCallback& callback) { |
| 115 } | |
| 116 | |
| 117 X11HotplugEventHandler::~X11HotplugEventHandler() { | |
| 118 } | |
| 119 | |
| 120 void X11HotplugEventHandler::OnHotplugEvent() { | |
| 121 const XIDeviceList& device_list = | |
| 122 DeviceListCacheX11::GetInstance()->GetXI2DeviceList(gfx::GetXDisplay()); | |
| 123 HandleTouchscreenDevices(device_list); | |
| 124 HandleKeyboardDevices(device_list); | |
| 125 } | |
| 126 | |
| 127 void X11HotplugEventHandler::HandleKeyboardDevices( | |
| 128 const XIDeviceList& x11_devices) { | |
| 129 std::vector<KeyboardDevice> devices; | 153 std::vector<KeyboardDevice> devices; |
| 130 | 154 |
| 131 for (int i = 0; i < x11_devices.count; i++) { | 155 for (auto device_info : device_infos) { |
| 132 if (!x11_devices[i].enabled || x11_devices[i].use != XISlaveKeyboard) | 156 XIDeviceInfo x11_device = device_info.device; |
| 157 | |
| 158 if (!x11_device.enabled || x11_device.use != XISlaveKeyboard) | |
| 133 continue; // Assume all keyboards are keyboard slaves | 159 continue; // Assume all keyboards are keyboard slaves |
| 134 std::string device_name(x11_devices[i].name); | 160 std::string device_name(x11_device.name); |
| 135 base::TrimWhitespaceASCII(device_name, base::TRIM_TRAILING, &device_name); | 161 base::TrimWhitespaceASCII(device_name, base::TRIM_TRAILING, &device_name); |
| 136 if (IsTestKeyboard(device_name)) | 162 if (IsTestKeyboard(device_name)) |
| 137 continue; // Skip test devices. | 163 continue; // Skip test devices. |
| 138 InputDeviceType type; | 164 InputDeviceType type; |
| 139 if (IsInternalKeyboard(device_name)) { | 165 if (IsInternalKeyboard(device_name)) { |
| 140 type = InputDeviceType::INPUT_DEVICE_INTERNAL; | 166 type = InputDeviceType::INPUT_DEVICE_INTERNAL; |
| 141 } else if (IsKnownKeyboard(device_name)) { | 167 } else if (IsKnownKeyboard(device_name)) { |
| 142 type = InputDeviceType::INPUT_DEVICE_EXTERNAL; | 168 type = InputDeviceType::INPUT_DEVICE_EXTERNAL; |
| 143 } else { | 169 } else { |
| 144 type = InputDeviceType::INPUT_DEVICE_UNKNOWN; | 170 type = InputDeviceType::INPUT_DEVICE_UNKNOWN; |
| 145 } | 171 } |
| 146 devices.push_back( | 172 devices.push_back( |
| 147 KeyboardDevice(x11_devices[i].deviceid, type, device_name)); | 173 KeyboardDevice(x11_device.deviceid, type, device_name)); |
| 148 } | 174 } |
| 149 delegate_->OnKeyboardDevicesUpdated(devices); | 175 |
| 176 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices)); | |
| 150 } | 177 } |
| 151 | 178 |
| 152 void X11HotplugEventHandler::HandleTouchscreenDevices( | 179 // Helper used to parse touchscreen information. When it is done it uses |
| 153 const XIDeviceList& x11_devices) { | 180 // |reply_runner| and |callback| to update the state on the UI thread. |
| 181 void HandleTouchscreenDevices(const std::vector<DeviceInfo>& device_infos, | |
| 182 const DisplayState& display_state, | |
| 183 scoped_refptr<base::TaskRunner> reply_runner, | |
| 184 const TouchscreenDeviceCallback& callback) { | |
| 154 std::vector<TouchscreenDevice> devices; | 185 std::vector<TouchscreenDevice> devices; |
| 155 Display* display = gfx::GetXDisplay(); | 186 if (display_state.mt_position_x == None || |
| 156 Atom valuator_x = XInternAtom(display, "Abs MT Position X", False); | 187 display_state.mt_position_y == None) |
| 157 Atom valuator_y = XInternAtom(display, "Abs MT Position Y", False); | |
| 158 if (valuator_x == None || valuator_y == None) | |
| 159 return; | 188 return; |
| 160 | 189 |
| 161 std::set<int> no_match_touchscreen; | 190 std::set<int> no_match_touchscreen; |
| 162 for (int i = 0; i < x11_devices.count; i++) { | 191 for (auto device_info : device_infos) { |
| 163 if (!x11_devices[i].enabled || x11_devices[i].use != XIFloatingSlave) | 192 XIDeviceInfo x11_device = device_info.device; |
| 193 if (!x11_device.enabled || x11_device.use != XIFloatingSlave) | |
| 164 continue; // Assume all touchscreens are floating slaves | 194 continue; // Assume all touchscreens are floating slaves |
| 165 | 195 |
| 166 double max_x = -1.0; | 196 double max_x = -1.0; |
| 167 double max_y = -1.0; | 197 double max_y = -1.0; |
| 168 bool is_direct_touch = false; | 198 bool is_direct_touch = false; |
| 169 | 199 |
| 170 for (int j = 0; j < x11_devices[i].num_classes; j++) { | 200 for (int j = 0; j < x11_device.num_classes; j++) { |
| 171 XIAnyClassInfo* class_info = x11_devices[i].classes[j]; | 201 XIAnyClassInfo* class_info = x11_device.classes[j]; |
|
sadrul
2014/11/06 21:05:27
Is it safe to access the XIDeviceInfo::classes her
dnicoara
2014/11/10 19:34:52
No :( ... followed your advice.
| |
| 172 | 202 |
| 173 if (class_info->type == XIValuatorClass) { | 203 if (class_info->type == XIValuatorClass) { |
| 174 XIValuatorClassInfo* valuator_info = | 204 XIValuatorClassInfo* valuator_info = |
| 175 reinterpret_cast<XIValuatorClassInfo*>(class_info); | 205 reinterpret_cast<XIValuatorClassInfo*>(class_info); |
| 176 | 206 |
| 177 if (valuator_x == valuator_info->label) { | 207 if (display_state.mt_position_x == valuator_info->label) { |
| 178 // Ignore X axis valuator with unexpected properties | 208 // Ignore X axis valuator with unexpected properties |
| 179 if (valuator_info->number == 0 && valuator_info->mode == Absolute && | 209 if (valuator_info->number == 0 && valuator_info->mode == Absolute && |
| 180 valuator_info->min == 0.0) { | 210 valuator_info->min == 0.0) { |
| 181 max_x = valuator_info->max; | 211 max_x = valuator_info->max; |
| 182 } | 212 } |
| 183 } else if (valuator_y == valuator_info->label) { | 213 } else if (display_state.mt_position_y == valuator_info->label) { |
| 184 // Ignore Y axis valuator with unexpected properties | 214 // Ignore Y axis valuator with unexpected properties |
| 185 if (valuator_info->number == 1 && valuator_info->mode == Absolute && | 215 if (valuator_info->number == 1 && valuator_info->mode == Absolute && |
| 186 valuator_info->min == 0.0) { | 216 valuator_info->min == 0.0) { |
| 187 max_y = valuator_info->max; | 217 max_y = valuator_info->max; |
| 188 } | 218 } |
| 189 } | 219 } |
| 190 } | 220 } |
| 191 #if defined(USE_XI2_MT) | 221 #if defined(USE_XI2_MT) |
| 192 if (class_info->type == XITouchClass) { | 222 if (class_info->type == XITouchClass) { |
| 193 XITouchClassInfo* touch_info = | 223 XITouchClassInfo* touch_info = |
| 194 reinterpret_cast<XITouchClassInfo*>(class_info); | 224 reinterpret_cast<XITouchClassInfo*>(class_info); |
| 195 is_direct_touch = touch_info->mode == XIDirectTouch; | 225 is_direct_touch = touch_info->mode == XIDirectTouch; |
| 196 } | 226 } |
| 197 #endif | 227 #endif |
| 198 } | 228 } |
| 199 | 229 |
| 200 // Touchscreens should have absolute X and Y axes, and be direct touch | 230 // Touchscreens should have absolute X and Y axes, and be direct touch |
| 201 // devices. | 231 // devices. |
| 202 if (max_x > 0.0 && max_y > 0.0 && is_direct_touch) { | 232 if (max_x > 0.0 && max_y > 0.0 && is_direct_touch) { |
| 203 InputDeviceType type = | 233 InputDeviceType type = IsTouchscreenInternal(device_info.path) |
| 204 IsTouchscreenInternal(display, x11_devices[i].deviceid) | |
| 205 ? InputDeviceType::INPUT_DEVICE_INTERNAL | 234 ? InputDeviceType::INPUT_DEVICE_INTERNAL |
| 206 : InputDeviceType::INPUT_DEVICE_EXTERNAL; | 235 : InputDeviceType::INPUT_DEVICE_EXTERNAL; |
| 207 std::string name(x11_devices[i].name); | 236 std::string name(x11_device.name); |
| 208 // |max_x| and |max_y| are inclusive values, so we need to add 1 to get | 237 // |max_x| and |max_y| are inclusive values, so we need to add 1 to get |
| 209 // the size. | 238 // the size. |
| 210 devices.push_back(TouchscreenDevice( | 239 devices.push_back(TouchscreenDevice( |
| 211 x11_devices[i].deviceid, | 240 x11_device.deviceid, |
| 212 type, | 241 type, |
| 213 name, | 242 name, |
| 214 gfx::Size(max_x + 1, max_y + 1))); | 243 gfx::Size(max_x + 1, max_y + 1))); |
| 215 } | 244 } |
| 216 } | 245 } |
| 217 | 246 |
| 218 delegate_->OnTouchscreenDevicesUpdated(devices); | 247 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices)); |
| 248 } | |
| 249 | |
| 250 // Called on a worker thread to parse the device information. | |
| 251 void HandleHotplugEvent(const std::vector<DeviceInfo>& devices, | |
| 252 const DisplayState& display_state, | |
| 253 scoped_refptr<base::TaskRunner> reply_runner, | |
| 254 const UiCallbacks& callbacks) { | |
| 255 HandleTouchscreenDevices( | |
| 256 devices, display_state, reply_runner, callbacks.touchscreen_callback); | |
| 257 HandleKeyboardDevices(devices, reply_runner, callbacks.keyboard_callback); | |
| 258 } | |
| 259 | |
| 260 DeviceHotplugEventObserver* GetHotplugEventObserver() { | |
| 261 return DeviceDataManager::GetInstance(); | |
| 262 } | |
| 263 | |
| 264 void OnKeyboardDevices(const std::vector<KeyboardDevice>& devices) { | |
| 265 GetHotplugEventObserver()->OnKeyboardDevicesUpdated(devices); | |
| 266 } | |
| 267 | |
| 268 void OnTouchscreenDevices(const std::vector<TouchscreenDevice>& devices) { | |
| 269 GetHotplugEventObserver()->OnTouchscreenDevicesUpdated(devices); | |
| 270 } | |
| 271 | |
| 272 } // namespace | |
| 273 | |
| 274 X11HotplugEventHandler::X11HotplugEventHandler() { | |
| 275 } | |
| 276 | |
| 277 X11HotplugEventHandler::~X11HotplugEventHandler() { | |
| 278 } | |
| 279 | |
| 280 void X11HotplugEventHandler::OnHotplugEvent() { | |
| 281 const XIDeviceList& device_list = | |
| 282 DeviceListCacheX11::GetInstance()->GetXI2DeviceList(gfx::GetXDisplay()); | |
| 283 Display* display = gfx::GetXDisplay(); | |
| 284 | |
| 285 std::vector<DeviceInfo> device_infos; | |
| 286 for (int i = 0; i < device_list.count; ++i) | |
| 287 device_infos.push_back(DeviceInfo( | |
| 288 device_list[i], GetDevicePath(display, device_list[i].deviceid))); | |
| 289 | |
| 290 // X11 is not thread safe, so first get all the required state. | |
| 291 DisplayState display_state; | |
| 292 display_state.mt_position_x = | |
| 293 XInternAtom(display, "Abs MT Position X", False); | |
| 294 display_state.mt_position_y = | |
| 295 XInternAtom(display, "Abs MT Position Y", False); | |
| 296 | |
| 297 UiCallbacks callbacks; | |
| 298 callbacks.keyboard_callback = base::Bind(&OnKeyboardDevices); | |
| 299 callbacks.touchscreen_callback = base::Bind(&OnTouchscreenDevices); | |
| 300 | |
| 301 // Parsing the device information may block, so delegate the operation to a | |
| 302 // worker thread. Once the device information is extracted the parsed devices | |
| 303 // will be returned via the callbacks. | |
| 304 base::WorkerPool::PostTask(FROM_HERE, | |
| 305 base::Bind(&HandleHotplugEvent, | |
| 306 device_infos, | |
| 307 display_state, | |
| 308 base::ThreadTaskRunnerHandle::Get(), | |
| 309 callbacks), | |
| 310 true /* task_is_slow */); | |
| 219 } | 311 } |
| 220 | 312 |
| 221 } // namespace ui | 313 } // namespace ui |
| OLD | NEW |