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