blob: 4296e18b707e617affbc63743cc7343e0e7d3bbd [file] [log] [blame]
// Copyright 2017 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 "components/exo/seat.h"
#include <memory>
#include "ash/wm/window_util.h"
#include "base/auto_reset.h"
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "components/exo/data_exchange_delegate.h"
#include "components/exo/data_source.h"
#include "components/exo/drag_drop_operation.h"
#include "components/exo/mime_utils.h"
#include "components/exo/seat_observer.h"
#include "components/exo/shell_surface_base.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/wm_helper.h"
#include "components/exo/xkb_tracker.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "ui/aura/client/focus_client.h"
#include "ui/base/clipboard/clipboard_monitor.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint_serializer.h"
#include "ui/display/screen.h"
#include "ui/events/event_utils.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/point_f.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/shell.h"
#include "ui/aura/env.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace exo {
Seat::Seat(std::unique_ptr<DataExchangeDelegate> delegate)
: changing_clipboard_data_to_selection_source_(false),
data_exchange_delegate_(std::move(delegate)) {
WMHelper::GetInstance()->AddFocusObserver(this);
// Prepend handler as it's critical that we see all events.
WMHelper::GetInstance()->PrependPreTargetHandler(this);
ui::ClipboardMonitor::GetInstance()->AddObserver(this);
// TODO(reveman): Need to handle the mus case where PlatformEventSource is
// null. https://crbug.com/856230
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
#if BUILDFLAG(IS_CHROMEOS_ASH)
ui_lock_controller_ = std::make_unique<UILockController>(this);
// Seat needs to be registered as observers before any Keyboard,
// because Keyboard expects that the XkbTracker is up-to-date when its
// observer method is called.
xkb_tracker_ = std::make_unique<XkbTracker>();
ash::ImeControllerImpl* ime_controller = ash::Shell::Get()->ime_controller();
xkb_tracker_->UpdateKeyboardLayout(ime_controller->keyboard_layout_name());
ime_controller->AddObserver(this);
#endif
}
Seat::Seat() : Seat(nullptr) {}
Seat::~Seat() {
Shutdown();
}
void Seat::Shutdown() {
if (was_shutdown_)
return;
was_shutdown_ = true;
DCHECK(!selection_source_) << "DataSource must be released before Seat";
#if BUILDFLAG(IS_CHROMEOS_ASH)
ash::Shell::Get()->ime_controller()->RemoveObserver(this);
#endif
WMHelper::GetInstance()->RemoveFocusObserver(this);
WMHelper::GetInstance()->RemovePreTargetHandler(this);
ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
}
void Seat::AddObserver(SeatObserver* observer, int priority) {
for (const auto& observer_list : priority_observer_list_)
if (observer_list.HasObserver(observer))
return;
DCHECK(IsValidObserverPriority(priority));
priority_observer_list_[priority].AddObserver(observer);
}
void Seat::RemoveObserver(SeatObserver* observer) {
// We assume that the number of priority variations is small enough.
for (auto& observer_list : priority_observer_list_)
observer_list.RemoveObserver(observer);
}
void Seat::NotifyPointerCaptureEnabled(Pointer* pointer,
aura::Window* capture_window) {
for (auto& observer_list : priority_observer_list_) {
for (auto& observer : observer_list)
observer.OnPointerCaptureEnabled(pointer, capture_window);
}
}
void Seat::NotifyPointerCaptureDisabled(Pointer* pointer,
aura::Window* capture_window) {
for (auto& observer_list : priority_observer_list_) {
for (auto& observer : observer_list)
observer.OnPointerCaptureDisabled(pointer, capture_window);
}
}
Surface* Seat::GetFocusedSurface() {
return GetTargetSurfaceForKeyboardFocus(
WMHelper::GetInstance()->GetFocusedWindow());
}
void Seat::StartDrag(DataSource* source,
Surface* origin,
Surface* icon,
ui::mojom::DragEventSource event_source) {
gfx::Point cursor_location = aura::Env::GetInstance()->GetLastPointerPoint(
event_source, origin->window(), /*fallback=*/absl::nullopt);
// DragDropOperation manages its own lifetime.
drag_drop_operation_ = DragDropOperation::Create(
data_exchange_delegate_.get(), source, origin, icon,
gfx::PointF(cursor_location), event_source);
}
void Seat::AbortPendingDragOperation() {
if (drag_drop_operation_)
drag_drop_operation_->AbortIfPending();
}
void Seat::SetSelection(DataSource* source) {
Surface* focused_surface = GetFocusedSurface();
if (!source || !focused_surface ||
!source->CanBeDataSourceForCopy(focused_surface)) {
if (source)
source->Cancelled();
return;
}
if (selection_source_) {
if (selection_source_->get() == source)
return;
selection_source_->get()->Cancelled();
}
selection_source_ = std::make_unique<ScopedDataSource>(source, this);
ui::EndpointType endpoint_type =
data_exchange_delegate_->GetDataTransferEndpointType(
focused_surface->window());
scoped_refptr<RefCountedScopedClipboardWriter> writer =
base::MakeRefCounted<RefCountedScopedClipboardWriter>(endpoint_type);
size_t num_data_read_callbacks = DataSource::kMaxDataTypes;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Lacros sends additional metadata, in a custom MIME type, to sync clipboard
// source metadata,
if (endpoint_type == ui::EndpointType::kLacros)
++num_data_read_callbacks;
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
base::RepeatingClosure data_read_callback = base::BarrierClosure(
num_data_read_callbacks,
base::BindOnce(&Seat::OnAllReadsFinished, weak_ptr_factory_.GetWeakPtr(),
writer));
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (endpoint_type == ui::EndpointType::kLacros) {
source->ReadDataTransferEndpoint(
base::BindOnce(&Seat::OnDataTransferEndpointRead,
weak_ptr_factory_.GetWeakPtr(), writer,
data_read_callback),
data_read_callback);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
source->GetDataForPreferredMimeTypes(
base::BindOnce(&Seat::OnTextRead, weak_ptr_factory_.GetWeakPtr(), writer,
data_read_callback),
base::BindOnce(&Seat::OnRTFRead, weak_ptr_factory_.GetWeakPtr(), writer,
data_read_callback),
base::BindOnce(&Seat::OnHTMLRead, weak_ptr_factory_.GetWeakPtr(), writer,
data_read_callback),
base::BindOnce(&Seat::OnImageRead, weak_ptr_factory_.GetWeakPtr(), writer,
data_read_callback),
base::BindOnce(&Seat::OnFilenamesRead, weak_ptr_factory_.GetWeakPtr(),
endpoint_type, writer, data_read_callback),
DataSource::ReadFileContentsDataCallback(),
base::BindOnce(&Seat::OnWebCustomDataRead, weak_ptr_factory_.GetWeakPtr(),
writer, data_read_callback),
data_read_callback);
}
class Seat::RefCountedScopedClipboardWriter
: public ui::ScopedClipboardWriter,
public base::RefCounted<RefCountedScopedClipboardWriter> {
public:
explicit RefCountedScopedClipboardWriter(ui::EndpointType type)
: ScopedClipboardWriter(
ui::ClipboardBuffer::kCopyPaste,
std::make_unique<ui::DataTransferEndpoint>(type)) {}
private:
friend class base::RefCounted<RefCountedScopedClipboardWriter>;
virtual ~RefCountedScopedClipboardWriter() = default;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
void Seat::OnDataTransferEndpointRead(
scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
std::u16string data) {
std::string utf8_json = base::UTF16ToUTF8(data);
auto clipboard_source = ui::ConvertJsonToDataTransferEndpoint(utf8_json);
writer->SetDataSource(std::move(clipboard_source));
std::move(callback).Run();
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
void Seat::OnTextRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
std::u16string data) {
writer->WriteText(std::move(data));
std::move(callback).Run();
}
void Seat::OnRTFRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
const std::vector<uint8_t>& data) {
writer->WriteRTF(
std::string(reinterpret_cast<const char*>(data.data()), data.size()));
std::move(callback).Run();
}
void Seat::OnHTMLRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
std::u16string data) {
writer->WriteHTML(std::move(data), std::string());
std::move(callback).Run();
}
void Seat::OnImageRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
const std::vector<uint8_t>& data) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
data_decoder::DecodeImageIsolated(
data, data_decoder::mojom::ImageCodec::kDefault, false,
std::numeric_limits<int64_t>::max(), gfx::Size(),
base::BindOnce(&Seat::OnImageDecoded, weak_ptr_factory_.GetWeakPtr(),
std::move(callback), writer));
#else
std::move(callback).Run();
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void Seat::OnImageDecoded(base::OnceClosure callback,
scoped_refptr<RefCountedScopedClipboardWriter> writer,
const SkBitmap& bitmap) {
if (!bitmap.isNull() && !bitmap.empty())
writer->WriteImage(bitmap);
std::move(callback).Run();
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
void Seat::OnFilenamesRead(
ui::EndpointType source,
scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
const std::vector<uint8_t>& data) {
std::vector<ui::FileInfo> filenames =
data_exchange_delegate_->GetFilenames(source, data);
writer->WriteFilenames(ui::FileInfosToURIList(filenames));
std::move(callback).Run();
}
void Seat::OnWebCustomDataRead(
scoped_refptr<RefCountedScopedClipboardWriter> writer,
base::OnceClosure callback,
const std::string& mime_type,
const std::vector<uint8_t>& data) {
base::Pickle pickle(reinterpret_cast<const char*>(data.data()), data.size());
writer->WritePickledData(pickle,
ui::ClipboardFormatType::WebCustomDataType());
std::move(callback).Run();
}
void Seat::OnAllReadsFinished(
scoped_refptr<RefCountedScopedClipboardWriter> writer) {
// We need to destroy the ScopedClipboardWriter in this call, before
// |auto_reset| is destroyed, so if there are outstanding references that
// would prevent that, reschedule this task.
if (!writer->HasOneRef()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&Seat::OnAllReadsFinished,
weak_ptr_factory_.GetWeakPtr(), std::move(writer)));
return;
}
base::AutoReset<bool> auto_reset(
&changing_clipboard_data_to_selection_source_, true);
writer.reset();
}
////////////////////////////////////////////////////////////////////////////////
// aura::client::FocusChangeObserver overrides:
void Seat::OnWindowFocused(aura::Window* gained_focus,
aura::Window* lost_focus) {
Surface* const gaining_focus_surface =
GetTargetSurfaceForKeyboardFocus(gained_focus);
Surface* const lost_focus_surface =
GetTargetSurfaceForKeyboardFocus(lost_focus);
for (auto& observer_list : priority_observer_list_) {
for (auto& observer : observer_list)
observer.OnSurfaceFocused(gaining_focus_surface, lost_focus_surface,
!!gained_focus);
}
}
////////////////////////////////////////////////////////////////////////////////
// ui::PlatformEventObserver overrides:
void Seat::WillProcessEvent(const ui::PlatformEvent& event) {
switch (ui::EventTypeFromNative(event)) {
case ui::ET_KEY_PRESSED:
case ui::ET_KEY_RELEASED:
physical_code_for_currently_processing_event_ = ui::CodeFromNative(event);
break;
default:
break;
}
}
void Seat::DidProcessEvent(const ui::PlatformEvent& event) {
switch (ui::EventTypeFromNative(event)) {
case ui::ET_KEY_PRESSED:
physical_code_for_currently_processing_event_ = ui::DomCode::NONE;
break;
case ui::ET_KEY_RELEASED:
// Remove this from the pressed key map because when IME is active we can
// end up getting the DidProcessEvent call before we get the OnKeyEvent
// callback and then the key will end up being stuck pressed.
if (physical_code_for_currently_processing_event_ != ui::DomCode::NONE) {
pressed_keys_.erase(physical_code_for_currently_processing_event_);
physical_code_for_currently_processing_event_ = ui::DomCode::NONE;
}
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// ui::EventHandler overrides:
void Seat::OnKeyEvent(ui::KeyEvent* event) {
// Ignore synthetic key repeat events.
if (event->is_repeat())
return;
if (physical_code_for_currently_processing_event_ != ui::DomCode::NONE) {
switch (event->type()) {
case ui::ET_KEY_PRESSED:
pressed_keys_.emplace(
physical_code_for_currently_processing_event_,
KeyState{event->code(), /*consumed_by_ime=*/false});
break;
case ui::ET_KEY_RELEASED:
pressed_keys_.erase(physical_code_for_currently_processing_event_);
break;
default:
NOTREACHED();
break;
}
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
xkb_tracker_->UpdateKeyboardModifiers(event->flags());
for (auto& observer_list : priority_observer_list_) {
for (auto& observer : observer_list)
observer.OnKeyboardModifierUpdated();
}
#endif
}
////////////////////////////////////////////////////////////////////////////////
// ui::ClipboardObserver overrides:
void Seat::OnClipboardDataChanged() {
if (!selection_source_ || changing_clipboard_data_to_selection_source_)
return;
selection_source_->get()->Cancelled();
selection_source_.reset();
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
UILockController* Seat::GetUILockControllerForTesting() {
return ui_lock_controller_.get();
}
////////////////////////////////////////////////////////////////////////////////
// ash::ImeControllerImpl::Observer overrides:
void Seat::OnCapsLockChanged(bool enabled) {}
void Seat::OnKeyboardLayoutNameChanged(const std::string& layout_name) {
xkb_tracker_->UpdateKeyboardLayout(layout_name);
}
#endif
////////////////////////////////////////////////////////////////////////////////
// DataSourceObserver overrides:
void Seat::OnDataSourceDestroying(DataSource* source) {
if (selection_source_ && selection_source_->get() == source)
selection_source_.reset();
}
} // namespace exo