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