| // 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 "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/exo/data_source.h" |
| #include "components/exo/seat_observer.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface.h" |
| #include "components/exo/wm_helper.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/events/event_utils.h" |
| #include "ui/events/platform/platform_event_source.h" |
| |
| namespace exo { |
| namespace { |
| |
| Surface* GetEffectiveFocus(aura::Window* window) { |
| if (!window) |
| return nullptr; |
| Surface* const surface = Surface::AsSurface(window); |
| if (surface) |
| return surface; |
| // Fallback to main surface. |
| aura::Window* const top_level_window = window->GetToplevelWindow(); |
| if (!top_level_window) |
| return nullptr; |
| return GetShellMainSurface(top_level_window); |
| } |
| |
| } // namespace |
| |
| Seat::Seat() : changing_clipboard_data_to_selection_source_(false) { |
| 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); |
| } |
| |
| Seat::~Seat() { |
| DCHECK(!selection_source_) << "DataSource must be released before Seat"; |
| 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) { |
| observers_.AddObserver(observer); |
| } |
| |
| void Seat::RemoveObserver(SeatObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| Surface* Seat::GetFocusedSurface() { |
| return GetEffectiveFocus(WMHelper::GetInstance()->GetFocusedWindow()); |
| } |
| |
| void Seat::SetSelection(DataSource* source) { |
| if (!source) { |
| ui::Clipboard::GetForCurrentThread()->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE); |
| // selection_source_ is Cancelled() and reset() in OnClipboardDataChanged(). |
| return; |
| } |
| |
| if (selection_source_) { |
| if (selection_source_->get() == source) |
| return; |
| selection_source_->get()->Cancelled(); |
| } |
| selection_source_ = std::make_unique<ScopedDataSource>(source, this); |
| |
| // Unretained is safe as Seat always outlives DataSource. |
| source->ReadData(base::BindOnce(&Seat::OnDataRead, base::Unretained(this))); |
| } |
| |
| void Seat::OnDataRead(const std::vector<uint8_t>& data) { |
| base::AutoReset<bool> auto_reset( |
| &changing_clipboard_data_to_selection_source_, true); |
| |
| ui::ScopedClipboardWriter writer(ui::CLIPBOARD_TYPE_COPY_PASTE); |
| writer.WriteText(base::UTF8ToUTF16(base::StringPiece( |
| reinterpret_cast<const char*>(data.data()), data.size()))); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura::client::FocusChangeObserver overrides: |
| |
| void Seat::OnWindowFocused(aura::Window* gained_focus, |
| aura::Window* lost_focus) { |
| Surface* const surface = GetEffectiveFocus(gained_focus); |
| for (auto& observer : observers_) { |
| observer.OnSurfaceFocusing(surface); |
| } |
| for (auto& observer : observers_) { |
| observer.OnSurfaceFocused(surface); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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_.insert( |
| {physical_code_for_currently_processing_event_, event->code()}); |
| break; |
| case ui::ET_KEY_RELEASED: |
| pressed_keys_.erase(physical_code_for_currently_processing_event_); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| modifier_flags_ = event->flags(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ui::ClipboardObserver overrides: |
| |
| void Seat::OnClipboardDataChanged() { |
| if (!selection_source_ || changing_clipboard_data_to_selection_source_) |
| return; |
| selection_source_->get()->Cancelled(); |
| selection_source_.reset(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DataSourceObserver overrides: |
| |
| void Seat::OnDataSourceDestroying(DataSource* source) { |
| if (selection_source_ && selection_source_->get() == source) |
| selection_source_.reset(); |
| } |
| |
| } // namespace exo |