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.
5 #include "ui/events/event_constants.h"
8 #include <X11/extensions/XInput.h>
9 #include <X11/extensions/XInput2.h>
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/message_loop/message_pump_x11.h"
15 #include "ui/events/event_utils.h"
16 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
17 #include "ui/events/x/device_data_manager.h"
18 #include "ui/events/x/device_list_cache_x.h"
19 #include "ui/events/x/touch_factory_x11.h"
20 #include "ui/gfx/display.h"
21 #include "ui/gfx/point.h"
22 #include "ui/gfx/rect.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/gfx/x/x11_atom_cache.h"
25 #include "ui/gfx/x/x11_types.h"
29 // Scroll amount for each wheelscroll event. 53 is also the value used for GTK+.
30 const int kWheelScrollAmount = 53;
32 const int kMinWheelButton = 4;
33 const int kMaxWheelButton = 7;
35 // A class to track current modifier state on master device. Only track ctrl,
36 // alt, shift and caps lock keys currently. The tracked state can then be used
37 // by floating device.
38 class XModifierStateWatcher{
40 static XModifierStateWatcher* GetInstance() {
41 return Singleton<XModifierStateWatcher>::get();
44 void UpdateStateFromEvent(const base::NativeEvent& native_event) {
45 // Floating device can't access the modifer state from master device.
46 // We need to track the states of modifier keys in a singleton for
47 // floating devices such as touch screen. Issue 106426 is one example
48 // of why we need the modifier states for floating device.
49 state_ = native_event->xkey.state;
50 // master_state is the state before key press. We need to track the
51 // state after key press for floating device. Currently only ctrl,
52 // shift, alt and caps lock keys are tracked.
53 ui::KeyboardCode keyboard_code = ui::KeyboardCodeFromNative(native_event);
54 unsigned int mask = 0;
56 switch (keyboard_code) {
57 case ui::VKEY_CONTROL: {
61 case ui::VKEY_SHIFT: {
69 case ui::VKEY_CAPITAL: {
77 if (native_event->type == KeyPress)
83 // Returns the current modifer state in master device. It only contains the
84 // state of ctrl, shift, alt and caps lock keys.
85 unsigned int state() { return state_; }
88 friend struct DefaultSingletonTraits<XModifierStateWatcher>;
90 XModifierStateWatcher() : state_(0) { }
94 DISALLOW_COPY_AND_ASSIGN(XModifierStateWatcher);
97 #if defined(USE_XI2_MT)
98 // Detects if a touch event is a driver-generated 'special event'.
99 // A 'special event' is a touch event with maximum radius and pressure at
101 // This needs to be done in a cleaner way: http://crbug.com/169256
102 bool TouchEventIsGeneratedHack(const base::NativeEvent& native_event) {
103 XIDeviceEvent* event =
104 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
105 CHECK(event->evtype == XI_TouchBegin ||
106 event->evtype == XI_TouchUpdate ||
107 event->evtype == XI_TouchEnd);
109 // Force is normalized to [0, 1].
110 if (ui::GetTouchForce(native_event) < 1.0f)
113 if (ui::EventLocationFromNative(native_event) != gfx::Point())
116 // Radius is in pixels, and the valuator is the diameter in pixels.
117 double radius = ui::GetTouchRadiusX(native_event), min, max;
118 unsigned int deviceid =
119 static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
120 if (!ui::DeviceDataManager::GetInstance()->GetDataRange(
121 deviceid, ui::DeviceDataManager::DT_TOUCH_MAJOR, &min, &max)) {
125 return radius * 2 == max;
129 int GetEventFlagsFromXState(unsigned int state) {
131 if (state & ControlMask)
132 flags |= ui::EF_CONTROL_DOWN;
133 if (state & ShiftMask)
134 flags |= ui::EF_SHIFT_DOWN;
135 if (state & Mod1Mask)
136 flags |= ui::EF_ALT_DOWN;
137 if (state & LockMask)
138 flags |= ui::EF_CAPS_LOCK_DOWN;
139 if (state & Mod5Mask)
140 flags |= ui::EF_ALTGR_DOWN;
141 if (state & Button1Mask)
142 flags |= ui::EF_LEFT_MOUSE_BUTTON;
143 if (state & Button2Mask)
144 flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
145 if (state & Button3Mask)
146 flags |= ui::EF_RIGHT_MOUSE_BUTTON;
150 // Get the event flag for the button in XButtonEvent. During a ButtonPress
151 // event, |state| in XButtonEvent does not include the button that has just been
152 // pressed. Instead |state| contains flags for the buttons (if any) that had
153 // already been pressed before the current button, and |button| stores the most
154 // current pressed button. So, if you press down left mouse button, and while
155 // pressing it down, press down the right mouse button, then for the latter
156 // event, |state| would have Button1Mask set but not Button3Mask, and |button|
158 int GetEventFlagsForButton(int button) {
161 return ui::EF_LEFT_MOUSE_BUTTON;
163 return ui::EF_MIDDLE_MOUSE_BUTTON;
165 return ui::EF_RIGHT_MOUSE_BUTTON;
171 int GetButtonMaskForX2Event(XIDeviceEvent* xievent) {
173 for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) {
174 if (XIMaskIsSet(xievent->buttons.mask, i)) {
175 int button = (xievent->sourceid == xievent->deviceid) ?
176 ui::DeviceDataManager::GetInstance()->GetMappedButton(i) : i;
177 buttonflags |= GetEventFlagsForButton(button);
183 ui::EventType GetTouchEventType(const base::NativeEvent& native_event) {
184 XIDeviceEvent* event =
185 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
186 #if defined(USE_XI2_MT)
187 switch(event->evtype) {
189 return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
190 ui::ET_TOUCH_PRESSED;
192 return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
195 return TouchEventIsGeneratedHack(native_event) ? ui::ET_TOUCH_CANCELLED :
196 ui::ET_TOUCH_RELEASED;
199 return ui::ET_UNKNOWN;
201 ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
203 // If this device doesn't support multi-touch, then just use the normal
204 // pressed/release events to indicate touch start/end. With multi-touch,
205 // these events are sent only for the first (pressed) or last (released)
206 // touch point, and so we must infer start/end from motion events.
207 if (!factory->IsMultiTouchDevice(event->sourceid)) {
208 switch (event->evtype) {
210 return ui::ET_TOUCH_PRESSED;
211 case XI_ButtonRelease:
212 return ui::ET_TOUCH_RELEASED;
214 if (GetButtonMaskForX2Event(event))
215 return ui::ET_TOUCH_MOVED;
216 return ui::ET_UNKNOWN;
222 DCHECK_EQ(event->evtype, XI_Motion);
224 // Note: We will not generate a _STATIONARY event here. It will be created,
225 // when necessary, by a RWHVV.
226 // TODO(sad): When should _CANCELLED be generated?
228 ui::DeviceDataManager* manager = ui::DeviceDataManager::GetInstance();
231 if (!manager->GetEventData(
232 *native_event, ui::DeviceDataManager::DT_TOUCH_SLOT_ID, &slot))
233 return ui::ET_UNKNOWN;
235 if (!factory->IsSlotUsed(slot)) {
236 // This is a new touch point.
237 return ui::ET_TOUCH_PRESSED;
241 if (!manager->GetEventData(
242 *native_event, ui::DeviceDataManager::DT_TOUCH_TRACKING_ID, &tracking))
243 return ui::ET_UNKNOWN;
245 if (tracking == 0l) {
246 // The touch point has been released.
247 return ui::ET_TOUCH_RELEASED;
250 return ui::ET_TOUCH_MOVED;
251 #endif // defined(USE_XI2_MT)
254 double GetTouchParamFromXEvent(XEvent* xev,
255 ui::DeviceDataManager::DataType val,
256 double default_value) {
257 ui::DeviceDataManager::GetInstance()->GetEventData(
258 *xev, val, &default_value);
259 return default_value;
262 Atom GetNoopEventAtom() {
263 return XInternAtom(gfx::GetXDisplay(), "noop", False);
270 void UpdateDeviceList() {
271 XDisplay* display = gfx::GetXDisplay();
272 DeviceListCacheX::GetInstance()->UpdateDeviceList(display);
273 TouchFactory::GetInstance()->UpdateDeviceList(display);
274 DeviceDataManager::GetInstance()->UpdateDeviceList(display);
277 EventType EventTypeFromNative(const base::NativeEvent& native_event) {
278 switch (native_event->type) {
280 return ET_KEY_PRESSED;
282 return ET_KEY_RELEASED;
284 if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
285 static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
286 return ET_MOUSEWHEEL;
287 return ET_MOUSE_PRESSED;
289 // Drop wheel events; we should've already scrolled on the press.
290 if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
291 static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
293 return ET_MOUSE_RELEASED;
295 if (native_event->xmotion.state &
296 (Button1Mask | Button2Mask | Button3Mask))
297 return ET_MOUSE_DRAGGED;
298 return ET_MOUSE_MOVED;
300 // The standard on Windows is to send a MouseMove event when the mouse
301 // first enters a window instead of sending a special mouse enter event.
302 // To be consistent we follow the same style.
303 return ET_MOUSE_MOVED;
305 return ET_MOUSE_EXITED;
307 TouchFactory* factory = TouchFactory::GetInstance();
308 if (!factory->ShouldProcessXI2Event(native_event))
311 XIDeviceEvent* xievent =
312 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
314 if (factory->IsTouchDevice(xievent->sourceid))
315 return GetTouchEventType(native_event);
317 switch (xievent->evtype) {
318 case XI_ButtonPress: {
319 int button = EventButtonFromNative(native_event);
320 if (button >= kMinWheelButton && button <= kMaxWheelButton)
321 return ET_MOUSEWHEEL;
322 return ET_MOUSE_PRESSED;
324 case XI_ButtonRelease: {
325 int button = EventButtonFromNative(native_event);
326 // Drop wheel events; we should've already scrolled on the press.
327 if (button >= kMinWheelButton && button <= kMaxWheelButton)
329 return ET_MOUSE_RELEASED;
333 if (GetFlingData(native_event, NULL, NULL, NULL, NULL, &is_cancel)) {
334 return is_cancel ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START;
335 } else if (DeviceDataManager::GetInstance()->IsScrollEvent(
337 return IsTouchpadEvent(native_event) ? ET_SCROLL : ET_MOUSEWHEEL;
338 } else if (DeviceDataManager::GetInstance()->IsCMTMetricsEvent(
341 } else if (GetButtonMaskForX2Event(xievent)) {
342 return ET_MOUSE_DRAGGED;
344 return ET_MOUSE_MOVED;
355 int EventFlagsFromNative(const base::NativeEvent& native_event) {
356 switch (native_event->type) {
359 XModifierStateWatcher::GetInstance()->UpdateStateFromEvent(native_event);
360 return GetEventFlagsFromXState(native_event->xkey.state);
363 case ButtonRelease: {
364 int flags = GetEventFlagsFromXState(native_event->xbutton.state);
365 const EventType type = EventTypeFromNative(native_event);
366 if (type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED)
367 flags |= GetEventFlagsForButton(native_event->xbutton.button);
371 return GetEventFlagsFromXState(native_event->xmotion.state);
373 XIDeviceEvent* xievent =
374 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
376 switch (xievent->evtype) {
377 #if defined(USE_XI2_MT)
381 return GetButtonMaskForX2Event(xievent) |
382 GetEventFlagsFromXState(xievent->mods.effective) |
383 GetEventFlagsFromXState(
384 XModifierStateWatcher::GetInstance()->state());
388 case XI_ButtonRelease: {
390 TouchFactory::GetInstance()->IsTouchDevice(xievent->sourceid);
391 int flags = GetButtonMaskForX2Event(xievent) |
392 GetEventFlagsFromXState(xievent->mods.effective);
394 flags |= GetEventFlagsFromXState(
395 XModifierStateWatcher::GetInstance()->state());
398 const EventType type = EventTypeFromNative(native_event);
399 int button = EventButtonFromNative(native_event);
400 if ((type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) && !touch)
401 flags |= GetEventFlagsForButton(button);
405 return GetButtonMaskForX2Event(xievent) |
406 GetEventFlagsFromXState(xievent->mods.effective);
413 base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
414 switch(native_event->type) {
417 return base::TimeDelta::FromMilliseconds(native_event->xkey.time);
420 return base::TimeDelta::FromMilliseconds(native_event->xbutton.time);
423 return base::TimeDelta::FromMilliseconds(native_event->xmotion.time);
427 return base::TimeDelta::FromMilliseconds(native_event->xcrossing.time);
431 double touch_timestamp;
432 if (GetGestureTimes(native_event, &start, &end)) {
433 // If the driver supports gesture times, use them.
434 return base::TimeDelta::FromMicroseconds(end * 1000000);
435 } else if (DeviceDataManager::GetInstance()->GetEventData(*native_event,
436 DeviceDataManager::DT_TOUCH_RAW_TIMESTAMP, &touch_timestamp)) {
437 return base::TimeDelta::FromMicroseconds(touch_timestamp * 1000000);
439 XIDeviceEvent* xide =
440 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
441 return base::TimeDelta::FromMilliseconds(xide->time);
447 return base::TimeDelta();
450 gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
451 switch (native_event->type) {
454 return gfx::Point(native_event->xcrossing.x, native_event->xcrossing.y);
457 return gfx::Point(native_event->xbutton.x, native_event->xbutton.y);
459 return gfx::Point(native_event->xmotion.x, native_event->xmotion.y);
461 XIDeviceEvent* xievent =
462 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
463 return gfx::Point(static_cast<int>(xievent->event_x),
464 static_cast<int>(xievent->event_y));
470 gfx::Point EventSystemLocationFromNative(
471 const base::NativeEvent& native_event) {
472 switch (native_event->type) {
475 return gfx::Point(native_event->xcrossing.x_root,
476 native_event->xcrossing.y_root);
479 case ButtonRelease: {
480 return gfx::Point(native_event->xbutton.x_root,
481 native_event->xbutton.y_root);
484 return gfx::Point(native_event->xmotion.x_root,
485 native_event->xmotion.y_root);
488 XIDeviceEvent* xievent =
489 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
490 return gfx::Point(xievent->root_x, xievent->root_y);
497 int EventButtonFromNative(const base::NativeEvent& native_event) {
498 CHECK_EQ(GenericEvent, native_event->type);
499 XIDeviceEvent* xievent =
500 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
501 int button = xievent->detail;
503 return (xievent->sourceid == xievent->deviceid) ?
504 DeviceDataManager::GetInstance()->GetMappedButton(button) : button;
507 KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
508 return KeyboardCodeFromXKeyEvent(native_event);
511 bool IsMouseEvent(const base::NativeEvent& native_event) {
512 if (native_event->type == EnterNotify ||
513 native_event->type == LeaveNotify ||
514 native_event->type == ButtonPress ||
515 native_event->type == ButtonRelease ||
516 native_event->type == MotionNotify)
518 if (native_event->type == GenericEvent) {
519 XIDeviceEvent* xievent =
520 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
521 return xievent->evtype == XI_ButtonPress ||
522 xievent->evtype == XI_ButtonRelease ||
523 xievent->evtype == XI_Motion;
528 int GetChangedMouseButtonFlagsFromNative(
529 const base::NativeEvent& native_event) {
530 switch (native_event->type) {
533 return GetEventFlagsFromXState(native_event->xbutton.state);
535 XIDeviceEvent* xievent =
536 static_cast<XIDeviceEvent*>(native_event->xcookie.data);
537 switch (xievent->evtype) {
539 case XI_ButtonRelease:
540 return GetEventFlagsForButton(EventButtonFromNative(native_event));
551 gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
552 float x_offset, y_offset;
553 if (GetScrollOffsets(
554 native_event, &x_offset, &y_offset, NULL, NULL, NULL)) {
555 return gfx::Vector2d(static_cast<int>(x_offset),
556 static_cast<int>(y_offset));
559 int button = native_event->type == GenericEvent ?
560 EventButtonFromNative(native_event) : native_event->xbutton.button;
564 return gfx::Vector2d(0, kWheelScrollAmount);
566 return gfx::Vector2d(0, -kWheelScrollAmount);
568 // TODO(derat): Do something for horizontal scrolls (buttons 6 and 7)?
569 return gfx::Vector2d();
573 void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
574 #if defined(USE_XI2_MT)
575 ui::EventType type = ui::EventTypeFromNative(xev);
576 if (type == ui::ET_TOUCH_CANCELLED ||
577 type == ui::ET_TOUCH_RELEASED) {
578 ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
579 ui::DeviceDataManager* manager = ui::DeviceDataManager::GetInstance();
581 if (manager->GetEventData(
582 *xev, ui::DeviceDataManager::DT_TOUCH_TRACKING_ID, &tracking_id)) {
583 factory->ReleaseSlotForTrackingID(tracking_id);
589 int GetTouchId(const base::NativeEvent& xev) {
591 ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
592 XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
593 #if defined(ENABLE_XI21_MT)
594 // If using XInput2.1 for multi-touch support, the slot is tracked by the
595 // source id of each device event.
596 slot = xievent->sourceid;
598 if (!factory->IsMultiTouchDevice(xievent->sourceid)) {
599 // TODO(sad): Come up with a way to generate touch-ids for multi-touch
600 // events when touch-events are generated from a single-touch device.
604 ui::DeviceDataManager* manager = ui::DeviceDataManager::GetInstance();
606 #if defined(USE_XI2_MT)
608 if (!manager->GetEventData(
609 *xev, ui::DeviceDataManager::DT_TOUCH_TRACKING_ID, &tracking_id)) {
610 LOG(ERROR) << "Could not get the tracking ID for the event. Using 0.";
612 slot = factory->GetSlotForTrackingID(tracking_id);
615 if (!manager->GetEventData(
616 *xev, ui::DeviceDataManager::DT_TOUCH_SLOT_ID, &slot))
617 LOG(ERROR) << "Could not get the slot ID for the event. Using 0.";
622 float GetTouchRadiusX(const base::NativeEvent& native_event) {
623 return GetTouchParamFromXEvent(native_event,
624 ui::DeviceDataManager::DT_TOUCH_MAJOR, 0.0) / 2.0;
627 float GetTouchRadiusY(const base::NativeEvent& native_event) {
628 return GetTouchParamFromXEvent(native_event,
629 ui::DeviceDataManager::DT_TOUCH_MINOR, 0.0) / 2.0;
632 float GetTouchAngle(const base::NativeEvent& native_event) {
633 return GetTouchParamFromXEvent(native_event,
634 ui::DeviceDataManager::DT_TOUCH_ORIENTATION, 0.0) / 2.0;
637 float GetTouchForce(const base::NativeEvent& native_event) {
639 force = GetTouchParamFromXEvent(native_event,
640 ui::DeviceDataManager::DT_TOUCH_PRESSURE, 0.0);
641 unsigned int deviceid =
642 static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
643 // Force is normalized to fall into [0, 1]
644 if (!ui::DeviceDataManager::GetInstance()->NormalizeData(
645 deviceid, ui::DeviceDataManager::DT_TOUCH_PRESSURE, &force))
650 bool GetScrollOffsets(const base::NativeEvent& native_event,
653 float* x_offset_ordinal,
654 float* y_offset_ordinal,
656 if (!DeviceDataManager::GetInstance()->IsScrollEvent(native_event))
659 // Temp values to prevent passing NULLs to DeviceDataManager.
660 float x_offset_, y_offset_;
661 float x_offset_ordinal_, y_offset_ordinal_;
664 x_offset = &x_offset_;
666 y_offset = &y_offset_;
667 if (!x_offset_ordinal)
668 x_offset_ordinal = &x_offset_ordinal_;
669 if (!y_offset_ordinal)
670 y_offset_ordinal = &y_offset_ordinal_;
672 finger_count = &finger_count_;
674 DeviceDataManager::GetInstance()->GetScrollOffsets(
677 x_offset_ordinal, y_offset_ordinal,
682 bool GetFlingData(const base::NativeEvent& native_event,
688 if (!DeviceDataManager::GetInstance()->IsFlingEvent(native_event))
692 float vx_ordinal_, vy_ordinal_;
699 vx_ordinal = &vx_ordinal_;
701 vy_ordinal = &vy_ordinal_;
703 is_cancel = &is_cancel_;
705 DeviceDataManager::GetInstance()->GetFlingData(
706 native_event, vx, vy, vx_ordinal, vy_ordinal, is_cancel);
710 bool GetGestureTimes(const base::NativeEvent& native_event,
713 if (!DeviceDataManager::GetInstance()->HasGestureTimes(native_event))
716 double start_time_, end_time_;
718 start_time = &start_time_;
720 end_time = &end_time_;
722 DeviceDataManager::GetInstance()->GetGestureTimes(
723 native_event, start_time, end_time);
727 void SetNaturalScroll(bool enabled) {
728 DeviceDataManager::GetInstance()->set_natural_scroll_enabled(enabled);
731 bool IsNaturalScrollEnabled() {
732 return DeviceDataManager::GetInstance()->natural_scroll_enabled();
735 bool IsTouchpadEvent(const base::NativeEvent& event) {
736 return DeviceDataManager::GetInstance()->IsTouchpadXInputEvent(event);
739 bool IsNoopEvent(const base::NativeEvent& event) {
740 return (event->type == ClientMessage &&
741 event->xclient.message_type == GetNoopEventAtom());
744 base::NativeEvent CreateNoopEvent() {
745 static XEvent* noop = NULL;
748 memset(noop, 0, sizeof(XEvent));
749 noop->xclient.type = ClientMessage;
750 noop->xclient.window = None;
751 noop->xclient.format = 8;
752 DCHECK(!noop->xclient.display);
754 // Make sure we use atom from current xdisplay, which may
755 // change during the test.
756 noop->xclient.message_type = GetNoopEventAtom();