| // Copyright 2018 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/ozone/platform/scenic/scenic_window.h" |
| |
| #include <string> |
| |
| #include <fuchsia/sys/cpp/fidl.h> |
| |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/keycodes/keyboard_code_conversion.h" |
| #include "ui/events/platform/platform_event_source.h" |
| #include "ui/ozone/platform/scenic/scenic_window_manager.h" |
| #include "ui/platform_window/platform_window_delegate.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| const uint32_t kUsbHidKeyboardPage = 0x07; |
| |
| int KeyModifiersToFlags(int modifiers) { |
| int flags = 0; |
| if (modifiers & fuchsia::ui::input::kModifierShift) |
| flags |= EF_SHIFT_DOWN; |
| if (modifiers & fuchsia::ui::input::kModifierControl) |
| flags |= EF_CONTROL_DOWN; |
| if (modifiers & fuchsia::ui::input::kModifierAlt) |
| flags |= EF_ALT_DOWN; |
| // TODO(crbug.com/850697): Add AltGraph support. |
| return flags; |
| } |
| |
| } // namespace |
| |
| ScenicWindow::ScenicWindow( |
| ScenicWindowManager* window_manager, |
| PlatformWindowDelegate* delegate, |
| fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> |
| view_owner_request) |
| : manager_(window_manager), |
| delegate_(delegate), |
| window_id_(manager_->AddWindow(this)), |
| view_listener_binding_(this), |
| scenic_session_(manager_->GetScenic(), this), |
| input_listener_binding_(this) { |
| // Create event pair to import parent view node to Scenic. One end is passed |
| // directly to Scenic in ImportResource command and the second one is passed |
| // to ViewManager::CreateView(). ViewManager will passes it to Scenic when the |
| // view is added to a container. |
| zx::eventpair parent_export_token; |
| zx::eventpair parent_import_token; |
| zx_status_t status = |
| zx::eventpair::create(0u, &parent_import_token, &parent_export_token); |
| ZX_CHECK(status == ZX_OK, status) << "zx_eventpair_create()"; |
| |
| // Create a new node and add it as a child to the parent. |
| parent_node_id_ = scenic_session_.ImportResource( |
| fuchsia::ui::gfx::ImportSpec::NODE, std::move(parent_import_token)); |
| node_id_ = scenic_session_.CreateEntityNode(); |
| scenic_session_.AddNodeChild(parent_node_id_, node_id_); |
| |
| // Subscribe to metrics events from the parent node. These events are used to |
| // get |device_pixel_ratio_| for the screen. |
| scenic_session_.SetEventMask(parent_node_id_, |
| fuchsia::ui::gfx::kMetricsEventMask); |
| |
| // Create the view. |
| manager_->GetViewManager()->CreateView( |
| view_.NewRequest(), std::move(view_owner_request), |
| view_listener_binding_.NewBinding(), std::move(parent_export_token), |
| "Chromium"); |
| view_.set_error_handler(fit::bind_member(this, &ScenicWindow::OnViewError)); |
| view_listener_binding_.set_error_handler( |
| fit::bind_member(this, &ScenicWindow::OnViewError)); |
| |
| // Setup input event listener. |
| fuchsia::sys::ServiceProviderPtr view_service_provider; |
| view_->GetServiceProvider(view_service_provider.NewRequest()); |
| view_service_provider->ConnectToService( |
| fuchsia::ui::input::InputConnection::Name_, |
| input_connection_.NewRequest().TakeChannel()); |
| input_connection_->SetEventListener(input_listener_binding_.NewBinding()); |
| |
| // Add shape node for window. |
| shape_id_ = scenic_session_.CreateShapeNode(); |
| scenic_session_.AddNodeChild(node_id_, shape_id_); |
| material_id_ = scenic_session_.CreateMaterial(); |
| scenic_session_.SetNodeMaterial(shape_id_, material_id_); |
| |
| // Call Present() to ensure that the scenic session commands are processed, |
| // which is necessary to receive metrics event from Scenic. |
| scenic_session_.Present(); |
| |
| delegate_->OnAcceleratedWidgetAvailable(window_id_); |
| } |
| |
| ScenicWindow::~ScenicWindow() { |
| scenic_session_.ReleaseResource(node_id_); |
| scenic_session_.ReleaseResource(parent_node_id_); |
| scenic_session_.ReleaseResource(shape_id_); |
| scenic_session_.ReleaseResource(material_id_); |
| |
| manager_->RemoveWindow(window_id_, this); |
| view_.Unbind(); |
| } |
| |
| void ScenicWindow::SetTexture(ScenicSession::ResourceId texture) { |
| scenic_session_.SetMaterialTexture(material_id_, texture); |
| } |
| |
| gfx::Rect ScenicWindow::GetBounds() { |
| return gfx::Rect(size_pixels_); |
| } |
| |
| void ScenicWindow::SetBounds(const gfx::Rect& bounds) { |
| // View dimensions are controlled by the containing view, it's not possible to |
| // set them here. |
| } |
| |
| void ScenicWindow::SetTitle(const base::string16& title) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Show() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Hide() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Close() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::PrepareForShutdown() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::SetCapture() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::ReleaseCapture() { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool ScenicWindow::HasCapture() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void ScenicWindow::ToggleFullscreen() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Maximize() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Minimize() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::Restore() { |
| NOTIMPLEMENTED(); |
| } |
| |
| PlatformWindowState ScenicWindow::GetPlatformWindowState() const { |
| return PLATFORM_WINDOW_STATE_NORMAL; |
| } |
| |
| void ScenicWindow::SetCursor(PlatformCursor cursor) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::MoveCursorTo(const gfx::Point& location) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void ScenicWindow::ConfineCursorToBounds(const gfx::Rect& bounds) { |
| NOTIMPLEMENTED(); |
| } |
| |
| PlatformImeController* ScenicWindow::GetPlatformImeController() { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| void ScenicWindow::SetRestoredBoundsInPixels(const gfx::Rect& bounds) { |
| NOTIMPLEMENTED(); |
| } |
| |
| gfx::Rect ScenicWindow::GetRestoredBoundsInPixels() const { |
| NOTIMPLEMENTED(); |
| return gfx::Rect(); |
| } |
| |
| void ScenicWindow::UpdateSize() { |
| gfx::SizeF scaled = ScaleSize(size_dips_, device_pixel_ratio_); |
| size_pixels_ = gfx::Size(ceilf(scaled.width()), ceilf(scaled.height())); |
| |
| // Translate the node by half of the view dimensions to put it in the center |
| // of the view. |
| float translation[] = {size_dips_.width() / 2.0, size_dips_.height() / 2.0, |
| 0.f}; |
| |
| // Set node shape to rectangle that matches size of the view. |
| ScenicSession::ResourceId rect_id = |
| scenic_session_.CreateRectangle(size_dips_.width(), size_dips_.height()); |
| scenic_session_.SetNodeShape(shape_id_, rect_id); |
| scenic_session_.SetNodeTranslation(shape_id_, translation); |
| scenic_session_.ReleaseResource(rect_id); |
| |
| delegate_->OnBoundsChanged(gfx::Rect(size_pixels_)); |
| } |
| |
| void ScenicWindow::OnPropertiesChanged( |
| fuchsia::ui::viewsv1::ViewProperties properties, |
| OnPropertiesChangedCallback callback) { |
| if (properties.view_layout) { |
| size_dips_.SetSize(properties.view_layout->size.width, |
| properties.view_layout->size.height); |
| if (device_pixel_ratio_ > 0.0) |
| UpdateSize(); |
| } |
| |
| callback(); |
| } |
| |
| void ScenicWindow::OnScenicError(const std::string& error) { |
| LOG(ERROR) << "ScenicSession failed: " << error; |
| delegate_->OnClosed(); |
| } |
| |
| void ScenicWindow::OnScenicEvents( |
| const std::vector<fuchsia::ui::scenic::Event>& events) { |
| for (const auto& event : events) { |
| if (!event.is_gfx() || !event.gfx().is_metrics()) |
| continue; |
| |
| auto& metrics = event.gfx().metrics(); |
| if (metrics.node_id != parent_node_id_) |
| continue; |
| |
| device_pixel_ratio_ = |
| std::max(metrics.metrics.scale_x, metrics.metrics.scale_y); |
| |
| ScenicScreen* screen = manager_->screen(); |
| if (screen) |
| screen->OnWindowMetrics(window_id_, device_pixel_ratio_); |
| |
| if (!size_dips_.IsEmpty()) |
| UpdateSize(); |
| } |
| } |
| |
| void ScenicWindow::OnEvent(fuchsia::ui::input::InputEvent event, |
| OnEventCallback callback) { |
| bool result = false; |
| |
| switch (event.Which()) { |
| case fuchsia::ui::input::InputEvent::Tag::kPointer: |
| // TODO(crbug.com/829980): Add touch support. |
| if (event.pointer().type == fuchsia::ui::input::PointerEventType::MOUSE) |
| result = OnMouseEvent(event.pointer()); |
| break; |
| |
| case fuchsia::ui::input::InputEvent::Tag::kKeyboard: |
| result = OnKeyboardEvent(event.keyboard()); |
| break; |
| |
| case fuchsia::ui::input::InputEvent::Tag::kFocus: |
| case fuchsia::ui::input::InputEvent::Tag::Invalid: |
| break; |
| } |
| |
| callback(result); |
| } |
| |
| void ScenicWindow::OnViewError() { |
| VLOG(1) << "viewsv1::View connection was closed."; |
| delegate_->OnClosed(); |
| } |
| |
| bool ScenicWindow::OnMouseEvent(const fuchsia::ui::input::PointerEvent& event) { |
| int flags = 0; |
| if (event.buttons & 1) |
| flags |= EF_LEFT_MOUSE_BUTTON; |
| if (event.buttons & 2) |
| flags |= EF_RIGHT_MOUSE_BUTTON; |
| if (event.buttons & 4) |
| flags |= EF_MIDDLE_MOUSE_BUTTON; |
| |
| EventType event_type; |
| |
| switch (event.phase) { |
| case fuchsia::ui::input::PointerEventPhase::DOWN: |
| event_type = ET_MOUSE_PRESSED; |
| break; |
| case fuchsia::ui::input::PointerEventPhase::MOVE: |
| event_type = flags ? ET_MOUSE_DRAGGED : ET_MOUSE_MOVED; |
| break; |
| case fuchsia::ui::input::PointerEventPhase::UP: |
| event_type = ET_MOUSE_RELEASED; |
| break; |
| |
| // Following phases are not expected for mouse events. |
| case fuchsia::ui::input::PointerEventPhase::HOVER: |
| case fuchsia::ui::input::PointerEventPhase::CANCEL: |
| case fuchsia::ui::input::PointerEventPhase::ADD: |
| case fuchsia::ui::input::PointerEventPhase::REMOVE: |
| NOTREACHED() << "Unexpected mouse phase " |
| << fidl::ToUnderlying(event.phase); |
| return false; |
| } |
| |
| gfx::Point location = |
| gfx::Point(event.x * device_pixel_ratio_, event.y * device_pixel_ratio_); |
| ui::MouseEvent mouse_event(event_type, location, location, |
| base::TimeTicks::FromZxTime(event.event_time), |
| flags, 0); |
| delegate_->DispatchEvent(&mouse_event); |
| return true; |
| } |
| |
| bool ScenicWindow::OnKeyboardEvent( |
| const fuchsia::ui::input::KeyboardEvent& event) { |
| EventType event_type; |
| |
| switch (event.phase) { |
| case fuchsia::ui::input::KeyboardEventPhase::PRESSED: |
| case fuchsia::ui::input::KeyboardEventPhase::REPEAT: |
| event_type = ET_KEY_PRESSED; |
| break; |
| |
| case fuchsia::ui::input::KeyboardEventPhase::RELEASED: |
| event_type = ET_KEY_RELEASED; |
| break; |
| |
| case fuchsia::ui::input::KeyboardEventPhase::CANCELLED: |
| NOTIMPLEMENTED() << "Key event cancellation is not supported."; |
| event_type = ET_KEY_RELEASED; |
| break; |
| } |
| |
| // Currently KeyboardEvent doesn't specify HID Usage page. |hid_usage| |
| // field always contains values from the Keyboard page. See |
| // https://fuchsia.atlassian.net/browse/SCN-762 . |
| DomCode dom_code = KeycodeConverter::UsbKeycodeToDomCode( |
| (kUsbHidKeyboardPage << 16) | event.hid_usage); |
| DomKey dom_key; |
| KeyboardCode key_code; |
| if (!DomCodeToUsLayoutDomKey(dom_code, KeyModifiersToFlags(event.modifiers), |
| &dom_key, &key_code)) { |
| LOG(ERROR) << "DomCodeToUsLayoutDomKey() failed for usb_key: " |
| << event.hid_usage; |
| key_code = VKEY_UNKNOWN; |
| } |
| |
| if (event.code_point) |
| dom_key = DomKey::FromCharacter(event.code_point); |
| |
| KeyEvent key_event(event_type, key_code, dom_code, |
| KeyModifiersToFlags(event.modifiers), dom_key, |
| base::TimeTicks::FromZxTime(event.event_time)); |
| delegate_->DispatchEvent(&key_event); |
| return true; |
| } |
| |
| } // namespace ui |