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/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
6 
7 #include <algorithm>
8 #include <list>
9 #include <memory>
10 #include <string>
11 #include <vector>
12 
13 #include "ui/aura/null_window_targeter.h"
14 #include "ui/aura/scoped_window_targeter.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_delegate.h"
17 #include "ui/display/display.h"
18 #include "ui/display/screen.h"
19 #include "ui/events/event.h"
20 #include "ui/platform_window/extensions/x11_extension.h"
21 #include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h"
22 #include "ui/platform_window/platform_window_init_properties.h"
23 #include "ui/views/linux_ui/linux_ui.h"
24 #include "ui/views/views_delegate.h"
25 #include "ui/views/widget/desktop_aura/window_event_filter_linux.h"
26 #include "ui/views/widget/widget.h"
27 
28 #if BUILDFLAG(USE_ATK)
29 #include "ui/accessibility/platform/atk_util_auralinux.h"
30 #endif
31 
32 DEFINE_UI_CLASS_PROPERTY_TYPE(views::DesktopWindowTreeHostLinux*)
33 
34 namespace views {
35 
36 std::list<gfx::AcceleratedWidget>* DesktopWindowTreeHostLinux::open_windows_ =
37     nullptr;
38 
39 DEFINE_UI_CLASS_PROPERTY_KEY(DesktopWindowTreeHostLinux*,
40                              kHostForRootWindow,
41                              nullptr)
42 
43 namespace {
44 
45 class SwapWithNewSizeObserverHelper : public ui::CompositorObserver {
46  public:
47   using HelperCallback = base::RepeatingCallback<void(const gfx::Size&)>;
SwapWithNewSizeObserverHelper(ui::Compositor * compositor,const HelperCallback & callback)48   SwapWithNewSizeObserverHelper(ui::Compositor* compositor,
49                                 const HelperCallback& callback)
50       : compositor_(compositor), callback_(callback) {
51     compositor_->AddObserver(this);
52   }
~SwapWithNewSizeObserverHelper()53   ~SwapWithNewSizeObserverHelper() override {
54     if (compositor_)
55       compositor_->RemoveObserver(this);
56   }
57 
58  private:
59   // ui::CompositorObserver:
OnCompositingCompleteSwapWithNewSize(ui::Compositor * compositor,const gfx::Size & size)60   void OnCompositingCompleteSwapWithNewSize(ui::Compositor* compositor,
61                                             const gfx::Size& size) override {
62     DCHECK_EQ(compositor, compositor_);
63     callback_.Run(size);
64   }
OnCompositingShuttingDown(ui::Compositor * compositor)65   void OnCompositingShuttingDown(ui::Compositor* compositor) override {
66     DCHECK_EQ(compositor, compositor_);
67     compositor_->RemoveObserver(this);
68     compositor_ = nullptr;
69   }
70 
71   ui::Compositor* compositor_;
72   const HelperCallback callback_;
73 
74   DISALLOW_COPY_AND_ASSIGN(SwapWithNewSizeObserverHelper);
75 };
76 
77 }  // namespace
78 
DesktopWindowTreeHostLinux(internal::NativeWidgetDelegate * native_widget_delegate,DesktopNativeWidgetAura * desktop_native_widget_aura)79 DesktopWindowTreeHostLinux::DesktopWindowTreeHostLinux(
80     internal::NativeWidgetDelegate* native_widget_delegate,
81     DesktopNativeWidgetAura* desktop_native_widget_aura)
82     : DesktopWindowTreeHostPlatform(native_widget_delegate,
83                                     desktop_native_widget_aura) {}
84 
~DesktopWindowTreeHostLinux()85 DesktopWindowTreeHostLinux::~DesktopWindowTreeHostLinux() {
86   window()->ClearProperty(kHostForRootWindow);
87 }
88 
89 // static
GetContentWindowForWidget(gfx::AcceleratedWidget widget)90 aura::Window* DesktopWindowTreeHostLinux::GetContentWindowForWidget(
91     gfx::AcceleratedWidget widget) {
92   auto* host = DesktopWindowTreeHostLinux::GetHostForWidget(widget);
93   return host ? host->GetContentWindow() : nullptr;
94 }
95 
96 // static
GetHostForWidget(gfx::AcceleratedWidget widget)97 DesktopWindowTreeHostLinux* DesktopWindowTreeHostLinux::GetHostForWidget(
98     gfx::AcceleratedWidget widget) {
99   aura::WindowTreeHost* host =
100       aura::WindowTreeHost::GetForAcceleratedWidget(widget);
101   return host ? host->window()->GetProperty(kHostForRootWindow) : nullptr;
102 }
103 
104 // static
GetAllOpenWindows()105 std::vector<aura::Window*> DesktopWindowTreeHostLinux::GetAllOpenWindows() {
106   std::vector<aura::Window*> windows(open_windows().size());
107   std::transform(open_windows().begin(), open_windows().end(), windows.begin(),
108                  GetContentWindowForWidget);
109   return windows;
110 }
111 
112 // static
CleanUpWindowList(void (* func)(aura::Window * window))113 void DesktopWindowTreeHostLinux::CleanUpWindowList(
114     void (*func)(aura::Window* window)) {
115   if (!open_windows_)
116     return;
117   while (!open_windows_->empty()) {
118     gfx::AcceleratedWidget widget = open_windows_->front();
119     func(GetContentWindowForWidget(widget));
120     if (!open_windows_->empty() && open_windows_->front() == widget)
121       open_windows_->erase(open_windows_->begin());
122   }
123 
124   delete open_windows_;
125   open_windows_ = nullptr;
126 }
127 
GetXRootWindowOuterBounds() const128 gfx::Rect DesktopWindowTreeHostLinux::GetXRootWindowOuterBounds() const {
129   // TODO(msisov): must be removed as soon as all X11 low-level bits are moved
130   // to Ozone.
131   DCHECK(GetX11Extension());
132   return GetX11Extension()->GetXRootWindowOuterBounds();
133 }
134 
ContainsPointInXRegion(const gfx::Point & point) const135 bool DesktopWindowTreeHostLinux::ContainsPointInXRegion(
136     const gfx::Point& point) const {
137   // TODO(msisov): must be removed as soon as all X11 low-level bits are moved
138   // to Ozone.
139   DCHECK(GetX11Extension());
140   return GetX11Extension()->ContainsPointInXRegion(point);
141 }
142 
LowerXWindow()143 void DesktopWindowTreeHostLinux::LowerXWindow() {
144   // TODO(msisov): must be removed as soon as all X11 low-level bits are moved
145   // to Ozone.
146   DCHECK(GetX11Extension());
147   GetX11Extension()->LowerXWindow();
148 }
149 
DisableEventListening()150 base::OnceClosure DesktopWindowTreeHostLinux::DisableEventListening() {
151   // Allows to open multiple file-pickers. See https://crbug.com/678982
152   modal_dialog_counter_++;
153   if (modal_dialog_counter_ == 1) {
154     // ScopedWindowTargeter is used to temporarily replace the event-targeter
155     // with NullWindowEventTargeter to make |dialog| modal.
156     targeter_for_modal_ = std::make_unique<aura::ScopedWindowTargeter>(
157         window(), std::make_unique<aura::NullWindowTargeter>());
158   }
159 
160   return base::BindOnce(&DesktopWindowTreeHostLinux::EnableEventListening,
161                         weak_factory_.GetWeakPtr());
162 }
163 
Init(const Widget::InitParams & params)164 void DesktopWindowTreeHostLinux::Init(const Widget::InitParams& params) {
165   DesktopWindowTreeHostPlatform::Init(params);
166 
167   if (GetX11Extension() && GetX11Extension()->IsSyncExtensionAvailable()) {
168     compositor_observer_ = std::make_unique<SwapWithNewSizeObserverHelper>(
169         compositor(),
170         base::BindRepeating(
171             &DesktopWindowTreeHostLinux::OnCompleteSwapWithNewSize,
172             base::Unretained(this)));
173   }
174 }
175 
OnNativeWidgetCreated(const Widget::InitParams & params)176 void DesktopWindowTreeHostLinux::OnNativeWidgetCreated(
177     const Widget::InitParams& params) {
178   window()->SetProperty(kHostForRootWindow, this);
179 
180   CreateNonClientEventFilter();
181   DesktopWindowTreeHostPlatform::OnNativeWidgetCreated(params);
182 }
183 
184 base::flat_map<std::string, std::string>
GetKeyboardLayoutMap()185 DesktopWindowTreeHostLinux::GetKeyboardLayoutMap() {
186   if (views::LinuxUI::instance())
187     return views::LinuxUI::instance()->GetKeyboardLayoutMap();
188   return {};
189 }
190 
InitModalType(ui::ModalType modal_type)191 void DesktopWindowTreeHostLinux::InitModalType(ui::ModalType modal_type) {
192   switch (modal_type) {
193     case ui::MODAL_TYPE_NONE:
194       break;
195     default:
196       // TODO(erg): Figure out under what situations |modal_type| isn't
197       // none. The comment in desktop_native_widget_aura.cc suggests that this
198       // is rare.
199       NOTIMPLEMENTED();
200   }
201 }
202 
OnDisplayMetricsChanged(const display::Display & display,uint32_t changed_metrics)203 void DesktopWindowTreeHostLinux::OnDisplayMetricsChanged(
204     const display::Display& display,
205     uint32_t changed_metrics) {
206   aura::WindowTreeHost::OnDisplayMetricsChanged(display, changed_metrics);
207 
208   if ((changed_metrics & DISPLAY_METRIC_DEVICE_SCALE_FACTOR) &&
209       display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id() ==
210           display.id()) {
211     // When the scale factor changes, also pretend that a resize
212     // occurred so that the window layout will be refreshed and a
213     // compositor redraw will be scheduled.  This is weird, but works.
214     // TODO(thomasanderson): Figure out a more direct way of doing
215     // this.
216     OnHostResizedInPixels(GetBoundsInPixels().size());
217   }
218 }
219 
DispatchEvent(ui::Event * event)220 void DesktopWindowTreeHostLinux::DispatchEvent(ui::Event* event) {
221   // The input can be disabled and the widget marked as non-active in case of
222   // opened file-dialogs.
223   if (event->IsKeyEvent() && !native_widget_delegate()->AsWidget()->IsActive())
224     return;
225 
226   // In Windows, the native events sent to chrome are separated into client
227   // and non-client versions of events, which we record on our LocatedEvent
228   // structures. On X11/Wayland, we emulate the concept of non-client. Before we
229   // pass this event to the cross platform event handling framework, we need to
230   // make sure it is appropriately marked as non-client if it's in the non
231   // client area, or otherwise, we can get into a state where the a window is
232   // set as the |mouse_pressed_handler_| in window_event_dispatcher.cc
233   // despite the mouse button being released.
234   //
235   // We can't do this later in the dispatch process because we share that
236   // with ash, and ash gets confused about event IS_NON_CLIENT-ness on
237   // events, since ash doesn't expect this bit to be set, because it's never
238   // been set before. (This works on ash on Windows because none of the mouse
239   // events on the ash desktop are clicking in what Windows considers to be a
240   // non client area.) Likewise, we won't want to do the following in any
241   // WindowTreeHost that hosts ash.
242   int hit_test_code = HTNOWHERE;
243   if (event->IsMouseEvent()) {
244     ui::MouseEvent* mouse_event = event->AsMouseEvent();
245     if (GetContentWindow() && GetContentWindow()->delegate()) {
246       int flags = mouse_event->flags();
247       gfx::Point location_in_dip = mouse_event->location();
248       GetRootTransform().TransformPointReverse(&location_in_dip);
249       hit_test_code = GetContentWindow()->delegate()->GetNonClientComponent(
250           location_in_dip);
251       if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE)
252         flags |= ui::EF_IS_NON_CLIENT;
253       mouse_event->set_flags(flags);
254     }
255 
256     // While we unset the urgency hint when we gain focus, we also must remove
257     // it on mouse clicks because we can call FlashFrame() on an active window.
258     if (mouse_event->IsAnyButton() || mouse_event->IsMouseWheelEvent())
259       FlashFrame(false);
260   }
261 
262   // Prehandle the event as long as as we are not able to track if it is handled
263   // or not as SendEventToSink results in copying the event and our copy of the
264   // event will not set to handled unless a dispatcher or a target are
265   // destroyed.
266   if (event->IsMouseEvent() && non_client_window_event_filter_) {
267     non_client_window_event_filter_->HandleMouseEventWithHitTest(
268         hit_test_code, event->AsMouseEvent());
269   }
270 
271   if (!event->handled())
272     WindowTreeHostPlatform::DispatchEvent(event);
273 }
274 
OnClosed()275 void DesktopWindowTreeHostLinux::OnClosed() {
276   open_windows().remove(GetAcceleratedWidget());
277   DestroyNonClientEventFilter();
278   DesktopWindowTreeHostPlatform::OnClosed();
279 }
280 
OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget)281 void DesktopWindowTreeHostLinux::OnAcceleratedWidgetAvailable(
282     gfx::AcceleratedWidget widget) {
283   open_windows().push_front(widget);
284   DesktopWindowTreeHostPlatform::OnAcceleratedWidgetAvailable(widget);
285 }
286 
OnActivationChanged(bool active)287 void DesktopWindowTreeHostLinux::OnActivationChanged(bool active) {
288   if (active) {
289     auto widget = GetAcceleratedWidget();
290     open_windows().remove(widget);
291     open_windows().insert(open_windows().begin(), widget);
292   }
293   DesktopWindowTreeHostPlatform::OnActivationChanged(active);
294 }
295 
GetX11Extension()296 ui::X11Extension* DesktopWindowTreeHostLinux::GetX11Extension() {
297   return ui::GetX11Extension(*(platform_window()));
298 }
299 
GetX11Extension() const300 const ui::X11Extension* DesktopWindowTreeHostLinux::GetX11Extension() const {
301   return ui::GetX11Extension(*(platform_window()));
302 }
303 
304 #if BUILDFLAG(USE_ATK)
OnAtkKeyEvent(AtkKeyEventStruct * atk_event)305 bool DesktopWindowTreeHostLinux::OnAtkKeyEvent(AtkKeyEventStruct* atk_event) {
306   if (!IsActive() && !HasCapture())
307     return false;
308   return ui::AtkUtilAuraLinux::HandleAtkKeyEvent(atk_event) ==
309          ui::DiscardAtkKeyEvent::Discard;
310 }
311 #endif
312 
IsOverrideRedirect() const313 bool DesktopWindowTreeHostLinux::IsOverrideRedirect() const {
314   // BrowserDesktopWindowTreeHostLinux implements this for browser windows.
315   return false;
316 }
317 
AddAdditionalInitProperties(const Widget::InitParams & params,ui::PlatformWindowInitProperties * properties)318 void DesktopWindowTreeHostLinux::AddAdditionalInitProperties(
319     const Widget::InitParams& params,
320     ui::PlatformWindowInitProperties* properties) {
321   // Set the background color on startup to make the initial flickering
322   // happening between the XWindow is mapped and the first expose event
323   // is completely handled less annoying. If possible, we use the content
324   // window's background color, otherwise we fallback to white.
325   base::Optional<int> background_color;
326   const views::LinuxUI* linux_ui = views::LinuxUI::instance();
327   if (linux_ui && GetContentWindow()) {
328     ui::NativeTheme::ColorId target_color;
329     switch (properties->type) {
330       case ui::PlatformWindowType::kBubble:
331         target_color = ui::NativeTheme::kColorId_BubbleBackground;
332         break;
333       case ui::PlatformWindowType::kTooltip:
334         target_color = ui::NativeTheme::kColorId_TooltipBackground;
335         break;
336       default:
337         target_color = ui::NativeTheme::kColorId_WindowBackground;
338         break;
339     }
340     ui::NativeTheme* theme = linux_ui->GetNativeTheme(GetContentWindow());
341     background_color = theme->GetSystemColor(target_color);
342   }
343   properties->prefer_dark_theme = linux_ui && linux_ui->PreferDarkTheme();
344   properties->background_color = background_color;
345   properties->icon = ViewsDelegate::GetInstance()->GetDefaultWindowIcon();
346 
347   properties->wm_class_name = params.wm_class_name;
348   properties->wm_class_class = params.wm_class_class;
349   properties->wm_role_name = params.wm_role_name;
350 
351   DCHECK(!properties->x11_extension_delegate);
352   properties->x11_extension_delegate = this;
353 }
354 
OnCompleteSwapWithNewSize(const gfx::Size & size)355 void DesktopWindowTreeHostLinux::OnCompleteSwapWithNewSize(
356     const gfx::Size& size) {
357   if (GetX11Extension())
358     GetX11Extension()->OnCompleteSwapAfterResize();
359 }
360 
CreateNonClientEventFilter()361 void DesktopWindowTreeHostLinux::CreateNonClientEventFilter() {
362   DCHECK(!non_client_window_event_filter_);
363   non_client_window_event_filter_ = std::make_unique<WindowEventFilterLinux>(
364       this, GetWmMoveResizeHandler(*platform_window()));
365 }
366 
DestroyNonClientEventFilter()367 void DesktopWindowTreeHostLinux::DestroyNonClientEventFilter() {
368   non_client_window_event_filter_.reset();
369 }
370 
GetWindowMask(const gfx::Size & size,SkPath * window_mask)371 void DesktopWindowTreeHostLinux::GetWindowMask(const gfx::Size& size,
372                                                SkPath* window_mask) {
373   DCHECK(window_mask);
374   Widget* widget = native_widget_delegate()->AsWidget();
375   if (widget->non_client_view()) {
376     // Some frame views define a custom (non-rectangular) window mask. If
377     // so, use it to define the window shape. If not, fall through.
378     widget->non_client_view()->GetWindowMask(size, window_mask);
379   }
380 }
381 
OnLostMouseGrab()382 void DesktopWindowTreeHostLinux::OnLostMouseGrab() {
383   dispatcher()->OnHostLostMouseGrab();
384 }
385 
EnableEventListening()386 void DesktopWindowTreeHostLinux::EnableEventListening() {
387   DCHECK_GT(modal_dialog_counter_, 0UL);
388   if (!--modal_dialog_counter_)
389     targeter_for_modal_.reset();
390 }
391 
open_windows()392 std::list<gfx::AcceleratedWidget>& DesktopWindowTreeHostLinux::open_windows() {
393   if (!open_windows_)
394     open_windows_ = new std::list<gfx::AcceleratedWidget>();
395   return *open_windows_;
396 }
397 
398 // As DWTHX11 subclasses DWTHPlatform through DWTHLinux now (during transition
399 // period. see https://crbug.com/990756), we need to guard this factory method.
400 // TODO(msisov): remove this guard once DWTHX11 is finally merged into
401 // DWTHPlatform and .
402 #if !defined(USE_X11)
403 // static
Create(internal::NativeWidgetDelegate * native_widget_delegate,DesktopNativeWidgetAura * desktop_native_widget_aura)404 DesktopWindowTreeHost* DesktopWindowTreeHost::Create(
405     internal::NativeWidgetDelegate* native_widget_delegate,
406     DesktopNativeWidgetAura* desktop_native_widget_aura) {
407   return new DesktopWindowTreeHostLinux(native_widget_delegate,
408                                         desktop_native_widget_aura);
409 }
410 #endif
411 
412 }  // namespace views
413