1 // Copyright 2019 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 #include "ui/events/x/x11_event_translation.h"
6 
7 #include <vector>
8 
9 #include "base/check.h"
10 #include "base/notreached.h"
11 #include "base/time/time.h"
12 #include "ui/events/devices/x11/touch_factory_x11.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_utils.h"
15 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
16 #include "ui/events/pointer_details.h"
17 #include "ui/events/types/event_type.h"
18 #include "ui/events/x/events_x_utils.h"
19 #include "ui/gfx/geometry/point.h"
20 #include "ui/gfx/x/xproto.h"
21 
22 #if defined(USE_OZONE)
23 #include "ui/base/ui_base_features.h"
24 #endif
25 
26 namespace ui {
27 
28 namespace {
29 
XkbGroupForCoreState(int state)30 int XkbGroupForCoreState(int state) {
31   return (state >> 13) & 0x3;
32 }
33 
34 // In X11 touch events, a new tracking_id/slot mapping is set up for each new
35 // event (see |ui::GetTouchIdFromXEvent| function), which needs to be cleared
36 // at destruction time for corresponding release/cancel events. In this
37 // particular case, ui::TouchEvent class is extended so that dtor can be
38 // overridden in order to implement this platform-specific behavior.
39 class TouchEventX11 : public ui::TouchEvent {
40  public:
TouchEventX11(EventType type,gfx::Point location,base::TimeTicks timestamp,const PointerDetails & pointer_details)41   TouchEventX11(EventType type,
42                 gfx::Point location,
43                 base::TimeTicks timestamp,
44                 const PointerDetails& pointer_details)
45       : TouchEvent(type, location, timestamp, pointer_details) {}
46 
~TouchEventX11()47   ~TouchEventX11() override {
48     if (type() == ET_TOUCH_RELEASED || type() == ET_TOUCH_CANCELLED)
49       TouchFactory::GetInstance()->ReleaseSlot(pointer_details().id);
50   }
51 };
52 
GetEventPropertiesFromXEvent(EventType type,const x11::Event & x11_event)53 Event::Properties GetEventPropertiesFromXEvent(EventType type,
54                                                const x11::Event& x11_event) {
55   using Values = std::vector<uint8_t>;
56   Event::Properties properties;
57   if (type == ET_KEY_PRESSED || type == ET_KEY_RELEASED) {
58     auto* key = x11_event.As<x11::KeyEvent>();
59 
60     // Keyboard group
61     auto state = static_cast<uint32_t>(key->state);
62     uint8_t group = XkbGroupForCoreState(state);
63     properties.emplace(kPropertyKeyboardGroup, Values{group});
64 
65     // Hardware keycode
66     uint8_t hw_keycode = static_cast<uint8_t>(key->detail);
67     properties.emplace(kPropertyKeyboardHwKeyCode, Values{hw_keycode});
68 
69     // IBus-gtk specific flags
70     uint8_t ibus_flags = (state >> kPropertyKeyboardIBusFlagOffset) &
71                          kPropertyKeyboardIBusFlagMask;
72     if (ibus_flags)
73       properties.emplace(kPropertyKeyboardIBusFlag, Values{ibus_flags});
74 
75   } else if (type == ET_MOUSE_EXITED) {
76     // NotifyVirtual events are created for intermediate windows that the
77     // pointer crosses through. These occur when middle clicking.
78     // Change these into mouse move events.
79     auto* crossing = x11_event.As<x11::CrossingEvent>();
80     bool crossing_intermediate_window =
81         crossing->detail == x11::NotifyDetail::Virtual;
82     if (crossing_intermediate_window) {
83       properties.emplace(kPropertyMouseCrossedIntermediateWindow,
84                          crossing_intermediate_window);
85     }
86   }
87   return properties;
88 }
89 
CreateKeyEvent(EventType event_type,const x11::Event & x11_event)90 std::unique_ptr<KeyEvent> CreateKeyEvent(EventType event_type,
91                                          const x11::Event& x11_event) {
92   KeyboardCode key_code = KeyboardCodeFromXKeyEvent(x11_event);
93   int event_flags = EventFlagsFromXEvent(x11_event);
94 
95   // In Ozone builds, keep DomCode/DomKey unset, so they are extracted lazily
96   // in KeyEvent::ApplyLayout() which makes it possible for CrOS/Linux, for
97   // example, to support host system keyboard layouts.
98   std::unique_ptr<KeyEvent> event;
99 #if defined(USE_OZONE)
100   if (features::IsUsingOzonePlatform()) {
101     event = std::make_unique<KeyEvent>(event_type, key_code, event_flags,
102                                        EventTimeFromXEvent(x11_event));
103   }
104 #endif
105 #if defined(USE_X11)
106   if (!event) {
107     event = std::make_unique<KeyEvent>(
108         event_type, key_code, CodeFromXEvent(x11_event), event_flags,
109         GetDomKeyFromXEvent(x11_event), EventTimeFromXEvent(x11_event));
110   }
111 #endif
112 
113   DCHECK(event);
114   event->SetProperties(GetEventPropertiesFromXEvent(event_type, x11_event));
115   event->InitializeNative();
116   return event;
117 }
118 
SetEventSourceDeviceId(MouseEvent * event,const x11::Event & xev)119 void SetEventSourceDeviceId(MouseEvent* event, const x11::Event& xev) {
120   DCHECK(event);
121   if (auto* xiev = xev.As<x11::Input::DeviceEvent>())
122     event->set_source_device_id(static_cast<uint16_t>(xiev->sourceid));
123 }
124 
CreateMouseEvent(EventType type,const x11::Event & x11_event)125 std::unique_ptr<MouseEvent> CreateMouseEvent(EventType type,
126                                              const x11::Event& x11_event) {
127   // Ignore EventNotify and LeaveNotify events from children of |xwindow_|.
128   // NativeViewGLSurfaceGLX adds a child to |xwindow_|.
129   // https://crbug.com/792322
130   auto* crossing = x11_event.As<x11::CrossingEvent>();
131   if (crossing && crossing->detail == x11::NotifyDetail::Inferior)
132     return nullptr;
133 
134   PointerDetails details{EventPointerType::kMouse};
135   auto event = std::make_unique<MouseEvent>(
136       type, EventLocationFromXEvent(x11_event),
137       EventSystemLocationFromXEvent(x11_event), EventTimeFromXEvent(x11_event),
138       EventFlagsFromXEvent(x11_event),
139       GetChangedMouseButtonFlagsFromXEvent(x11_event), details);
140 
141   DCHECK(event);
142   SetEventSourceDeviceId(event.get(), x11_event);
143   event->SetProperties(GetEventPropertiesFromXEvent(type, x11_event));
144   event->InitializeNative();
145   return event;
146 }
147 
CreateMouseWheelEvent(const x11::Event & x11_event)148 std::unique_ptr<MouseWheelEvent> CreateMouseWheelEvent(
149     const x11::Event& x11_event) {
150   int button_flags = x11_event.As<x11::Input::DeviceEvent>()
151                          ? GetChangedMouseButtonFlagsFromXEvent(x11_event)
152                          : 0;
153   auto event = std::make_unique<MouseWheelEvent>(
154       GetMouseWheelOffsetFromXEvent(x11_event),
155       EventLocationFromXEvent(x11_event),
156       EventSystemLocationFromXEvent(x11_event), EventTimeFromXEvent(x11_event),
157       EventFlagsFromXEvent(x11_event), button_flags);
158 
159   DCHECK(event);
160   event->InitializeNative();
161   return event;
162 }
163 
CreateTouchEvent(EventType type,const x11::Event & xev)164 std::unique_ptr<TouchEvent> CreateTouchEvent(EventType type,
165                                              const x11::Event& xev) {
166   auto event = std::make_unique<TouchEventX11>(
167       type, EventLocationFromXEvent(xev), EventTimeFromXEvent(xev),
168       GetTouchPointerDetailsFromXEvent(xev));
169 #if defined(USE_OZONE)
170   if (features::IsUsingOzonePlatform()) {
171     // Touch events don't usually have |root_location| set differently than
172     // |location|, since there is a touch device to display association, but
173     // this doesn't happen in Ozone X11.
174     event->set_root_location(EventSystemLocationFromXEvent(xev));
175   }
176 #endif
177   return event;
178 }
179 
CreateScrollEvent(EventType type,const x11::Event & xev)180 std::unique_ptr<ScrollEvent> CreateScrollEvent(EventType type,
181                                                const x11::Event& xev) {
182   float x_offset, y_offset, x_offset_ordinal, y_offset_ordinal;
183   int finger_count = 0;
184 
185   if (type == ET_SCROLL) {
186     GetScrollOffsetsFromXEvent(xev, &x_offset, &y_offset, &x_offset_ordinal,
187                                &y_offset_ordinal, &finger_count);
188   } else {
189     GetFlingDataFromXEvent(xev, &x_offset, &y_offset, &x_offset_ordinal,
190                            &y_offset_ordinal, nullptr);
191   }
192   auto event = std::make_unique<ScrollEvent>(
193       type, EventLocationFromXEvent(xev), EventTimeFromXEvent(xev),
194       EventFlagsFromXEvent(xev), x_offset, y_offset, x_offset_ordinal,
195       y_offset_ordinal, finger_count);
196 
197   DCHECK(event);
198   // We need to filter zero scroll offset here. Because MouseWheelEventQueue
199   // assumes we'll never get a zero scroll offset event and we need delta to
200   // determine which element to scroll on phaseBegan.
201   return (event->x_offset() != 0.0 || event->y_offset() != 0.0)
202              ? std::move(event)
203              : nullptr;
204 }
205 
206 // Translates XI2 XEvent into a ui::Event.
TranslateFromXI2Event(const x11::Event & xev,EventType event_type)207 std::unique_ptr<ui::Event> TranslateFromXI2Event(const x11::Event& xev,
208                                                  EventType event_type) {
209   switch (event_type) {
210     case ET_KEY_PRESSED:
211     case ET_KEY_RELEASED:
212       return CreateKeyEvent(event_type, xev);
213     case ET_MOUSE_PRESSED:
214     case ET_MOUSE_RELEASED:
215     case ET_MOUSE_MOVED:
216     case ET_MOUSE_DRAGGED:
217     case ET_MOUSE_ENTERED:
218     case ET_MOUSE_EXITED:
219       return CreateMouseEvent(event_type, xev);
220     case ET_MOUSEWHEEL:
221       return CreateMouseWheelEvent(xev);
222     case ET_SCROLL_FLING_START:
223     case ET_SCROLL_FLING_CANCEL:
224     case ET_SCROLL:
225       return CreateScrollEvent(event_type, xev);
226     case ET_TOUCH_MOVED:
227     case ET_TOUCH_PRESSED:
228     case ET_TOUCH_CANCELLED:
229     case ET_TOUCH_RELEASED:
230       return CreateTouchEvent(event_type, xev);
231     case ET_UNKNOWN:
232       return nullptr;
233     default:
234       break;
235   }
236   return nullptr;
237 }
238 
TranslateFromXEvent(const x11::Event & xev)239 std::unique_ptr<Event> TranslateFromXEvent(const x11::Event& xev) {
240   EventType event_type = EventTypeFromXEvent(xev);
241   if (xev.As<x11::CrossingEvent>() || xev.As<x11::MotionNotifyEvent>())
242     return CreateMouseEvent(event_type, xev);
243   if (xev.As<x11::KeyEvent>())
244     return CreateKeyEvent(event_type, xev);
245   if (xev.As<x11::ButtonEvent>()) {
246     switch (event_type) {
247       case ET_MOUSEWHEEL:
248         return CreateMouseWheelEvent(xev);
249       case ET_MOUSE_PRESSED:
250       case ET_MOUSE_RELEASED:
251         return CreateMouseEvent(event_type, xev);
252       case ET_UNKNOWN:
253         // No event is created for X11-release events for mouse-wheel
254         // buttons.
255         break;
256       default:
257         NOTREACHED();
258     }
259   }
260   if (xev.As<x11::Input::DeviceEvent>())
261     return TranslateFromXI2Event(xev, event_type);
262   return nullptr;
263 }
264 
265 }  // namespace
266 
267 // Translates a XEvent into a ui::Event.
BuildEventFromXEvent(const x11::Event & xev)268 std::unique_ptr<Event> BuildEventFromXEvent(const x11::Event& xev) {
269   auto event = TranslateFromXEvent(xev);
270   if (event)
271     ui::ComputeEventLatencyOS(event.get());
272   return event;
273 }
274 
275 // Convenience function that translates XEvent into ui::KeyEvent
BuildKeyEventFromXEvent(const x11::Event & xev)276 std::unique_ptr<KeyEvent> BuildKeyEventFromXEvent(const x11::Event& xev) {
277   auto event = BuildEventFromXEvent(xev);
278   if (!event || !event->IsKeyEvent())
279     return nullptr;
280   return std::unique_ptr<KeyEvent>{event.release()->AsKeyEvent()};
281 }
282 
283 // Convenience function that translates XEvent into ui::MouseEvent
BuildMouseEventFromXEvent(const x11::Event & xev)284 std::unique_ptr<MouseEvent> BuildMouseEventFromXEvent(const x11::Event& xev) {
285   auto event = BuildEventFromXEvent(xev);
286   if (!event || !event->IsMouseEvent())
287     return nullptr;
288   return std::unique_ptr<MouseEvent>{event.release()->AsMouseEvent()};
289 }
290 
291 // Convenience function that translates XEvent into ui::TouchEvent
BuildTouchEventFromXEvent(const x11::Event & xev)292 std::unique_ptr<TouchEvent> BuildTouchEventFromXEvent(const x11::Event& xev) {
293   auto event = BuildEventFromXEvent(xev);
294   if (!event || !event->IsTouchEvent())
295     return nullptr;
296   return std::unique_ptr<TouchEvent>{event.release()->AsTouchEvent()};
297 }
298 
299 // Convenience function that translates XEvent into ui::MouseWheelEvent
BuildMouseWheelEventFromXEvent(const x11::Event & xev)300 std::unique_ptr<MouseWheelEvent> BuildMouseWheelEventFromXEvent(
301     const x11::Event& xev) {
302   auto event = BuildEventFromXEvent(xev);
303   if (!event || !event->IsMouseWheelEvent())
304     return nullptr;
305   return std::unique_ptr<MouseWheelEvent>{event.release()->AsMouseWheelEvent()};
306 }
307 
308 }  // namespace ui
309