blob: 44edb4b781ef1d19040e390c4503ffb0ea523fde [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "ui/aura/null_window_targeter.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/platform_window/extensions/x11_extension.h"
#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/desktop_aura/window_event_filter_linux.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(USE_ATK)
#include "ui/accessibility/platform/atk_util_auralinux.h"
#endif
DEFINE_UI_CLASS_PROPERTY_TYPE(views::DesktopWindowTreeHostLinux*)
namespace views {
std::list<gfx::AcceleratedWidget>* DesktopWindowTreeHostLinux::open_windows_ =
nullptr;
DEFINE_UI_CLASS_PROPERTY_KEY(DesktopWindowTreeHostLinux*,
kHostForRootWindow,
nullptr)
namespace {
class SwapWithNewSizeObserverHelper : public ui::CompositorObserver {
public:
using HelperCallback = base::RepeatingCallback<void(const gfx::Size&)>;
SwapWithNewSizeObserverHelper(ui::Compositor* compositor,
const HelperCallback& callback)
: compositor_(compositor), callback_(callback) {
compositor_->AddObserver(this);
}
~SwapWithNewSizeObserverHelper() override {
if (compositor_)
compositor_->RemoveObserver(this);
}
private:
// ui::CompositorObserver:
void OnCompositingCompleteSwapWithNewSize(ui::Compositor* compositor,
const gfx::Size& size) override {
DCHECK_EQ(compositor, compositor_);
callback_.Run(size);
}
void OnCompositingShuttingDown(ui::Compositor* compositor) override {
DCHECK_EQ(compositor, compositor_);
compositor_->RemoveObserver(this);
compositor_ = nullptr;
}
ui::Compositor* compositor_;
const HelperCallback callback_;
DISALLOW_COPY_AND_ASSIGN(SwapWithNewSizeObserverHelper);
};
} // namespace
DesktopWindowTreeHostLinux::DesktopWindowTreeHostLinux(
internal::NativeWidgetDelegate* native_widget_delegate,
DesktopNativeWidgetAura* desktop_native_widget_aura)
: DesktopWindowTreeHostPlatform(native_widget_delegate,
desktop_native_widget_aura) {}
DesktopWindowTreeHostLinux::~DesktopWindowTreeHostLinux() {
window()->ClearProperty(kHostForRootWindow);
}
// static
aura::Window* DesktopWindowTreeHostLinux::GetContentWindowForWidget(
gfx::AcceleratedWidget widget) {
auto* host = DesktopWindowTreeHostLinux::GetHostForWidget(widget);
return host ? host->GetContentWindow() : nullptr;
}
// static
DesktopWindowTreeHostLinux* DesktopWindowTreeHostLinux::GetHostForWidget(
gfx::AcceleratedWidget widget) {
aura::WindowTreeHost* host =
aura::WindowTreeHost::GetForAcceleratedWidget(widget);
return host ? host->window()->GetProperty(kHostForRootWindow) : nullptr;
}
// static
std::vector<aura::Window*> DesktopWindowTreeHostLinux::GetAllOpenWindows() {
std::vector<aura::Window*> windows(open_windows().size());
std::transform(open_windows().begin(), open_windows().end(), windows.begin(),
GetContentWindowForWidget);
return windows;
}
// static
void DesktopWindowTreeHostLinux::CleanUpWindowList(
void (*func)(aura::Window* window)) {
if (!open_windows_)
return;
while (!open_windows_->empty()) {
gfx::AcceleratedWidget widget = open_windows_->front();
func(GetContentWindowForWidget(widget));
if (!open_windows_->empty() && open_windows_->front() == widget)
open_windows_->erase(open_windows_->begin());
}
delete open_windows_;
open_windows_ = nullptr;
}
gfx::Rect DesktopWindowTreeHostLinux::GetXRootWindowOuterBounds() const {
// TODO(msisov): must be removed as soon as all X11 low-level bits are moved
// to Ozone.
DCHECK(GetX11Extension());
return GetX11Extension()->GetXRootWindowOuterBounds();
}
bool DesktopWindowTreeHostLinux::ContainsPointInXRegion(
const gfx::Point& point) const {
// TODO(msisov): must be removed as soon as all X11 low-level bits are moved
// to Ozone.
DCHECK(GetX11Extension());
return GetX11Extension()->ContainsPointInXRegion(point);
}
void DesktopWindowTreeHostLinux::LowerXWindow() {
// TODO(msisov): must be removed as soon as all X11 low-level bits are moved
// to Ozone.
DCHECK(GetX11Extension());
GetX11Extension()->LowerXWindow();
}
base::OnceClosure DesktopWindowTreeHostLinux::DisableEventListening() {
// Allows to open multiple file-pickers. See https://crbug.com/678982
modal_dialog_counter_++;
if (modal_dialog_counter_ == 1) {
// ScopedWindowTargeter is used to temporarily replace the event-targeter
// with NullWindowEventTargeter to make |dialog| modal.
targeter_for_modal_ = std::make_unique<aura::ScopedWindowTargeter>(
window(), std::make_unique<aura::NullWindowTargeter>());
}
return base::BindOnce(&DesktopWindowTreeHostLinux::EnableEventListening,
weak_factory_.GetWeakPtr());
}
void DesktopWindowTreeHostLinux::Init(const Widget::InitParams& params) {
DesktopWindowTreeHostPlatform::Init(params);
if (GetX11Extension() && GetX11Extension()->IsSyncExtensionAvailable()) {
compositor_observer_ = std::make_unique<SwapWithNewSizeObserverHelper>(
compositor(),
base::BindRepeating(
&DesktopWindowTreeHostLinux::OnCompleteSwapWithNewSize,
base::Unretained(this)));
}
}
void DesktopWindowTreeHostLinux::OnNativeWidgetCreated(
const Widget::InitParams& params) {
window()->SetProperty(kHostForRootWindow, this);
CreateNonClientEventFilter();
DesktopWindowTreeHostPlatform::OnNativeWidgetCreated(params);
}
base::flat_map<std::string, std::string>
DesktopWindowTreeHostLinux::GetKeyboardLayoutMap() {
if (views::LinuxUI::instance())
return views::LinuxUI::instance()->GetKeyboardLayoutMap();
return {};
}
void DesktopWindowTreeHostLinux::InitModalType(ui::ModalType modal_type) {
switch (modal_type) {
case ui::MODAL_TYPE_NONE:
break;
default:
// TODO(erg): Figure out under what situations |modal_type| isn't
// none. The comment in desktop_native_widget_aura.cc suggests that this
// is rare.
NOTIMPLEMENTED();
}
}
void DesktopWindowTreeHostLinux::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
aura::WindowTreeHost::OnDisplayMetricsChanged(display, changed_metrics);
if ((changed_metrics & DISPLAY_METRIC_DEVICE_SCALE_FACTOR) &&
display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id() ==
display.id()) {
// When the scale factor changes, also pretend that a resize
// occurred so that the window layout will be refreshed and a
// compositor redraw will be scheduled. This is weird, but works.
// TODO(thomasanderson): Figure out a more direct way of doing
// this.
OnHostResizedInPixels(GetBoundsInPixels().size());
}
}
void DesktopWindowTreeHostLinux::DispatchEvent(ui::Event* event) {
// The input can be disabled and the widget marked as non-active in case of
// opened file-dialogs.
if (event->IsKeyEvent() && !native_widget_delegate()->AsWidget()->IsActive())
return;
// In Windows, the native events sent to chrome are separated into client
// and non-client versions of events, which we record on our LocatedEvent
// structures. On X11/Wayland, we emulate the concept of non-client. Before we
// pass this event to the cross platform event handling framework, we need to
// make sure it is appropriately marked as non-client if it's in the non
// client area, or otherwise, we can get into a state where the a window is
// set as the |mouse_pressed_handler_| in window_event_dispatcher.cc
// despite the mouse button being released.
//
// We can't do this later in the dispatch process because we share that
// with ash, and ash gets confused about event IS_NON_CLIENT-ness on
// events, since ash doesn't expect this bit to be set, because it's never
// been set before. (This works on ash on Windows because none of the mouse
// events on the ash desktop are clicking in what Windows considers to be a
// non client area.) Likewise, we won't want to do the following in any
// WindowTreeHost that hosts ash.
int hit_test_code = HTNOWHERE;
if (event->IsMouseEvent()) {
ui::MouseEvent* mouse_event = event->AsMouseEvent();
if (GetContentWindow() && GetContentWindow()->delegate()) {
int flags = mouse_event->flags();
gfx::Point location_in_dip = mouse_event->location();
GetRootTransform().TransformPointReverse(&location_in_dip);
hit_test_code = GetContentWindow()->delegate()->GetNonClientComponent(
location_in_dip);
if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE)
flags |= ui::EF_IS_NON_CLIENT;
mouse_event->set_flags(flags);
}
// While we unset the urgency hint when we gain focus, we also must remove
// it on mouse clicks because we can call FlashFrame() on an active window.
if (mouse_event->IsAnyButton() || mouse_event->IsMouseWheelEvent())
FlashFrame(false);
}
// Prehandle the event as long as as we are not able to track if it is handled
// or not as SendEventToSink results in copying the event and our copy of the
// event will not set to handled unless a dispatcher or a target are
// destroyed.
if (event->IsMouseEvent() && non_client_window_event_filter_) {
non_client_window_event_filter_->HandleMouseEventWithHitTest(
hit_test_code, event->AsMouseEvent());
}
if (!event->handled())
WindowTreeHostPlatform::DispatchEvent(event);
}
void DesktopWindowTreeHostLinux::OnClosed() {
open_windows().remove(GetAcceleratedWidget());
DestroyNonClientEventFilter();
DesktopWindowTreeHostPlatform::OnClosed();
}
void DesktopWindowTreeHostLinux::OnAcceleratedWidgetAvailable(
gfx::AcceleratedWidget widget) {
open_windows().push_front(widget);
DesktopWindowTreeHostPlatform::OnAcceleratedWidgetAvailable(widget);
}
void DesktopWindowTreeHostLinux::OnActivationChanged(bool active) {
if (active) {
auto widget = GetAcceleratedWidget();
open_windows().remove(widget);
open_windows().insert(open_windows().begin(), widget);
}
DesktopWindowTreeHostPlatform::OnActivationChanged(active);
}
#if BUILDFLAG(USE_ATK)
bool DesktopWindowTreeHostLinux::OnAtkKeyEvent(AtkKeyEventStruct* atk_event) {
if (!IsActive() && !HasCapture())
return false;
return ui::AtkUtilAuraLinux::HandleAtkKeyEvent(atk_event) ==
ui::DiscardAtkKeyEvent::Discard;
}
#endif
void DesktopWindowTreeHostLinux::AddAdditionalInitProperties(
const Widget::InitParams& params,
ui::PlatformWindowInitProperties* properties) {
// Set the background color on startup to make the initial flickering
// happening between the XWindow is mapped and the first expose event
// is completely handled less annoying. If possible, we use the content
// window's background color, otherwise we fallback to white.
base::Optional<int> background_color;
const views::LinuxUI* linux_ui = views::LinuxUI::instance();
if (linux_ui && GetContentWindow()) {
ui::NativeTheme::ColorId target_color;
switch (properties->type) {
case ui::PlatformWindowType::kBubble:
target_color = ui::NativeTheme::kColorId_BubbleBackground;
break;
case ui::PlatformWindowType::kTooltip:
target_color = ui::NativeTheme::kColorId_TooltipBackground;
break;
default:
target_color = ui::NativeTheme::kColorId_WindowBackground;
break;
}
ui::NativeTheme* theme = linux_ui->GetNativeTheme(GetContentWindow());
background_color = theme->GetSystemColor(target_color);
}
properties->prefer_dark_theme = linux_ui && linux_ui->PreferDarkTheme();
properties->background_color = background_color;
properties->icon = ViewsDelegate::GetInstance()->GetDefaultWindowIcon();
properties->wm_class_name = params.wm_class_name;
properties->wm_class_class = params.wm_class_class;
properties->wm_role_name = params.wm_role_name;
DCHECK(!properties->x11_extension_delegate);
properties->x11_extension_delegate = this;
}
void DesktopWindowTreeHostLinux::OnCompleteSwapWithNewSize(
const gfx::Size& size) {
if (GetX11Extension())
GetX11Extension()->OnCompleteSwapAfterResize();
}
void DesktopWindowTreeHostLinux::CreateNonClientEventFilter() {
DCHECK(!non_client_window_event_filter_);
non_client_window_event_filter_ = std::make_unique<WindowEventFilterLinux>(
this, GetWmMoveResizeHandler(*platform_window()));
}
void DesktopWindowTreeHostLinux::DestroyNonClientEventFilter() {
non_client_window_event_filter_.reset();
}
void DesktopWindowTreeHostLinux::GetWindowMask(const gfx::Size& size,
SkPath* window_mask) {
DCHECK(window_mask);
Widget* widget = native_widget_delegate()->AsWidget();
if (widget->non_client_view()) {
// Some frame views define a custom (non-rectangular) window mask. If
// so, use it to define the window shape. If not, fall through.
widget->non_client_view()->GetWindowMask(size, window_mask);
}
}
void DesktopWindowTreeHostLinux::OnLostMouseGrab() {
dispatcher()->OnHostLostMouseGrab();
}
void DesktopWindowTreeHostLinux::EnableEventListening() {
DCHECK_GT(modal_dialog_counter_, 0UL);
if (!--modal_dialog_counter_)
targeter_for_modal_.reset();
}
ui::X11Extension* DesktopWindowTreeHostLinux::GetX11Extension() {
return ui::GetX11Extension(*(platform_window()));
}
const ui::X11Extension* DesktopWindowTreeHostLinux::GetX11Extension() const {
return ui::GetX11Extension(*(platform_window()));
}
std::list<gfx::AcceleratedWidget>& DesktopWindowTreeHostLinux::open_windows() {
if (!open_windows_)
open_windows_ = new std::list<gfx::AcceleratedWidget>();
return *open_windows_;
}
// As DWTHX11 subclasses DWTHPlatform through DWTHLinux now (during transition
// period. see https://crbug.com/990756), we need to guard this factory method.
// TODO(msisov): remove this guard once DWTHX11 is finally merged into
// DWTHPlatform and .
#if !defined(USE_X11)
// static
DesktopWindowTreeHost* DesktopWindowTreeHost::Create(
internal::NativeWidgetDelegate* native_widget_delegate,
DesktopNativeWidgetAura* desktop_native_widget_aura) {
return new DesktopWindowTreeHostLinux(native_widget_delegate,
desktop_native_widget_aura);
}
#endif
} // namespace views