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
3// found in the LICENSE file.
4
5#include "ui/platform_window/x11/x11_window.h"
6
7#include <X11/extensions/XInput2.h>
8#include <X11/Xatom.h>
9#include <X11/Xlib.h>
10#include <X11/Xutil.h>
11
12#include "ui/events/event.h"
13#include "ui/events/event_utils.h"
14#include "ui/events/platform/platform_event_dispatcher.h"
15#include "ui/events/platform/platform_event_source.h"
16#include "ui/events/platform/x11/x11_event_source.h"
17#include "ui/events/x/touch_factory_x11.h"
18#include "ui/gfx/geometry/rect.h"
19#include "ui/gfx/x/x11_atom_cache.h"
20#include "ui/gfx/x/x11_types.h"
21#include "ui/platform_window/platform_window_delegate.h"
22
23namespace ui {
24
25namespace {
26
27const char* kAtomsToCache[] = {
28  "WM_DELETE_WINDOW",
29  "_NET_WM_PING",
30  "_NET_WM_PID",
31  NULL
32};
33
34XID FindXEventTarget(XEvent* xevent) {
35  XID target = xevent->xany.window;
36  if (xevent->type == GenericEvent)
37    target = static_cast<XIDeviceEvent*>(xevent->xcookie.data)->event;
38  return target;
39}
40
41}  // namespace
42
43X11Window::X11Window(PlatformWindowDelegate* delegate)
44    : delegate_(delegate),
45      xdisplay_(gfx::GetXDisplay()),
46      xwindow_(None),
47      xroot_window_(DefaultRootWindow(xdisplay_)),
48      atom_cache_(xdisplay_, kAtomsToCache),
49      window_mapped_(false) {
50  CHECK(delegate_);
51  TouchFactory::SetTouchDeviceListFromCommandLine();
52}
53
54X11Window::~X11Window() {
55  Destroy();
56}
57
58void X11Window::Destroy() {
59  if (xwindow_ == None)
60    return;
61
62  // Stop processing events.
63  PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
64  XDestroyWindow(xdisplay_, xwindow_);
65  xwindow_ = None;
66}
67
68void X11Window::ProcessXInput2Event(XEvent* xev) {
69  if (!TouchFactory::GetInstance()->ShouldProcessXI2Event(xev))
70    return;
71  EventType event_type = EventTypeFromNative(xev);
72  switch (event_type) {
73    case ET_KEY_PRESSED:
74    case ET_KEY_RELEASED: {
75      KeyEvent key_event(xev);
76      delegate_->DispatchEvent(&key_event);
77      break;
78    }
79    case ET_MOUSE_PRESSED:
80    case ET_MOUSE_MOVED:
81    case ET_MOUSE_DRAGGED:
82    case ET_MOUSE_RELEASED: {
83      MouseEvent mouse_event(xev);
84      delegate_->DispatchEvent(&mouse_event);
85      break;
86    }
87    case ET_MOUSEWHEEL: {
88      MouseWheelEvent wheel_event(xev);
89      delegate_->DispatchEvent(&wheel_event);
90      break;
91    }
92    case ET_SCROLL_FLING_START:
93    case ET_SCROLL_FLING_CANCEL:
94    case ET_SCROLL: {
95      ScrollEvent scroll_event(xev);
96      delegate_->DispatchEvent(&scroll_event);
97      break;
98    }
99    case ET_TOUCH_MOVED:
100    case ET_TOUCH_PRESSED:
101    case ET_TOUCH_CANCELLED:
102    case ET_TOUCH_RELEASED: {
103      TouchEvent touch_event(xev);
104      delegate_->DispatchEvent(&touch_event);
105      break;
106    }
107    default:
108      break;
109  }
110}
111
112void X11Window::Show() {
113  if (window_mapped_)
114    return;
115
116  CHECK(PlatformEventSource::GetInstance());
117  PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
118
119  XSetWindowAttributes swa;
120  memset(&swa, 0, sizeof(swa));
121  swa.background_pixmap = None;
122  swa.override_redirect = False;
123  xwindow_ = XCreateWindow(xdisplay_,
124                           xroot_window_,
125                           requested_bounds_.x(),
126                           requested_bounds_.y(),
127                           requested_bounds_.width(),
128                           requested_bounds_.height(),
129                           0,               // border width
130                           CopyFromParent,  // depth
131                           InputOutput,
132                           CopyFromParent,  // visual
133                           CWBackPixmap | CWOverrideRedirect,
134                           &swa);
135
136  long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
137                    KeyPressMask | KeyReleaseMask | EnterWindowMask |
138                    LeaveWindowMask | ExposureMask | VisibilityChangeMask |
139                    StructureNotifyMask | PropertyChangeMask |
140                    PointerMotionMask;
141  XSelectInput(xdisplay_, xwindow_, event_mask);
142
143  unsigned char mask[XIMaskLen(XI_LASTEVENT)];
144  memset(mask, 0, sizeof(mask));
145
146  XISetMask(mask, XI_TouchBegin);
147  XISetMask(mask, XI_TouchUpdate);
148  XISetMask(mask, XI_TouchEnd);
149  XISetMask(mask, XI_ButtonPress);
150  XISetMask(mask, XI_ButtonRelease);
151  XISetMask(mask, XI_Motion);
152  XISetMask(mask, XI_KeyPress);
153  XISetMask(mask, XI_KeyRelease);
154
155  XIEventMask evmask;
156  evmask.deviceid = XIAllDevices;
157  evmask.mask_len = sizeof(mask);
158  evmask.mask = mask;
159  XISelectEvents(xdisplay_, xwindow_, &evmask, 1);
160  XFlush(xdisplay_);
161
162  ::Atom protocols[2];
163  protocols[0] = atom_cache_.GetAtom("WM_DELETE_WINDOW");
164  protocols[1] = atom_cache_.GetAtom("_NET_WM_PING");
165  XSetWMProtocols(xdisplay_, xwindow_, protocols, 2);
166
167  // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with
168  // the desktop environment.
169  XSetWMProperties(
170      xdisplay_, xwindow_, NULL, NULL, NULL, 0, NULL, NULL, NULL);
171
172  // Likewise, the X server needs to know this window's pid so it knows which
173  // program to kill if the window hangs.
174  // XChangeProperty() expects "pid" to be long.
175  COMPILE_ASSERT(sizeof(long) >= sizeof(pid_t), pid_t_bigger_than_long);
176  long pid = getpid();
177  XChangeProperty(xdisplay_,
178                  xwindow_,
179                  atom_cache_.GetAtom("_NET_WM_PID"),
180                  XA_CARDINAL,
181                  32,
182                  PropModeReplace,
183                  reinterpret_cast<unsigned char*>(&pid),
184                  1);
185  // Before we map the window, set size hints. Otherwise, some window managers
186  // will ignore toplevel XMoveWindow commands.
187  XSizeHints size_hints;
188  size_hints.flags = PPosition | PWinGravity;
189  size_hints.x = requested_bounds_.x();
190  size_hints.y = requested_bounds_.y();
191  // Set StaticGravity so that the window position is not affected by the
192  // frame width when running with window manager.
193  size_hints.win_gravity = StaticGravity;
194  XSetWMNormalHints(xdisplay_, xwindow_, &size_hints);
195
196  delegate_->OnAcceleratedWidgetAvailable(xwindow_);
197
198  XMapWindow(xdisplay_, xwindow_);
199
200  // We now block until our window is mapped. Some X11 APIs will crash and
201  // burn if passed |xwindow_| before the window is mapped, and XMapWindow is
202  // asynchronous.
203  if (X11EventSource::GetInstance())
204    X11EventSource::GetInstance()->BlockUntilWindowMapped(xwindow_);
205  window_mapped_ = true;
206}
207
208void X11Window::Hide() {
209  if (!window_mapped_)
210    return;
211  XWithdrawWindow(xdisplay_, xwindow_, 0);
212  window_mapped_ = false;
213}
214
215void X11Window::Close() {
216  Destroy();
217}
218
219void X11Window::SetBounds(const gfx::Rect& bounds) {
220  requested_bounds_ = bounds;
221  if (!window_mapped_)
222    return;
223  XWindowChanges changes = {0};
224  unsigned value_mask = CWX | CWY | CWWidth | CWHeight;
225  changes.x = bounds.x();
226  changes.y = bounds.y();
227  changes.width = bounds.width();
228  changes.height = bounds.height();
229  XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes);
230}
231
232gfx::Rect X11Window::GetBounds() {
233  return confirmed_bounds_;
234}
235
236void X11Window::SetCapture() {}
237
238void X11Window::ReleaseCapture() {}
239
240void X11Window::ToggleFullscreen() {}
241
242void X11Window::Maximize() {}
243
244void X11Window::Minimize() {}
245
246void X11Window::Restore() {}
247
248void X11Window::SetCursor(PlatformCursor cursor) {}
249
250void X11Window::MoveCursorTo(const gfx::Point& location) {}
251
252bool X11Window::CanDispatchEvent(const PlatformEvent& event) {
253  return FindXEventTarget(event) == xwindow_;
254}
255
256uint32_t X11Window::DispatchEvent(const PlatformEvent& event) {
257  XEvent* xev = event;
258  switch (xev->type) {
259    case EnterNotify: {
260      // EnterNotify creates ET_MOUSE_MOVED. Mark as synthesized as this is
261      // not real mouse move event.
262      MouseEvent mouse_event(xev);
263      CHECK_EQ(ET_MOUSE_MOVED, mouse_event.type());
264      mouse_event.set_flags(mouse_event.flags() | EF_IS_SYNTHESIZED);
265      delegate_->DispatchEvent(&mouse_event);
266      break;
267    }
268    case LeaveNotify: {
269      MouseEvent mouse_event(xev);
270      delegate_->DispatchEvent(&mouse_event);
271      break;
272    }
273
274    case Expose: {
275      gfx::Rect damage_rect(xev->xexpose.x,
276                            xev->xexpose.y,
277                            xev->xexpose.width,
278                            xev->xexpose.height);
279      delegate_->OnDamageRect(damage_rect);
280      break;
281    }
282
283    case KeyPress:
284    case KeyRelease: {
285      KeyEvent key_event(xev);
286      delegate_->DispatchEvent(&key_event);
287      break;
288    }
289
290    case ButtonPress:
291    case ButtonRelease: {
292      switch (EventTypeFromNative(xev)) {
293        case ET_MOUSEWHEEL: {
294          MouseWheelEvent mouseev(xev);
295          delegate_->DispatchEvent(&mouseev);
296          break;
297        }
298        case ET_MOUSE_PRESSED:
299        case ET_MOUSE_RELEASED: {
300          MouseEvent mouseev(xev);
301          delegate_->DispatchEvent(&mouseev);
302          break;
303        }
304        case ET_UNKNOWN:
305          // No event is created for X11-release events for mouse-wheel
306          // buttons.
307          break;
308        default:
309          NOTREACHED();
310      }
311      break;
312    }
313
314    case FocusOut:
315      if (xev->xfocus.mode != NotifyGrab)
316        delegate_->OnLostCapture();
317      break;
318
319    case ConfigureNotify: {
320      DCHECK_EQ(xwindow_, xev->xconfigure.event);
321      DCHECK_EQ(xwindow_, xev->xconfigure.window);
322      gfx::Rect bounds(xev->xconfigure.x,
323                       xev->xconfigure.y,
324                       xev->xconfigure.width,
325                       xev->xconfigure.height);
326      if (confirmed_bounds_ != bounds) {
327        confirmed_bounds_ = bounds;
328        delegate_->OnBoundsChanged(confirmed_bounds_);
329      }
330      break;
331    }
332
333    case ClientMessage: {
334      Atom message = static_cast<Atom>(xev->xclient.data.l[0]);
335      if (message == atom_cache_.GetAtom("WM_DELETE_WINDOW")) {
336        delegate_->OnCloseRequest();
337      } else if (message == atom_cache_.GetAtom("_NET_WM_PING")) {
338        XEvent reply_event = *xev;
339        reply_event.xclient.window = xroot_window_;
340
341        XSendEvent(xdisplay_,
342                   reply_event.xclient.window,
343                   False,
344                   SubstructureRedirectMask | SubstructureNotifyMask,
345                   &reply_event);
346        XFlush(xdisplay_);
347      }
348      break;
349    }
350
351    case GenericEvent: {
352      ProcessXInput2Event(xev);
353      break;
354    }
355  }
356  return POST_DISPATCH_STOP_PROPAGATION;
357}
358
359}  // namespace ui
360