blob: 90dc226ddad0ec789c0888ef3b5a16a469d6acb5 [file]
// Copyright 2026 The Fuchsia 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 "src/ui/scenic/lib/input/dso/pointerinjector_registry.h"
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-engine/types.h>
#include <lib/trace/event.h>
#include <zircon/status.h>
#include <array>
#include <mutex>
#include "src/ui/scenic/lib/utils/check_is_on_thread.h"
#include "src/ui/scenic/lib/utils/fidl_array_cast.h"
#include "src/ui/scenic/lib/utils/helpers.h"
#include "src/ui/scenic/lib/utils/math.h"
#include "zircon/errors.h"
namespace scenic_impl::input_dso {
using fuchsia_ui_pointerinjector::wire::Context;
using fuchsia_ui_pointerinjector::wire::DeviceType;
using fuchsia_ui_pointerinjector::wire::DispatchPolicy;
using fuchsia_ui_pointerinjector::wire::Target;
namespace {
bool IsValidConfig(const fuchsia_ui_pointerinjector::wire::Config& config) {
if (!config.has_device_id() || !config.has_device_type() || !config.has_context() ||
!config.has_target() || !config.has_viewport() || !config.has_dispatch_policy()) {
FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config| is incomplete.";
return false;
}
const auto device_type = config.device_type();
if (device_type != DeviceType::kTouch) {
FX_LOGS(ERROR) << "InjectorRegistry::Register : Unknown DeviceType.";
return false;
}
const auto dispatch_policy = config.dispatch_policy();
if (device_type == DeviceType::kTouch) {
if (dispatch_policy != DispatchPolicy::kExclusiveTarget &&
dispatch_policy != DispatchPolicy::kTopHitAndAncestorsInTarget) {
FX_LOGS(ERROR)
<< "InjectorRegistry::Register : DeviceType::TOUCH with mismatched dispatch policy.";
return false;
}
} else {
FX_NOTREACHED();
}
if (!config.context().is_view() || !config.target().is_view()) {
FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
"is not a view. Only views are supported.";
return false;
}
if (Injector::IsValidViewport(config.viewport()) != ZX_OK) {
// Errors printed in IsValidViewport. Just return result here.
return false;
}
return true;
}
} // namespace
PointerinjectorRegistry::PointerinjectorRegistry(
async_dispatcher_t* input_dispatcher,
std::shared_ptr<view_tree::SnapshotHolder> snapshot_holder,
TouchInjectFunc inject_touch_exclusive, TouchInjectFunc inject_touch_hit_tested,
inspect::Node inspect_node)
: inject_touch_exclusive_(std::move(inject_touch_exclusive)),
inject_touch_hit_tested_(std::move(inject_touch_hit_tested)),
snapshot_holder_(std::move(snapshot_holder)),
input_dispatcher_(input_dispatcher),
inspect_node_(std::move(inspect_node)) {
FX_DCHECK(input_dispatcher);
}
void PointerinjectorRegistry::Bind(fdf::Channel channel) {
utils::CheckIsOnInputThread();
injector_registry_.AddBinding(
reinterpret_cast<fdf_dispatcher_t*>(input_dispatcher_),
fdf::ServerEnd<fuchsia_ui_pointerinjector_dso::Registry>(std::move(channel)), this,
[](fidl::UnbindInfo unbind) {});
}
void PointerinjectorRegistry::Register(RegisterRequestView request, fdf::Arena& arena,
RegisterCompleter::Sync& completer) {
TRACE_DURATION("input", "PointerinjectorRegistry::Register");
auto& config = request->config;
auto& injector = request->injector;
if (!IsValidConfig(config)) {
// Errors printed inside IsValidConfig. Just return here.
injector.reset(); // no epitaph supported by driver transport
return;
}
// Check connectivity here, since injector doesn't have access to it.
const zx_koid_t context_koid = utils::ExtractKoid(config.context().view());
const zx_koid_t target_koid = utils::ExtractKoid(config.target().view());
if (context_koid == ZX_KOID_INVALID || target_koid == ZX_KOID_INVALID) {
FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| or |config.target| "
"was invalid.";
injector.reset(); // no epitaph supported by driver transport
return;
}
auto snapshot = snapshot_holder_->GetSnapshot();
if (!snapshot->IsDescendant(target_koid, context_koid)) {
FX_LOGS(ERROR) << "InjectorRegistry::Register : Argument |config.context| must be connected to "
"the Scene, and |config.target| must be a descendant of |config.context|";
injector.reset(); // no epitaph supported by driver transport
return;
}
const InjectorId id = ++last_injector_id_;
InjectorSettings settings{.dispatch_policy = config.dispatch_policy(),
.device_id = config.device_id(),
.device_type = config.device_type(),
.context_koid = context_koid,
.target_koid = target_koid};
const auto& e = config.viewport().extents();
Viewport viewport{
.extents = std::array<std::array<float, 2>, 2>{std::array<float, 2>{e[0][0], e[0][1]},
std::array<float, 2>{e[1][0], e[1][1]}},
.context_from_viewport_transform = utils::ColumnMajorMat3ArrayToMat4(
utils::ReinterpretFidlArrayAsStdArray(config.viewport().viewport_to_context_transform())),
};
fit::function<void()> on_channel_closed = [this, id] { injectors_.erase(id); };
if (settings.device_type == fuchsia_ui_pointerinjector::DeviceType::kTouch) {
const auto [_, success] = injectors_.emplace(
id,
std::make_unique<TouchInjector>(
input_dispatcher_, snapshot_holder_,
inspect_node_.CreateChild(inspect_node_.UniqueName("touch-injector-")),
std::move(settings), viewport, std::move(injector),
/*inject=*/
[&inject_func = settings.dispatch_policy ==
fuchsia_ui_pointerinjector::DispatchPolicy::kExclusiveTarget
? inject_touch_exclusive_
: inject_touch_hit_tested_](
InternalTouchEvent event, StreamId stream_id, const view_tree::Snapshot& snapshot) {
TRACE_DURATION("input", "TouchInjector::inject_");
inject_func(std::move(event), stream_id, snapshot);
},
std::move(on_channel_closed)));
FX_CHECK(success) << "Injector already exists.";
} else {
FX_NOTREACHED();
}
completer.buffer(arena).Reply();
}
} // namespace scenic_impl::input_dso