blob: 3f9f62644deb5f587b0c528723f6bb02a9c60c31 [file] [log] [blame]
// 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 <fuchsia/sys/cpp/fidl.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#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/ozone/events_ozone.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()));
gfx::Rect size_rect(size_pixels_);
// Update this window's Screen's dimensions to match the new size.
ScenicScreen* screen = manager_->screen();
if (screen)
screen->OnWindowBoundsChanged(window_id_, size_rect);
// Translate the node by half of the view dimensions to put it in the center
// of the view.
const 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);
scenic_session_.Present();
delegate_->OnBoundsChanged(size_rect);
}
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:
switch (event.pointer().type) {
case fuchsia::ui::input::PointerEventType::MOUSE:
result = OnMouseEvent(event.pointer());
break;
case fuchsia::ui::input::PointerEventType::TOUCH:
result = OnTouchEvent(event.pointer());
break;
case fuchsia::ui::input::PointerEventType::STYLUS:
case fuchsia::ui::input::PointerEventType::INVERTED_STYLUS:
NOTIMPLEMENTED() << "Stylus input is not yet supported.";
break;
}
break;
case fuchsia::ui::input::InputEvent::Tag::kKeyboard:
result = OnKeyboardEvent(event.keyboard());
break;
case fuchsia::ui::input::InputEvent::Tag::kFocus:
result = OnFocusEvent(event.focus());
break;
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::OnTouchEvent(const fuchsia::ui::input::PointerEvent& event) {
EventType event_type;
switch (event.phase) {
case fuchsia::ui::input::PointerEventPhase::DOWN:
event_type = ET_TOUCH_PRESSED;
break;
case fuchsia::ui::input::PointerEventPhase::MOVE:
event_type = ET_TOUCH_MOVED;
break;
case fuchsia::ui::input::PointerEventPhase::CANCEL:
event_type = ET_TOUCH_CANCELLED;
break;
case fuchsia::ui::input::PointerEventPhase::UP:
event_type = ET_TOUCH_RELEASED;
break;
case fuchsia::ui::input::PointerEventPhase::ADD:
case fuchsia::ui::input::PointerEventPhase::REMOVE:
case fuchsia::ui::input::PointerEventPhase::HOVER:
return false;
}
// TODO(crbug.com/876933): Add more detailed fields such as
// force/orientation/tilt once they are added to PointerEvent.
ui::PointerDetails pointer_details(ui::EventPointerType::POINTER_TYPE_TOUCH,
event.pointer_id);
gfx::Point location =
gfx::Point(event.x * device_pixel_ratio_, event.y * device_pixel_ratio_);
ui::TouchEvent touch_event(event_type, location,
base::TimeTicks::FromZxTime(event.event_time),
pointer_details);
delegate_->DispatchEvent(&touch_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;
}
bool ScenicWindow::OnFocusEvent(const fuchsia::ui::input::FocusEvent& event) {
delegate_->OnActivationChanged(event.focused);
return true;
}
} // namespace ui