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/base/x/x11_window.h"
6 
7 #include <algorithm>
8 #include <vector>
9 
10 #include "base/location.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/thread_task_runner_handle.h"
15 #include "base/time/time.h"
16 #include "third_party/skia/include/core/SkRegion.h"
17 #include "ui/base/hit_test_x11.h"
18 #include "ui/base/wm_role_names_linux.h"
19 #include "ui/base/x/x11_menu_registrar.h"
20 #include "ui/base/x/x11_pointer_grab.h"
21 #include "ui/base/x/x11_util.h"
22 #include "ui/base/x/x11_util_internal.h"
23 #include "ui/events/devices/x11/device_data_manager_x11.h"
24 #include "ui/events/devices/x11/touch_factory_x11.h"
25 #include "ui/events/event.h"
26 #include "ui/events/event_utils.h"
27 #include "ui/events/platform/x11/x11_event_source.h"
28 #include "ui/events/x/events_x_utils.h"
29 #include "ui/events/x/x11_window_event_manager.h"
30 #include "ui/gfx/geometry/insets.h"
31 #include "ui/gfx/image/image_skia.h"
32 #include "ui/gfx/image/image_skia_rep.h"
33 #include "ui/gfx/skia_util.h"
34 #include "ui/gfx/x/x11_atom_cache.h"
35 #include "ui/gfx/x/x11_error_tracker.h"
36 #include "ui/gfx/x/x11_path.h"
37 #include "ui/platform_window/common/platform_window_defaults.h"
38 
39 namespace ui {
40 
41 namespace {
42 
43 // Special value of the _NET_WM_DESKTOP property which indicates that the window
44 // should appear on all workspaces/desktops.
45 const int kAllWorkspaces = 0xFFFFFFFF;
46 
47 constexpr char kX11WindowRolePopup[] = "popup";
48 constexpr char kX11WindowRoleBubble[] = "bubble";
49 constexpr unsigned char kDarkGtkThemeVariant[] = "dark";
50 
51 constexpr long kSystemTrayRequestDock = 0;
52 
53 constexpr int kXembedInfoProtocolVersion = 0;
54 constexpr int kXembedFlagMap = 1 << 0;
55 constexpr int kXembedInfoFlags = kXembedFlagMap;
56 
57 // In some situations, views tries to make a zero sized window, and that
58 // makes us crash. Make sure we have valid sizes.
SanitizeBounds(const gfx::Rect & bounds)59 gfx::Rect SanitizeBounds(const gfx::Rect& bounds) {
60   gfx::Size sanitized_size(std::max(bounds.width(), 1),
61                            std::max(bounds.height(), 1));
62   gfx::Rect sanitized_bounds(bounds.origin(), sanitized_size);
63   return sanitized_bounds;
64 }
65 
SerializeImageRepresentation(const gfx::ImageSkiaRep & rep,std::vector<unsigned long> * data)66 void SerializeImageRepresentation(const gfx::ImageSkiaRep& rep,
67                                   std::vector<unsigned long>* data) {
68   int width = rep.GetWidth();
69   data->push_back(width);
70 
71   int height = rep.GetHeight();
72   data->push_back(height);
73 
74   const SkBitmap& bitmap = rep.GetBitmap();
75 
76   for (int y = 0; y < height; ++y)
77     for (int x = 0; x < width; ++x)
78       data->push_back(bitmap.getColor(x, y));
79 }
80 
XI2ModeToXMode(int xi2_mode)81 int XI2ModeToXMode(int xi2_mode) {
82   switch (xi2_mode) {
83     case XINotifyNormal:
84       return NotifyNormal;
85     case XINotifyGrab:
86     case XINotifyPassiveGrab:
87       return NotifyGrab;
88     case XINotifyUngrab:
89     case XINotifyPassiveUngrab:
90       return NotifyUngrab;
91     case XINotifyWhileGrabbed:
92       return NotifyWhileGrabbed;
93     default:
94       NOTREACHED();
95       return NotifyNormal;
96   }
97 }
98 
SyncSetCounter(XDisplay * display,XID counter,int64_t value)99 bool SyncSetCounter(XDisplay* display, XID counter, int64_t value) {
100   XSyncValue sync_value;
101   XSyncIntsToValue(&sync_value, value & 0xFFFFFFFF, value >> 32);
102   return XSyncSetCounter(display, counter, sync_value) == x11::True;
103 }
104 
105 // Returns the whole path from |window| to the root.
GetParentsList(XDisplay * xdisplay,::Window window)106 std::vector<::Window> GetParentsList(XDisplay* xdisplay, ::Window window) {
107   ::Window parent_win, root_win;
108   Window* child_windows;
109   unsigned int num_child_windows;
110   std::vector<::Window> result;
111 
112   while (window) {
113     result.push_back(window);
114     if (!XQueryTree(xdisplay, window, &root_win, &parent_win, &child_windows,
115                     &num_child_windows))
116       break;
117     if (child_windows)
118       XFree(child_windows);
119     window = parent_win;
120   }
121   return result;
122 }
123 
124 }  // namespace
125 
Configuration()126 XWindow::Configuration::Configuration()
127     : type(WindowType::kWindow),
128       opacity(WindowOpacity::kInferOpacity),
129       icon(nullptr),
130       activatable(true),
131       force_show_in_taskbar(false),
132       keep_on_top(false),
133       visible_on_all_workspaces(false),
134       remove_standard_frame(true),
135       prefer_dark_theme(false) {}
136 
137 XWindow::Configuration::Configuration(const Configuration&) = default;
138 
139 XWindow::Configuration::~Configuration() = default;
140 
XWindow()141 XWindow::XWindow()
142     : xdisplay_(gfx::GetXDisplay()),
143       x_root_window_(DefaultRootWindow(xdisplay_)) {
144   DCHECK(xdisplay_);
145   DCHECK_NE(x_root_window_, x11::None);
146 }
147 
~XWindow()148 XWindow::~XWindow() {
149   DCHECK_EQ(xwindow_, x11::None) << "XWindow destructed without calling "
150                                     "Close() to release allocated resources.";
151 }
152 
Init(const Configuration & config)153 void XWindow::Init(const Configuration& config) {
154   // Ensure that the X11MenuRegistrar exists. The X11MenuRegistrar is
155   // necessary to properly track menu windows.
156   X11MenuRegistrar::Get();
157 
158   activatable_ = config.activatable;
159 
160   unsigned long attribute_mask = CWBackPixel | CWBitGravity;
161   XSetWindowAttributes swa;
162   memset(&swa, 0, sizeof(swa));
163   swa.background_pixmap = x11::None;
164   swa.bit_gravity = NorthWestGravity;
165   swa.background_pixel = config.background_color.has_value()
166                              ? config.background_color.value()
167                              : WhitePixel(xdisplay_, DefaultScreen(xdisplay_));
168 
169   XAtom window_type;
170   switch (config.type) {
171     case WindowType::kMenu:
172       swa.override_redirect = x11::True;
173       window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU");
174       break;
175     case WindowType::kTooltip:
176       swa.override_redirect = x11::True;
177       window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP");
178       break;
179     case WindowType::kPopup:
180       swa.override_redirect = x11::True;
181       window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION");
182       break;
183     case WindowType::kDrag:
184       swa.override_redirect = x11::True;
185       window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_DND");
186       break;
187     default:
188       window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
189       break;
190   }
191   // An in-activatable window should not interact with the system wm.
192   if (!activatable_ || config.override_redirect)
193     swa.override_redirect = x11::True;
194 
195 #if !defined(USE_X11)
196   // It seems like there is a difference how tests are instantiated in case of
197   // non-Ozone X11 and Ozone. See more details in
198   // EnableTestConfigForPlatformWindows. The reason why this must be here is
199   // that we removed X11WindowBase in favor of the XWindow. The X11WindowBase
200   // was only used with PlatformWindow, which meant non-Ozone X11 did not use it
201   // and set override_redirect based only on |activatable_| variable or
202   // WindowType. But now as XWindow is subclassed by X11Window, which is also a
203   // PlatformWindow, and non-Ozone X11 uses it, we have to add this workaround
204   // here. Otherwise, tests for non-Ozone X11 fail.
205   // TODO(msisov): figure out usage of this for non-Ozone X11.
206   if (UseTestConfigForPlatformWindows())
207     swa.override_redirect = true;
208 #endif
209 
210   override_redirect_ = swa.override_redirect == x11::True;
211   if (override_redirect_)
212     attribute_mask |= CWOverrideRedirect;
213 
214   bool enable_transparent_visuals;
215   switch (config.opacity) {
216     case WindowOpacity::kOpaqueWindow:
217       enable_transparent_visuals = false;
218       break;
219     case WindowOpacity::kTranslucentWindow:
220       enable_transparent_visuals = true;
221       break;
222     case WindowOpacity::kInferOpacity:
223       enable_transparent_visuals = config.type == WindowType::kDrag;
224   }
225 
226   int visual_id;
227   if (config.wm_role_name == kStatusIconWmRoleName) {
228     std::string atom_name =
229         "_NET_SYSTEM_TRAY_S" + base::NumberToString(DefaultScreen(xdisplay_));
230     XID manager =
231         XGetSelectionOwner(xdisplay_, gfx::GetAtom(atom_name.c_str()));
232     if (ui::GetIntProperty(manager, "_NET_SYSTEM_TRAY_VISUAL", &visual_id))
233       visual_id_ = visual_id;
234   }
235 
236   Visual* visual = CopyFromParent;
237   int depth = CopyFromParent;
238   Colormap colormap = CopyFromParent;
239   ui::XVisualManager* visual_manager = ui::XVisualManager::GetInstance();
240   if (!visual_id_ ||
241       !visual_manager->GetVisualInfo(visual_id_, &visual, &depth, &colormap,
242                                      &visual_has_alpha_)) {
243     visual_manager->ChooseVisualForWindow(enable_transparent_visuals, &visual,
244                                           &depth, &colormap,
245                                           &visual_has_alpha_);
246   }
247 
248   if (colormap != CopyFromParent) {
249     attribute_mask |= CWColormap;
250     swa.colormap = colormap;
251   }
252 
253   // x.org will BadMatch if we don't set a border when the depth isn't the
254   // same as the parent depth.
255   attribute_mask |= CWBorderPixel;
256   swa.border_pixel = 0;
257 
258   bounds_in_pixels_ = SanitizeBounds(config.bounds);
259   xwindow_ = XCreateWindow(xdisplay_, x_root_window_, bounds_in_pixels_.x(),
260                            bounds_in_pixels_.y(), bounds_in_pixels_.width(),
261                            bounds_in_pixels_.height(),
262                            0,  // border width
263                            depth, InputOutput, visual, attribute_mask, &swa);
264 
265   // It can be a status icon window. If it fails to initialize, don't provide
266   // him with a native window handle, close self and let the client destroy
267   // ourselves.
268   if (config.wm_role_name == kStatusIconWmRoleName &&
269       !InitializeAsStatusIcon()) {
270     Close();
271     return;
272   }
273 
274   OnXWindowCreated();
275 
276   // TODO(erg): Maybe need to set a ViewProp here like in RWHL::RWHL().
277 
278   long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
279                     KeyPressMask | KeyReleaseMask | EnterWindowMask |
280                     LeaveWindowMask | ExposureMask | VisibilityChangeMask |
281                     StructureNotifyMask | PropertyChangeMask |
282                     PointerMotionMask;
283   xwindow_events_ =
284       std::make_unique<ui::XScopedEventSelector>(xwindow_, event_mask);
285   XFlush(xdisplay_);
286 
287   if (ui::IsXInput2Available())
288     ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_);
289 
290   // TODO(erg): We currently only request window deletion events. We also
291   // should listen for activation events and anything else that GTK+ listens
292   // for, and do something useful.
293   // Request the _NET_WM_SYNC_REQUEST protocol which is used for synchronizing
294   // between chrome and desktop compositor (or WM) during resizing.
295   // The resizing behavior with _NET_WM_SYNC_REQUEST is:
296   // 1. Desktop compositor (or WM) sends client message _NET_WM_SYNC_REQUEST
297   //    with a 64 bits counter to notify about an incoming resize.
298   // 2. Desktop compositor resizes chrome browser window.
299   // 3. Desktop compositor waits on an alert on value change of XSyncCounter on
300   //    chrome window.
301   // 4. Chrome handles the ConfigureNotify event, and renders a new frame with
302   //    the new size.
303   // 5. Chrome increases the XSyncCounter on chrome window
304   // 6. Desktop compositor gets the alert of counter change, and draws a new
305   //    frame with new content from chrome.
306   // 7. Desktop compositor responses user mouse move events, and starts a new
307   //    resize process, go to step 1.
308   XAtom protocols[] = {
309       gfx::GetAtom("WM_DELETE_WINDOW"),
310       gfx::GetAtom("_NET_WM_PING"),
311       gfx::GetAtom("_NET_WM_SYNC_REQUEST"),
312   };
313   XSetWMProtocols(xdisplay_, xwindow_, protocols, base::size(protocols));
314 
315   // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with
316   // the desktop environment.
317   XSetWMProperties(xdisplay_, xwindow_, nullptr, nullptr, nullptr, 0, nullptr,
318                    nullptr, nullptr);
319 
320   // Likewise, the X server needs to know this window's pid so it knows which
321   // program to kill if the window hangs.
322   // XChangeProperty() expects "pid" to be long.
323   static_assert(sizeof(long) >= sizeof(pid_t),
324                 "pid_t should not be larger than long");
325   long pid = getpid();
326   XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_PID"), XA_CARDINAL,
327                   32, PropModeReplace, reinterpret_cast<unsigned char*>(&pid),
328                   1);
329 
330   XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_WINDOW_TYPE"),
331                   XA_ATOM, 32, PropModeReplace,
332                   reinterpret_cast<unsigned char*>(&window_type), 1);
333 
334   // The changes to |window_properties_| here will be sent to the X server just
335   // before the window is mapped.
336 
337   // Remove popup windows from taskbar unless overridden.
338   if ((config.type == WindowType::kPopup ||
339        config.type == WindowType::kBubble) &&
340       !config.force_show_in_taskbar) {
341     window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_SKIP_TASKBAR"));
342   }
343 
344   // If the window should stay on top of other windows, add the
345   // _NET_WM_STATE_ABOVE property.
346   is_always_on_top_ = config.keep_on_top;
347   if (is_always_on_top_)
348     window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_ABOVE"));
349 
350   workspace_ = base::nullopt;
351   if (config.visible_on_all_workspaces) {
352     window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_STICKY"));
353     ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllWorkspaces);
354   } else if (!config.workspace.empty()) {
355     int workspace;
356     if (base::StringToInt(config.workspace, &workspace))
357       ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", workspace);
358   }
359 
360   if (!config.wm_class_name.empty() || !config.wm_class_class.empty()) {
361     ui::SetWindowClassHint(xdisplay_, xwindow_, config.wm_class_name,
362                            config.wm_class_class);
363   }
364 
365   const char* wm_role_name = nullptr;
366   // If the widget isn't overriding the role, provide a default value for popup
367   // and bubble types.
368   if (!config.wm_role_name.empty()) {
369     wm_role_name = config.wm_role_name.c_str();
370   } else {
371     switch (config.type) {
372       case WindowType::kPopup:
373         wm_role_name = kX11WindowRolePopup;
374         break;
375       case WindowType::kBubble:
376         wm_role_name = kX11WindowRoleBubble;
377         break;
378       default:
379         break;
380     }
381   }
382   if (wm_role_name)
383     ui::SetWindowRole(xdisplay_, xwindow_, std::string(wm_role_name));
384 
385   if (config.remove_standard_frame) {
386     // Setting _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED tells gnome-shell to not force
387     // fullscreen on the window when it matches the desktop size.
388     ui::SetHideTitlebarWhenMaximizedProperty(xwindow_,
389                                              ui::HIDE_TITLEBAR_WHEN_MAXIMIZED);
390   }
391 
392   if (config.prefer_dark_theme) {
393     XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_GTK_THEME_VARIANT"),
394                     gfx::GetAtom("UTF8_STRING"), 8, PropModeReplace,
395                     kDarkGtkThemeVariant, base::size(kDarkGtkThemeVariant) - 1);
396   }
397 
398   if (ui::IsSyncExtensionAvailable()) {
399     XSyncValue value;
400     XSyncIntToValue(&value, 0);
401     update_counter_ = XSyncCreateCounter(xdisplay_, value);
402     extended_update_counter_ = XSyncCreateCounter(xdisplay_, value);
403     XID counters[]{update_counter_, extended_update_counter_};
404 
405     // Set XSyncCounter as window property _NET_WM_SYNC_REQUEST_COUNTER. the
406     // compositor will listen on them during resizing.
407     XChangeProperty(
408         xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_SYNC_REQUEST_COUNTER"),
409         XA_CARDINAL, 32, PropModeReplace,
410         reinterpret_cast<const unsigned char*>(counters), base::size(counters));
411   }
412 
413   // Always composite Chromium windows if a compositing WM is used.  Sometimes,
414   // WMs will not composite fullscreen windows as an optimization, but this can
415   // lead to tearing of fullscreen videos.
416   ui::SetIntProperty(xwindow_, "_NET_WM_BYPASS_COMPOSITOR", "CARDINAL", 2);
417 
418   if (config.icon)
419     SetXWindowIcons(gfx::ImageSkia(), *config.icon);
420 }
421 
Map(bool inactive)422 void XWindow::Map(bool inactive) {
423   // Before we map the window, set size hints. Otherwise, some window managers
424   // will ignore toplevel XMoveWindow commands.
425   XSizeHints size_hints;
426   size_hints.flags = 0;
427   long supplied_return;
428   XGetWMNormalHints(xdisplay_, xwindow_, &size_hints, &supplied_return);
429   size_hints.flags |= PPosition;
430   size_hints.x = bounds_in_pixels_.x();
431   size_hints.y = bounds_in_pixels_.y();
432   XSetWMNormalHints(xdisplay_, xwindow_, &size_hints);
433 
434   ignore_keyboard_input_ = inactive;
435   unsigned long wm_user_time_ms =
436       ignore_keyboard_input_ ? 0
437                              : X11EventSource::GetInstance()->GetTimestamp();
438   if (inactive || wm_user_time_ms != 0) {
439     XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"),
440                     XA_CARDINAL, 32, PropModeReplace,
441                     reinterpret_cast<const unsigned char*>(&wm_user_time_ms),
442                     1);
443   }
444 
445   UpdateMinAndMaxSize();
446 
447   if (window_properties_.empty()) {
448     XDeleteProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_STATE"));
449   } else {
450     ui::SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM",
451                              std::vector<XAtom>(std::begin(window_properties_),
452                                                 std::end(window_properties_)));
453   }
454 
455   XMapWindow(xdisplay_, xwindow_);
456   window_mapped_in_client_ = true;
457 
458   // TODO(thomasanderson): Find out why this flush is necessary.
459   XFlush(xdisplay_);
460 }
461 
Close()462 void XWindow::Close() {
463   if (xwindow_ == x11::None)
464     return;
465 
466   CancelResize();
467   UnconfineCursor();
468 
469   XDestroyWindow(xdisplay_, xwindow_);
470   xwindow_ = x11::None;
471 
472   if (update_counter_ != x11::None) {
473     XSyncDestroyCounter(xdisplay_, update_counter_);
474     XSyncDestroyCounter(xdisplay_, extended_update_counter_);
475     update_counter_ = x11::None;
476     extended_update_counter_ = x11::None;
477   }
478 }
479 
Maximize()480 void XWindow::Maximize() {
481   // Some WMs do not respect maximization hints on unmapped windows, so we
482   // save this one for later too.
483   should_maximize_after_map_ = !window_mapped_in_client_;
484 
485   SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
486                  gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
487 }
488 
Minimize()489 void XWindow::Minimize() {
490   if (window_mapped_in_client_)
491     XIconifyWindow(xdisplay_, xwindow_, 0);
492   else
493     SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None);
494 }
495 
Unmaximize()496 void XWindow::Unmaximize() {
497   should_maximize_after_map_ = false;
498   SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
499                  gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
500 }
501 
Hide()502 bool XWindow::Hide() {
503   if (!window_mapped_in_client_)
504     return false;
505 
506   // Make sure no resize task will run after the window is unmapped.
507   CancelResize();
508 
509   XWithdrawWindow(xdisplay_, xwindow_, 0);
510   window_mapped_in_client_ = false;
511   return true;
512 }
513 
Unhide()514 void XWindow::Unhide() {
515   SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None);
516 }
517 
SetFullscreen(bool fullscreen)518 void XWindow::SetFullscreen(bool fullscreen) {
519   SetWMSpecState(fullscreen, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"),
520                  x11::None);
521 }
522 
Activate()523 void XWindow::Activate() {
524   if (!IsXWindowVisible() || !activatable_)
525     return;
526 
527   BeforeActivationStateChanged();
528 
529   ignore_keyboard_input_ = false;
530 
531   // wmii says that it supports _NET_ACTIVE_WINDOW but does not.
532   // https://code.google.com/p/wmii/issues/detail?id=266
533   static bool wm_supports_active_window =
534       ui::GuessWindowManager() != ui::WM_WMII &&
535       ui::WmSupportsHint(gfx::GetAtom("_NET_ACTIVE_WINDOW"));
536 
537   ::Time timestamp = X11EventSource::GetInstance()->GetTimestamp();
538 
539   // override_redirect windows ignore _NET_ACTIVE_WINDOW.
540   // https://crbug.com/940924
541   if (wm_supports_active_window && !override_redirect_) {
542     XEvent xclient;
543     memset(&xclient, 0, sizeof(xclient));
544     xclient.type = ClientMessage;
545     xclient.xclient.window = xwindow_;
546     xclient.xclient.message_type = gfx::GetAtom("_NET_ACTIVE_WINDOW");
547     xclient.xclient.format = 32;
548     xclient.xclient.data.l[0] = 1;  // Specified we are an app.
549     xclient.xclient.data.l[1] = timestamp;
550     // TODO(thomasanderson): if another chrome window is active, specify that in
551     // data.l[2].  The EWMH spec claims this may make the WM more likely to
552     // service our _NET_ACTIVE_WINDOW request.
553     xclient.xclient.data.l[2] = x11::None;
554     xclient.xclient.data.l[3] = 0;
555     xclient.xclient.data.l[4] = 0;
556 
557     XSendEvent(xdisplay_, x_root_window_, x11::False,
558                SubstructureRedirectMask | SubstructureNotifyMask, &xclient);
559   } else {
560     XRaiseWindow(xdisplay_, xwindow_);
561     // Directly ask the X server to give focus to the window. Note that the call
562     // would have raised an X error if the window is not mapped.
563     auto ignore_errors = [](XDisplay*, XErrorEvent*) -> int { return 0; };
564     auto old_error_handler = XSetErrorHandler(ignore_errors);
565     XSetInputFocus(xdisplay_, xwindow_, RevertToParent, timestamp);
566     // At this point, we know we will receive focus, and some
567     // webdriver tests depend on a window being IsActive() immediately
568     // after an Activate(), so just set this state now.
569     has_pointer_focus_ = false;
570     has_window_focus_ = true;
571     window_mapped_in_server_ = true;
572     XSetErrorHandler(old_error_handler);
573   }
574 
575   AfterActivationStateChanged();
576 }
577 
Deactivate()578 void XWindow::Deactivate() {
579   BeforeActivationStateChanged();
580 
581   // Ignore future input events.
582   ignore_keyboard_input_ = true;
583 
584   XLowerWindow(xdisplay_, xwindow_);
585 
586   AfterActivationStateChanged();
587 }
588 
IsActive() const589 bool XWindow::IsActive() const {
590   // Focus and stacking order are independent in X11.  Since we cannot guarantee
591   // a window is topmost iff it has focus, just use the focus state to determine
592   // if a window is active.  Note that Activate() and Deactivate() change the
593   // stacking order in addition to changing the focus state.
594   return (has_window_focus_ || has_pointer_focus_) && !ignore_keyboard_input_;
595 }
SetSize(const gfx::Size & size_in_pixels)596 void XWindow::SetSize(const gfx::Size& size_in_pixels) {
597   XResizeWindow(xdisplay_, xwindow_, size_in_pixels.width(),
598                 size_in_pixels.height());
599   bounds_in_pixels_.set_size(size_in_pixels);
600 }
601 
SetBounds(const gfx::Rect & requested_bounds_in_pixels)602 void XWindow::SetBounds(const gfx::Rect& requested_bounds_in_pixels) {
603   gfx::Rect bounds_in_pixels(requested_bounds_in_pixels);
604   bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
605   bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
606   XWindowChanges changes = {0};
607   unsigned value_mask = 0;
608 
609   if (size_changed) {
610     // Update the minimum and maximum sizes in case they have changed.
611     UpdateMinAndMaxSize();
612 
613     if (bounds_in_pixels.width() < min_size_in_pixels_.width() ||
614         bounds_in_pixels.height() < min_size_in_pixels_.height() ||
615         (!max_size_in_pixels_.IsEmpty() &&
616          (bounds_in_pixels.width() > max_size_in_pixels_.width() ||
617           bounds_in_pixels.height() > max_size_in_pixels_.height()))) {
618       gfx::Size size_in_pixels = bounds_in_pixels.size();
619       if (!max_size_in_pixels_.IsEmpty())
620         size_in_pixels.SetToMin(max_size_in_pixels_);
621       size_in_pixels.SetToMax(min_size_in_pixels_);
622       bounds_in_pixels.set_size(size_in_pixels);
623     }
624 
625     changes.width = bounds_in_pixels.width();
626     changes.height = bounds_in_pixels.height();
627     value_mask |= CWHeight | CWWidth;
628   }
629 
630   if (origin_changed) {
631     changes.x = bounds_in_pixels.x();
632     changes.y = bounds_in_pixels.y();
633     value_mask |= CWX | CWY;
634   }
635 
636   if (value_mask)
637     XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes);
638 
639   // Assume that the resize will go through as requested, which should be the
640   // case if we're running without a window manager.  If there's a window
641   // manager, it can modify or ignore the request, but (per ICCCM) we'll get a
642   // (possibly synthetic) ConfigureNotify about the actual size and correct
643   // |bounds_in_pixels_| later.
644   bounds_in_pixels_ = bounds_in_pixels;
645   ResetWindowRegion();
646 }
647 
IsXWindowVisible() const648 bool XWindow::IsXWindowVisible() const {
649   // On Windows, IsVisible() returns true for minimized windows.  On X11, a
650   // minimized window is not mapped, so an explicit IsMinimized() check is
651   // necessary.
652   return window_mapped_in_client_ || IsMinimized();
653 }
654 
IsMinimized() const655 bool XWindow::IsMinimized() const {
656   return ui::HasWMSpecProperty(window_properties_,
657                                gfx::GetAtom("_NET_WM_STATE_HIDDEN"));
658 }
659 
IsMaximized() const660 bool XWindow::IsMaximized() const {
661   return (ui::HasWMSpecProperty(window_properties_,
662                                 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) &&
663           ui::HasWMSpecProperty(window_properties_,
664                                 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")));
665 }
666 
IsFullscreen() const667 bool XWindow::IsFullscreen() const {
668   return ui::HasWMSpecProperty(window_properties_,
669                                gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"));
670 }
671 
GetOutterBounds() const672 gfx::Rect XWindow::GetOutterBounds() const {
673   gfx::Rect outer_bounds(bounds_in_pixels_);
674   outer_bounds.Inset(-native_window_frame_borders_in_pixels_);
675   return outer_bounds;
676 }
677 
GrabPointer()678 void XWindow::GrabPointer() {
679   // If the pointer is already in |xwindow_|, we will not get a crossing event
680   // with a mode of NotifyGrab, so we must record the grab state manually.
681   has_pointer_grab_ |= !ui::GrabPointer(xwindow_, true, x11::None);
682 }
683 
ReleasePointerGrab()684 void XWindow::ReleasePointerGrab() {
685   ui::UngrabPointer();
686   has_pointer_grab_ = false;
687 }
688 
StackXWindowAbove(::Window window)689 void XWindow::StackXWindowAbove(::Window window) {
690   DCHECK(window != x11::None);
691 
692   // Find all parent windows up to the root.
693   std::vector<::Window> window_below_parents =
694       GetParentsList(xdisplay_, window);
695   std::vector<::Window> window_above_parents =
696       GetParentsList(xdisplay_, xwindow_);
697 
698   // Find their common ancestor.
699   auto it_below_window = window_below_parents.rbegin();
700   auto it_above_window = window_above_parents.rbegin();
701   for (; it_below_window != window_below_parents.rend() &&
702          it_above_window != window_above_parents.rend() &&
703          *it_below_window == *it_above_window;
704        ++it_below_window, ++it_above_window) {
705   }
706 
707   if (it_below_window != window_below_parents.rend() &&
708       it_above_window != window_above_parents.rend()) {
709     // First stack |xwindow| below so Z-order of |window| stays the same.
710     ::Window windows[] = {*it_below_window, *it_above_window};
711     if (XRestackWindows(xdisplay_, windows, 2) == 0) {
712       // Now stack them properly.
713       std::swap(windows[0], windows[1]);
714       XRestackWindows(xdisplay_, windows, 2);
715     }
716   }
717 }
718 
StackXWindowAtTop()719 void XWindow::StackXWindowAtTop() {
720   XRaiseWindow(xdisplay_, xwindow_);
721 }
722 
SetCursor(::Cursor cursor)723 void XWindow::SetCursor(::Cursor cursor) {
724   XDefineCursor(xdisplay_, xwindow_, cursor);
725 }
726 
SetTitle(base::string16 title)727 bool XWindow::SetTitle(base::string16 title) {
728   if (window_title_ == title)
729     return false;
730 
731   window_title_ = title;
732   std::string utf8str = base::UTF16ToUTF8(title);
733   XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_NAME"),
734                   gfx::GetAtom("UTF8_STRING"), 8, PropModeReplace,
735                   reinterpret_cast<const unsigned char*>(utf8str.c_str()),
736                   utf8str.size());
737   XTextProperty xtp;
738   char* c_utf8_str = const_cast<char*>(utf8str.c_str());
739   if (Xutf8TextListToTextProperty(xdisplay_, &c_utf8_str, 1, XUTF8StringStyle,
740                                   &xtp) == x11::Success) {
741     XSetWMName(xdisplay_, xwindow_, &xtp);
742     XFree(xtp.value);
743   }
744   return true;
745 }
746 
SetXWindowOpacity(float opacity)747 void XWindow::SetXWindowOpacity(float opacity) {
748   // X server opacity is in terms of 32 bit unsigned int space, and counts from
749   // the opposite direction.
750   // XChangeProperty() expects "cardinality" to be long.
751 
752   // Scale opacity to [0 .. 255] range.
753   unsigned long opacity_8bit =
754       static_cast<unsigned long>(opacity * 255.0f) & 0xFF;
755   // Use opacity value for all channels.
756   const unsigned long channel_multiplier = 0x1010101;
757   unsigned long cardinality = opacity_8bit * channel_multiplier;
758 
759   if (cardinality == 0xffffffff) {
760     XDeleteProperty(xdisplay_, xwindow_,
761                     gfx::GetAtom("_NET_WM_WINDOW_OPACITY"));
762   } else {
763     XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY"),
764                     XA_CARDINAL, 32, PropModeReplace,
765                     reinterpret_cast<unsigned char*>(&cardinality), 1);
766   }
767 }
768 
SetXWindowAspectRatio(const gfx::SizeF & aspect_ratio)769 void XWindow::SetXWindowAspectRatio(const gfx::SizeF& aspect_ratio) {
770   XSizeHints size_hints;
771   size_hints.flags = 0;
772   long supplied_return;
773 
774   XGetWMNormalHints(xdisplay_, xwindow_, &size_hints, &supplied_return);
775   // Unforce aspect ratio is parameter length is 0, otherwise set normally.
776   if (!aspect_ratio.IsEmpty()) {
777     size_hints.flags |= PAspect;
778     size_hints.min_aspect.x = size_hints.max_aspect.x = aspect_ratio.width();
779     size_hints.min_aspect.y = size_hints.max_aspect.y = aspect_ratio.height();
780   }
781   XSetWMNormalHints(xdisplay_, xwindow_, &size_hints);
782 }
783 
SetXWindowIcons(const gfx::ImageSkia & window_icon,const gfx::ImageSkia & app_icon)784 void XWindow::SetXWindowIcons(const gfx::ImageSkia& window_icon,
785                               const gfx::ImageSkia& app_icon) {
786   // TODO(erg): The way we handle icons across different versions of chrome
787   // could be substantially improved. The Windows version does its own thing
788   // and only sometimes comes down this code path. The icon stuff in
789   // ChromeViewsDelegate is hard coded to use HICONs. Likewise, we're hard
790   // coded to be given two images instead of an arbitrary collection of images
791   // so that we can pass to the WM.
792   //
793   // All of this could be made much, much better.
794   std::vector<unsigned long> data;
795 
796   if (!window_icon.isNull())
797     SerializeImageRepresentation(window_icon.GetRepresentation(1.0f), &data);
798 
799   if (!app_icon.isNull())
800     SerializeImageRepresentation(app_icon.GetRepresentation(1.0f), &data);
801 
802   if (!data.empty())
803     ui::SetAtomArrayProperty(xwindow_, "_NET_WM_ICON", "CARDINAL", data);
804 }
805 
SetXWindowVisibleOnAllWorkspaces(bool visible)806 void XWindow::SetXWindowVisibleOnAllWorkspaces(bool visible) {
807   SetWMSpecState(visible, gfx::GetAtom("_NET_WM_STATE_STICKY"), x11::None);
808 
809   int new_desktop = 0;
810   if (visible) {
811     new_desktop = kAllWorkspaces;
812   } else {
813     if (!ui::GetCurrentDesktop(&new_desktop))
814       return;
815   }
816 
817   workspace_ = kAllWorkspaces;
818   XEvent xevent;
819   memset(&xevent, 0, sizeof(xevent));
820   xevent.type = ClientMessage;
821   xevent.xclient.window = xwindow_;
822   xevent.xclient.message_type = gfx::GetAtom("_NET_WM_DESKTOP");
823   xevent.xclient.format = 32;
824   xevent.xclient.data.l[0] = new_desktop;
825   xevent.xclient.data.l[1] = 0;
826   xevent.xclient.data.l[2] = 0;
827   xevent.xclient.data.l[3] = 0;
828   xevent.xclient.data.l[4] = 0;
829   XSendEvent(xdisplay_, x_root_window_, x11::False,
830              SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
831 }
832 
IsXWindowVisibleOnAllWorkspaces() const833 bool XWindow::IsXWindowVisibleOnAllWorkspaces() const {
834   // We don't need a check for _NET_WM_STATE_STICKY because that would specify
835   // that the window remain in a fixed position even if the viewport scrolls.
836   // This is different from the type of workspace that's associated with
837   // _NET_WM_DESKTOP.
838   return workspace_ == kAllWorkspaces;
839 }
840 
MoveCursorTo(const gfx::Point & location_in_pixels)841 void XWindow::MoveCursorTo(const gfx::Point& location_in_pixels) {
842   XWarpPointer(xdisplay_, x11::None, x_root_window_, 0, 0, 0, 0,
843                bounds_in_pixels_.x() + location_in_pixels.x(),
844                bounds_in_pixels_.y() + location_in_pixels.y());
845 }
846 
ResetWindowRegion()847 void XWindow::ResetWindowRegion() {
848   XRegion* xregion = nullptr;
849   if (!use_custom_shape() && !IsMaximized() && !IsFullscreen()) {
850     SkPath window_mask;
851     GetWindowMaskForXWindow(bounds().size(), &window_mask);
852     // Some frame views define a custom (non-rectangular) window mask. If
853     // so, use it to define the window shape. If not, fall through.
854     if (window_mask.countPoints() > 0)
855       xregion = gfx::CreateRegionFromSkPath(window_mask);
856   }
857   UpdateWindowRegion(xregion);
858 }
859 
OnWorkspaceUpdated()860 void XWindow::OnWorkspaceUpdated() {
861   auto old_workspace = workspace_;
862   int workspace;
863   if (ui::GetWindowDesktop(xwindow_, &workspace))
864     workspace_ = workspace;
865   else
866     workspace_ = base::nullopt;
867 
868   if (workspace_ != old_workspace)
869     OnXWindowWorkspaceChanged();
870 }
871 
SetAlwaysOnTop(bool always_on_top)872 void XWindow::SetAlwaysOnTop(bool always_on_top) {
873   is_always_on_top_ = always_on_top;
874   SetWMSpecState(always_on_top, gfx::GetAtom("_NET_WM_STATE_ABOVE"), x11::None);
875 }
876 
SetFlashFrameHint(bool flash_frame)877 void XWindow::SetFlashFrameHint(bool flash_frame) {
878   if (urgency_hint_set_ == flash_frame)
879     return;
880 
881   gfx::XScopedPtr<XWMHints> hints(XGetWMHints(xdisplay_, xwindow_));
882   if (!hints) {
883     // The window hasn't had its hints set yet.
884     hints.reset(XAllocWMHints());
885   }
886 
887   if (flash_frame)
888     hints->flags |= XUrgencyHint;
889   else
890     hints->flags &= ~XUrgencyHint;
891 
892   XSetWMHints(xdisplay_, xwindow_, hints.get());
893 
894   urgency_hint_set_ = flash_frame;
895 }
896 
UpdateMinAndMaxSize()897 void XWindow::UpdateMinAndMaxSize() {
898   base::Optional<gfx::Size> minimum_in_pixels = GetMinimumSizeForXWindow();
899   base::Optional<gfx::Size> maximum_in_pixels = GetMaximumSizeForXWindow();
900   if ((!minimum_in_pixels ||
901        min_size_in_pixels_ == minimum_in_pixels.value()) &&
902       (!maximum_in_pixels || max_size_in_pixels_ == maximum_in_pixels.value()))
903     return;
904 
905   min_size_in_pixels_ = minimum_in_pixels.value();
906   max_size_in_pixels_ = maximum_in_pixels.value();
907 
908   XSizeHints hints;
909   hints.flags = 0;
910   long supplied_return;
911   XGetWMNormalHints(xdisplay_, xwindow_, &hints, &supplied_return);
912 
913   if (min_size_in_pixels_.IsEmpty()) {
914     hints.flags &= ~PMinSize;
915   } else {
916     hints.flags |= PMinSize;
917     hints.min_width = min_size_in_pixels_.width();
918     hints.min_height = min_size_in_pixels_.height();
919   }
920 
921   if (max_size_in_pixels_.IsEmpty()) {
922     hints.flags &= ~PMaxSize;
923   } else {
924     hints.flags |= PMaxSize;
925     hints.max_width = max_size_in_pixels_.width();
926     hints.max_height = max_size_in_pixels_.height();
927   }
928 
929   XSetWMNormalHints(xdisplay_, xwindow_, &hints);
930 }
931 
BeforeActivationStateChanged()932 void XWindow::BeforeActivationStateChanged() {
933   was_active_ = IsActive();
934   had_pointer_ = has_pointer_;
935   had_pointer_grab_ = has_pointer_grab_;
936   had_window_focus_ = has_window_focus_;
937 }
938 
AfterActivationStateChanged()939 void XWindow::AfterActivationStateChanged() {
940   if (had_pointer_grab_ && !has_pointer_grab_)
941     OnXWindowLostPointerGrab();
942 
943   bool had_pointer_capture = had_pointer_ || had_pointer_grab_;
944   bool has_pointer_capture = has_pointer_ || has_pointer_grab_;
945   if (had_pointer_capture && !has_pointer_capture)
946     OnXWindowLostCapture();
947 
948   bool is_active = IsActive();
949   if (!was_active_ && is_active)
950     SetFlashFrameHint(false);
951 
952   if (was_active_ != is_active)
953     OnXWindowIsActiveChanged(is_active);
954 }
955 
SetUseNativeFrame(bool use_native_frame)956 void XWindow::SetUseNativeFrame(bool use_native_frame) {
957   use_native_frame_ = use_native_frame;
958   ui::SetUseOSWindowFrame(xwindow_, use_native_frame);
959   ResetWindowRegion();
960 }
961 
OnCrossingEvent(bool enter,bool focus_in_window_or_ancestor,int mode,int detail)962 void XWindow::OnCrossingEvent(bool enter,
963                               bool focus_in_window_or_ancestor,
964                               int mode,
965                               int detail) {
966   // NotifyInferior on a crossing event means the pointer moved into or out of a
967   // child window, but the pointer is still within |xwindow_|.
968   if (detail == NotifyInferior)
969     return;
970 
971   BeforeActivationStateChanged();
972 
973   if (mode == NotifyGrab)
974     has_pointer_grab_ = enter;
975   else if (mode == NotifyUngrab)
976     has_pointer_grab_ = false;
977 
978   has_pointer_ = enter;
979   if (focus_in_window_or_ancestor && !has_window_focus_) {
980     // If we reach this point, we know the focus is in an ancestor or the
981     // pointer root.  The definition of |has_pointer_focus_| is (An ancestor
982     // window or the PointerRoot is focused) && |has_pointer_|.  Therefore, we
983     // can just use |has_pointer_| in the assignment.  The transitions for when
984     // the focus changes are handled in OnFocusEvent().
985     has_pointer_focus_ = has_pointer_;
986   }
987 
988   AfterActivationStateChanged();
989 }
990 
OnFocusEvent(bool focus_in,int mode,int detail)991 void XWindow::OnFocusEvent(bool focus_in, int mode, int detail) {
992   // NotifyInferior on a focus event means the focus moved into or out of a
993   // child window, but the focus is still within |xwindow_|.
994   if (detail == NotifyInferior)
995     return;
996 
997   bool notify_grab = mode == NotifyGrab || mode == NotifyUngrab;
998 
999   BeforeActivationStateChanged();
1000 
1001   // For every focus change, the X server sends normal focus events which are
1002   // useful for tracking |has_window_focus_|, but supplements these events with
1003   // NotifyPointer events which are only useful for tracking pointer focus.
1004 
1005   // For |has_pointer_focus_| and |has_window_focus_|, we continue tracking
1006   // state during a grab, but ignore grab/ungrab events themselves.
1007   if (!notify_grab && detail != NotifyPointer)
1008     has_window_focus_ = focus_in;
1009 
1010   if (!notify_grab && has_pointer_) {
1011     switch (detail) {
1012       case NotifyAncestor:
1013       case NotifyVirtual:
1014         // If we reach this point, we know |has_pointer_| was true before and
1015         // after this event.  Since the definition of |has_pointer_focus_| is
1016         // (An ancestor window or the PointerRoot is focused) && |has_pointer_|,
1017         // we only need to worry about transitions on the first conjunct.
1018         // Therefore, |has_pointer_focus_| will become true when:
1019         // 1. Focus moves from |xwindow_| to an ancestor
1020         //    (FocusOut with NotifyAncestor)
1021         // 2. Focus moves from a descendant of |xwindow_| to an ancestor
1022         //    (FocusOut with NotifyVirtual)
1023         // |has_pointer_focus_| will become false when:
1024         // 1. Focus moves from an ancestor to |xwindow_|
1025         //    (FocusIn with NotifyAncestor)
1026         // 2. Focus moves from an ancestor to a child of |xwindow_|
1027         //    (FocusIn with NotifyVirtual)
1028         has_pointer_focus_ = !focus_in;
1029         break;
1030       case NotifyPointer:
1031         // The remaining cases for |has_pointer_focus_| becoming true are:
1032         // 3. Focus moves from |xwindow_| to the PointerRoot
1033         // 4. Focus moves from a descendant of |xwindow_| to the PointerRoot
1034         // 5. Focus moves from None to the PointerRoot
1035         // 6. Focus moves from Other to the PointerRoot
1036         // 7. Focus moves from None to an ancestor of |xwindow_|
1037         // 8. Focus moves from Other to an ancestor of |xwindow_|
1038         // In each case, we will get a FocusIn with a detail of NotifyPointer.
1039         // The remaining cases for |has_pointer_focus_| becoming false are:
1040         // 3. Focus moves from the PointerRoot to |xwindow_|
1041         // 4. Focus moves from the PointerRoot to a descendant of |xwindow|
1042         // 5. Focus moves from the PointerRoot to None
1043         // 6. Focus moves from an ancestor of |xwindow_| to None
1044         // 7. Focus moves from the PointerRoot to Other
1045         // 8. Focus moves from an ancestor of |xwindow_| to Other
1046         // In each case, we will get a FocusOut with a detail of NotifyPointer.
1047         has_pointer_focus_ = focus_in;
1048         break;
1049       case NotifyNonlinear:
1050       case NotifyNonlinearVirtual:
1051         // We get Nonlinear(Virtual) events when
1052         // 1. Focus moves from Other to |xwindow_|
1053         //    (FocusIn with NotifyNonlinear)
1054         // 2. Focus moves from Other to a descendant of |xwindow_|
1055         //    (FocusIn with NotifyNonlinearVirtual)
1056         // 3. Focus moves from |xwindow_| to Other
1057         //    (FocusOut with NotifyNonlinear)
1058         // 4. Focus moves from a descendant of |xwindow_| to Other
1059         //    (FocusOut with NotifyNonlinearVirtual)
1060         // |has_pointer_focus_| should be false before and after this event.
1061         has_pointer_focus_ = false;
1062         break;
1063       default:
1064         break;
1065     }
1066   }
1067 
1068   ignore_keyboard_input_ = false;
1069 
1070   AfterActivationStateChanged();
1071 }
1072 
IsTargetedBy(const XEvent & xev) const1073 bool XWindow::IsTargetedBy(const XEvent& xev) const {
1074   ::Window target_window =
1075       (xev.type == GenericEvent)
1076           ? static_cast<XIDeviceEvent*>(xev.xcookie.data)->event
1077           : xev.xany.window;
1078   return target_window == xwindow_;
1079 }
1080 
WmMoveResize(int hittest,const gfx::Point & location) const1081 void XWindow::WmMoveResize(int hittest, const gfx::Point& location) const {
1082   int direction = HitTestToWmMoveResizeDirection(hittest);
1083   if (direction == -1)
1084     return;
1085 
1086   DoWMMoveResize(xdisplay_, x_root_window_, xwindow_, location, direction);
1087 }
1088 
1089 // In Ozone, there are no ui::*Event constructors receiving XEvent* as input,
1090 // in this case ui::PlatformEvent is expected. Furthermore,
1091 // X11EventSourceLibevent is used in that case, which already translates
1092 // Mouse/Key/Touch/Scroll events into ui::Events so they should not be handled
1093 // by PlatformWindow, which is supposed to use XWindow in Ozone builds. So
1094 // handling these events is disabled for Ozone.
ProcessEvent(XEvent * xev)1095 void XWindow::ProcessEvent(XEvent* xev) {
1096   // We can lose track of the window's position when the window is reparented.
1097   // When the parent window is moved, we won't get an event, so the window's
1098   // position relative to the root window will get out-of-sync.  We can re-sync
1099   // when getting pointer events (EnterNotify, LeaveNotify, ButtonPress,
1100   // ButtonRelease, MotionNotify) which include the pointer location both
1101   // relative to this window and relative to the root window, so we can
1102   // calculate this window's position from that information.
1103   gfx::Point window_point = ui::EventLocationFromXEvent(*xev);
1104   gfx::Point root_point = ui::EventSystemLocationFromXEvent(*xev);
1105   if (!window_point.IsOrigin() && !root_point.IsOrigin()) {
1106     gfx::Point window_origin = gfx::Point() + (root_point - window_point);
1107     if (bounds_in_pixels_.origin() != window_origin) {
1108       bounds_in_pixels_.set_origin(window_origin);
1109       NotifyBoundsChanged(bounds_in_pixels_);
1110     }
1111   }
1112 
1113   // May want to factor CheckXEventForConsistency(xev); into a common location
1114   // since it is called here.
1115   switch (xev->type) {
1116     case EnterNotify:
1117     case LeaveNotify: {
1118       OnCrossingEvent(xev->type == EnterNotify, xev->xcrossing.focus,
1119                       xev->xcrossing.mode, xev->xcrossing.detail);
1120       break;
1121     }
1122     case Expose: {
1123       gfx::Rect damage_rect_in_pixels(xev->xexpose.x, xev->xexpose.y,
1124                                       xev->xexpose.width, xev->xexpose.height);
1125       OnXWindowDamageEvent(damage_rect_in_pixels);
1126       break;
1127     }
1128     case x11::FocusIn:
1129     case x11::FocusOut:
1130       OnFocusEvent(xev->type == x11::FocusIn, xev->xfocus.mode,
1131                    xev->xfocus.detail);
1132       break;
1133     case ConfigureNotify:
1134       OnConfigureEvent(xev);
1135       break;
1136     case GenericEvent: {
1137       ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
1138       if (!factory->ShouldProcessXI2Event(xev))
1139         break;
1140 
1141       XIEnterEvent* enter_event = static_cast<XIEnterEvent*>(xev->xcookie.data);
1142       switch (static_cast<XIEvent*>(xev->xcookie.data)->evtype) {
1143         case XI_Enter:
1144         case XI_Leave: {
1145           OnCrossingEvent(enter_event->evtype == XI_Enter, enter_event->focus,
1146                           XI2ModeToXMode(enter_event->mode),
1147                           enter_event->detail);
1148           return;
1149         }
1150         case XI_FocusIn:
1151         case XI_FocusOut: {
1152           OnFocusEvent(enter_event->evtype == XI_FocusIn,
1153                        XI2ModeToXMode(enter_event->mode), enter_event->detail);
1154           return;
1155         }
1156         default:
1157           break;
1158       }
1159       break;
1160     }
1161     case MapNotify: {
1162       OnWindowMapped();
1163       break;
1164     }
1165     case UnmapNotify: {
1166       window_mapped_in_server_ = false;
1167       has_pointer_ = false;
1168       has_pointer_grab_ = false;
1169       has_pointer_focus_ = false;
1170       has_window_focus_ = false;
1171       break;
1172     }
1173     case ClientMessage: {
1174       Atom message_type = xev->xclient.message_type;
1175       if (message_type == gfx::GetAtom("WM_PROTOCOLS")) {
1176         Atom protocol = static_cast<Atom>(xev->xclient.data.l[0]);
1177         if (protocol == gfx::GetAtom("WM_DELETE_WINDOW")) {
1178           // We have received a close message from the window manager.
1179           OnXWindowCloseRequested();
1180         } else if (protocol == gfx::GetAtom("_NET_WM_PING")) {
1181           XEvent reply_event = *xev;
1182           reply_event.xclient.window = x_root_window_;
1183 
1184           XSendEvent(xdisplay_, reply_event.xclient.window, x11::False,
1185                      SubstructureRedirectMask | SubstructureNotifyMask,
1186                      &reply_event);
1187         } else if (protocol == gfx::GetAtom("_NET_WM_SYNC_REQUEST")) {
1188           pending_counter_value_ =
1189               xev->xclient.data.l[2] +
1190               (static_cast<int64_t>(xev->xclient.data.l[3]) << 32);
1191           pending_counter_value_is_extended_ = xev->xclient.data.l[4] != 0;
1192         }
1193       } else {
1194         OnXWindowDragDropEvent(xev);
1195       }
1196       break;
1197     }
1198     case MappingNotify: {
1199       switch (xev->xmapping.request) {
1200         case MappingModifier:
1201         case MappingKeyboard:
1202           XRefreshKeyboardMapping(&xev->xmapping);
1203           break;
1204         case MappingPointer:
1205           ui::DeviceDataManagerX11::GetInstance()->UpdateButtonMap();
1206           break;
1207         default:
1208           NOTIMPLEMENTED() << " Unknown request: " << xev->xmapping.request;
1209           break;
1210       }
1211       break;
1212     }
1213     case PropertyNotify: {
1214       XAtom changed_atom = xev->xproperty.atom;
1215       if (changed_atom == gfx::GetAtom("_NET_WM_STATE")) {
1216         OnWMStateUpdated();
1217       } else if (changed_atom == gfx::GetAtom("_NET_FRAME_EXTENTS")) {
1218         OnFrameExtentsUpdated();
1219       } else if (changed_atom == gfx::GetAtom("_NET_WM_DESKTOP")) {
1220         OnWorkspaceUpdated();
1221       }
1222       break;
1223     }
1224     case SelectionNotify: {
1225       OnXWindowSelectionEvent(xev);
1226       break;
1227     }
1228   }
1229 }
1230 
UpdateWMUserTime(ui::Event * event)1231 void XWindow::UpdateWMUserTime(ui::Event* event) {
1232   if (!IsActive())
1233     return;
1234   DCHECK(event);
1235   ui::EventType type = event->type();
1236   if (type == ui::ET_MOUSE_PRESSED || type == ui::ET_KEY_PRESSED ||
1237       type == ui::ET_TOUCH_PRESSED) {
1238     unsigned long wm_user_time_ms =
1239         (event->time_stamp() - base::TimeTicks()).InMilliseconds();
1240     XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"),
1241                     XA_CARDINAL, 32, PropModeReplace,
1242                     reinterpret_cast<const unsigned char*>(&wm_user_time_ms),
1243                     1);
1244   }
1245 }
1246 
OnWindowMapped()1247 void XWindow::OnWindowMapped() {
1248   window_mapped_in_server_ = true;
1249   // Some WMs only respect maximize hints after the window has been mapped.
1250   // Check whether we need to re-do a maximization.
1251   if (should_maximize_after_map_) {
1252     Maximize();
1253     should_maximize_after_map_ = false;
1254   }
1255 }
1256 
OnConfigureEvent(XEvent * xev)1257 void XWindow::OnConfigureEvent(XEvent* xev) {
1258   DCHECK_EQ(xwindow_, xev->xconfigure.window);
1259   DCHECK_EQ(xwindow_, xev->xconfigure.event);
1260 
1261   if (pending_counter_value_) {
1262     DCHECK(!configure_counter_value_);
1263     configure_counter_value_ = pending_counter_value_;
1264     configure_counter_value_is_extended_ = pending_counter_value_is_extended_;
1265     pending_counter_value_is_extended_ = 0;
1266     pending_counter_value_ = 0;
1267   }
1268 
1269   // It's possible that the X window may be resized by some other means than
1270   // from within aura (e.g. the X window manager can change the size). Make
1271   // sure the root window size is maintained properly.
1272   int translated_x_in_pixels = xev->xconfigure.x;
1273   int translated_y_in_pixels = xev->xconfigure.y;
1274   if (!xev->xconfigure.send_event && !xev->xconfigure.override_redirect) {
1275     Window unused;
1276     XTranslateCoordinates(xdisplay_, xwindow_, x_root_window_, 0, 0,
1277                           &translated_x_in_pixels, &translated_y_in_pixels,
1278                           &unused);
1279   }
1280   gfx::Rect bounds_in_pixels(translated_x_in_pixels, translated_y_in_pixels,
1281                              xev->xconfigure.width, xev->xconfigure.height);
1282   bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
1283   bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
1284   previous_bounds_in_pixels_ = bounds_in_pixels_;
1285   bounds_in_pixels_ = bounds_in_pixels;
1286 
1287   if (size_changed)
1288     DispatchResize();
1289   else if (origin_changed)
1290     NotifyBoundsChanged(bounds_in_pixels_);
1291 }
1292 
SetWMSpecState(bool enabled,XAtom state1,XAtom state2)1293 void XWindow::SetWMSpecState(bool enabled, XAtom state1, XAtom state2) {
1294   if (window_mapped_in_client_) {
1295     ui::SetWMSpecState(xwindow_, enabled, state1, state2);
1296   } else {
1297     // The updated state will be set when the window is (re)mapped.
1298     base::flat_set<XAtom> new_window_properties = window_properties_;
1299     for (XAtom atom : {state1, state2}) {
1300       if (enabled)
1301         new_window_properties.insert(atom);
1302       else
1303         new_window_properties.erase(atom);
1304     }
1305     UpdateWindowProperties(new_window_properties);
1306   }
1307 }
1308 
OnWMStateUpdated()1309 void XWindow::OnWMStateUpdated() {
1310   // The EWMH spec requires window managers to remove the _NET_WM_STATE property
1311   // when a window is unmapped.  However, Chromium code wants the state to
1312   // persist across a Hide() and Show().  So if the window is currently
1313   // unmapped, leave the state unchanged so it will be restored when the window
1314   // is remapped.
1315   std::vector<XAtom> atom_list;
1316   if (ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list) ||
1317       window_mapped_in_client_) {
1318     UpdateWindowProperties(
1319         base::flat_set<XAtom>(std::begin(atom_list), std::end(atom_list)));
1320   }
1321 }
1322 
UpdateWindowProperties(const base::flat_set<XAtom> & new_window_properties)1323 void XWindow::UpdateWindowProperties(
1324     const base::flat_set<XAtom>& new_window_properties) {
1325   was_minimized_ = IsMinimized();
1326 
1327   window_properties_ = new_window_properties;
1328 
1329   // Ignore requests by the window manager to enter or exit fullscreen (e.g. as
1330   // a result of pressing a window manager accelerator key). Chrome does not
1331   // handle window manager initiated fullscreen. In particular, Chrome needs to
1332   // do preprocessing before the x window's fullscreen state is toggled.
1333 
1334   is_always_on_top_ = ui::HasWMSpecProperty(
1335       window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE"));
1336   OnXWindowStateChanged();
1337   ResetWindowRegion();
1338 }
1339 
OnFrameExtentsUpdated()1340 void XWindow::OnFrameExtentsUpdated() {
1341   std::vector<int> insets;
1342   if (ui::GetIntArrayProperty(xwindow_, "_NET_FRAME_EXTENTS", &insets) &&
1343       insets.size() == 4) {
1344     // |insets| are returned in the order: [left, right, top, bottom].
1345     native_window_frame_borders_in_pixels_ =
1346         gfx::Insets(insets[2], insets[0], insets[3], insets[1]);
1347   } else {
1348     native_window_frame_borders_in_pixels_ = gfx::Insets();
1349   }
1350 }
1351 
NotifySwapAfterResize()1352 void XWindow::NotifySwapAfterResize() {
1353   if (configure_counter_value_is_extended_) {
1354     if ((current_counter_value_ % 2) == 1) {
1355       // An increase 3 means that the frame was not drawn as fast as possible.
1356       // This can trigger different handling from the compositor.
1357       // Setting an even number to |extended_update_counter_| will trigger a
1358       // new resize.
1359       current_counter_value_ += 3;
1360       SyncSetCounter(xdisplay_, extended_update_counter_,
1361                      current_counter_value_);
1362     }
1363     return;
1364   }
1365 
1366   if (configure_counter_value_ != 0) {
1367     SyncSetCounter(xdisplay_, update_counter_, configure_counter_value_);
1368     configure_counter_value_ = 0;
1369   }
1370 }
1371 
1372 // Removes |delayed_resize_task_| from the task queue (if it's in
1373 // the queue) and adds it back at the end of the queue.
DispatchResize()1374 void XWindow::DispatchResize() {
1375   if (update_counter_ == x11::None || configure_counter_value_ == 0) {
1376     // WM doesn't support _NET_WM_SYNC_REQUEST.
1377     // Or we are too slow, so _NET_WM_SYNC_REQUEST is disabled by the
1378     // compositor.
1379     delayed_resize_task_.Reset(base::BindOnce(
1380         &XWindow::DelayedResize, base::Unretained(this), bounds_in_pixels_));
1381     base::ThreadTaskRunnerHandle::Get()->PostTask(
1382         FROM_HERE, delayed_resize_task_.callback());
1383     return;
1384   }
1385 
1386   if (configure_counter_value_is_extended_) {
1387     current_counter_value_ = configure_counter_value_;
1388     configure_counter_value_ = 0;
1389     // Make sure the counter is even number.
1390     if ((current_counter_value_ % 2) == 1)
1391       ++current_counter_value_;
1392   }
1393 
1394   // If _NET_WM_SYNC_REQUEST is used to synchronize with compositor during
1395   // resizing, the compositor will not resize the window, until last resize is
1396   // handled, so we don't need accumulate resize events.
1397   DelayedResize(bounds_in_pixels_);
1398 }
1399 
DelayedResize(const gfx::Rect & bounds_in_pixels)1400 void XWindow::DelayedResize(const gfx::Rect& bounds_in_pixels) {
1401   base::WeakPtr<XWindow> alive(resize_weak_factory_.GetWeakPtr());
1402 
1403   if (configure_counter_value_is_extended_ &&
1404       (current_counter_value_ % 2) == 0) {
1405     // Increase the |extended_update_counter_|, so the compositor will know we
1406     // are not frozen and re-enable _NET_WM_SYNC_REQUEST, if it was disabled.
1407     // Increase the |extended_update_counter_| to an odd number will not trigger
1408     // a new resize.
1409     SyncSetCounter(xdisplay_, extended_update_counter_,
1410                    ++current_counter_value_);
1411   }
1412   NotifyBoundsChanged(bounds_in_pixels);
1413 
1414   // Bounds change propagation above may spin a window move loop, which might
1415   // end up closing and destroying this instance (e.g: when a chrome window is
1416   // snapped into a tab strip). So we must handle this possible scenario before
1417   // trying to access any class variable/function. See crbug.com/1068755.
1418   if (!alive)
1419     return;
1420 
1421   CancelResize();
1422 }
1423 
CancelResize()1424 void XWindow::CancelResize() {
1425   delayed_resize_task_.Cancel();
1426 }
1427 
ConfineCursorTo(const gfx::Rect & bounds)1428 void XWindow::ConfineCursorTo(const gfx::Rect& bounds) {
1429   UnconfineCursor();
1430 
1431   if (bounds.IsEmpty())
1432     return;
1433 
1434   gfx::Rect barrier = bounds + bounds_in_pixels_.OffsetFromOrigin();
1435 
1436   // Top horizontal barrier.
1437   pointer_barriers_[0] = XFixesCreatePointerBarrier(
1438       xdisplay_, x_root_window_, barrier.x(), barrier.y(), barrier.right(),
1439       barrier.y(), BarrierPositiveY, 0, XIAllDevices);
1440   // Bottom horizontal barrier.
1441   pointer_barriers_[1] = XFixesCreatePointerBarrier(
1442       xdisplay_, x_root_window_, barrier.x(), barrier.bottom(), barrier.right(),
1443       barrier.bottom(), BarrierNegativeY, 0, XIAllDevices);
1444   // Left vertical barrier.
1445   pointer_barriers_[2] = XFixesCreatePointerBarrier(
1446       xdisplay_, x_root_window_, barrier.x(), barrier.y(), barrier.x(),
1447       barrier.bottom(), BarrierPositiveX, 0, XIAllDevices);
1448   // Right vertical barrier.
1449   pointer_barriers_[3] = XFixesCreatePointerBarrier(
1450       xdisplay_, x_root_window_, barrier.right(), barrier.y(), barrier.right(),
1451       barrier.bottom(), BarrierNegativeX, 0, XIAllDevices);
1452 
1453   has_pointer_barriers_ = true;
1454 }
1455 
LowerWindow()1456 void XWindow::LowerWindow() {
1457   XLowerWindow(xdisplay_, xwindow_);
1458 }
1459 
SetOverrideRedirect(bool override_redirect)1460 void XWindow::SetOverrideRedirect(bool override_redirect) {
1461   bool remap = window_mapped_in_client_;
1462   if (remap)
1463     Hide();
1464   XSetWindowAttributes swa;
1465   swa.override_redirect = override_redirect;
1466   XChangeWindowAttributes(xdisplay_, xwindow_, CWOverrideRedirect, &swa);
1467   if (remap) {
1468     Map();
1469     if (has_pointer_grab_)
1470       ui::ChangeActivePointerGrabCursor(x11::None);
1471   }
1472 }
1473 
ContainsPointInRegion(const gfx::Point & point) const1474 bool XWindow::ContainsPointInRegion(const gfx::Point& point) const {
1475   if (!shape())
1476     return true;
1477 
1478   return XPointInRegion(shape(), point.x(), point.y()) == x11::True;
1479 }
1480 
SetXWindowShape(std::unique_ptr<NativeShapeRects> native_shape,const gfx::Transform & transform)1481 void XWindow::SetXWindowShape(std::unique_ptr<NativeShapeRects> native_shape,
1482                               const gfx::Transform& transform) {
1483   XRegion* xregion = nullptr;
1484   if (native_shape) {
1485     SkRegion native_region;
1486     for (const gfx::Rect& rect : *native_shape)
1487       native_region.op(gfx::RectToSkIRect(rect), SkRegion::kUnion_Op);
1488     if (!transform.IsIdentity() && !native_region.isEmpty()) {
1489       SkPath path_in_dip;
1490       if (native_region.getBoundaryPath(&path_in_dip)) {
1491         SkPath path_in_pixels;
1492         path_in_dip.transform(SkMatrix(transform.matrix()), &path_in_pixels);
1493         xregion = gfx::CreateRegionFromSkPath(path_in_pixels);
1494       } else {
1495         xregion = XCreateRegion();
1496       }
1497     } else {
1498       xregion = gfx::CreateRegionFromSkRegion(native_region);
1499     }
1500   }
1501 
1502   custom_window_shape_ = !!xregion;
1503   window_shape_.reset(xregion);
1504   ResetWindowRegion();
1505 }
1506 
UnconfineCursor()1507 void XWindow::UnconfineCursor() {
1508   if (!has_pointer_barriers_)
1509     return;
1510 
1511   for (XID pointer_barrier : pointer_barriers_)
1512     XFixesDestroyPointerBarrier(xdisplay_, pointer_barrier);
1513   pointer_barriers_.fill(x11::None);
1514 
1515   has_pointer_barriers_ = false;
1516 }
1517 
UpdateWindowRegion(XRegion * xregion)1518 void XWindow::UpdateWindowRegion(XRegion* xregion) {
1519   // If a custom window shape was supplied then apply it.
1520   if (use_custom_shape()) {
1521     XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0,
1522                         window_shape_.get(), false);
1523     return;
1524   }
1525 
1526   window_shape_.reset(xregion);
1527   if (window_shape_) {
1528     XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0,
1529                         window_shape_.get(), false);
1530     return;
1531   }
1532 
1533   // If we didn't set the shape for any reason, reset the shaping information.
1534   // How this is done depends on the border style, due to quirks and bugs in
1535   // various window managers.
1536   if (use_native_frame()) {
1537     // If the window has system borders, the mask must be set to null (not a
1538     // rectangle), because several window managers (eg, KDE, XFCE, XMonad) will
1539     // not put borders on a window with a custom shape.
1540     XShapeCombineMask(xdisplay_, xwindow_, ShapeBounding, 0, 0, x11::None,
1541                       ShapeSet);
1542   } else {
1543     // Conversely, if the window does not have system borders, the mask must be
1544     // manually set to a rectangle that covers the whole window (not null). This
1545     // is due to a bug in KWin <= 4.11.5 (KDE bug #330573) where setting a null
1546     // shape causes the hint to disable system borders to be ignored (resulting
1547     // in a double border).
1548     XRectangle r = {0, 0,
1549                     static_cast<unsigned short>(bounds_in_pixels_.width()),
1550                     static_cast<unsigned short>(bounds_in_pixels_.height())};
1551     XShapeCombineRectangles(xdisplay_, xwindow_, ShapeBounding, 0, 0, &r, 1,
1552                             ShapeSet, YXBanded);
1553   }
1554 }
1555 
NotifyBoundsChanged(const gfx::Rect & new_bounds_in_px)1556 void XWindow::NotifyBoundsChanged(const gfx::Rect& new_bounds_in_px) {
1557   ResetWindowRegion();
1558   OnXWindowBoundsChanged(new_bounds_in_px);
1559 }
1560 
InitializeAsStatusIcon()1561 bool XWindow::InitializeAsStatusIcon() {
1562   std::string atom_name =
1563       "_NET_SYSTEM_TRAY_S" + base::NumberToString(DefaultScreen(xdisplay_));
1564   XID manager = XGetSelectionOwner(xdisplay_, gfx::GetAtom(atom_name.c_str()));
1565   if (manager == x11::None)
1566     return false;
1567 
1568   ui::SetIntArrayProperty(xwindow_, "_XEMBED_INFO", "CARDINAL",
1569                           {kXembedInfoProtocolVersion, kXembedInfoFlags});
1570 
1571   XSetWindowAttributes attrs;
1572   unsigned long flags = 0;
1573   if (has_alpha()) {
1574     flags |= CWBackPixel;
1575     attrs.background_pixel = 0;
1576   } else {
1577     ui::SetIntProperty(xwindow_, "CHROMIUM_COMPOSITE_WINDOW", "CARDINAL", 1);
1578     flags |= CWBackPixmap;
1579     attrs.background_pixmap = ParentRelative;
1580   }
1581   XChangeWindowAttributes(xdisplay_, xwindow_, flags, &attrs);
1582   XEvent ev;
1583   memset(&ev, 0, sizeof(ev));
1584   ev.xclient.type = ClientMessage;
1585   ev.xclient.window = manager;
1586   ev.xclient.message_type = gfx::GetAtom("_NET_SYSTEM_TRAY_OPCODE");
1587   ev.xclient.format = 32;
1588   ev.xclient.data.l[0] = ui::X11EventSource::GetInstance()->GetTimestamp();
1589   ev.xclient.data.l[1] = kSystemTrayRequestDock;
1590   ev.xclient.data.l[2] = xwindow_;
1591   bool error;
1592   {
1593     gfx::X11ErrorTracker error_tracker;
1594     XSendEvent(xdisplay_, manager, false, NoEventMask, &ev);
1595     error = error_tracker.FoundNewError();
1596   }
1597   return !error;
1598 }
1599 
1600 }  // namespace ui
1601