// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "components/exo/wayland/surface_augmenter.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "components/exo/buffer.h"
#include "components/exo/sub_surface.h"
#include "components/exo/sub_surface_observer.h"
#include "components/exo/surface.h"
#include "components/exo/surface_observer.h"
#include "components/exo/wayland/server_util.h"
#include "ui/accessibility/aura/aura_window_properties.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/size.h"
namespace exo::wayland {
namespace {
// A property key containing a boolean set to true if a surface augmenter is
// associated with with subsurface object.
DEFINE_UI_CLASS_PROPERTY_KEY(bool, kSubSurfaceHasAugmentedSubSurfaceKey, false)
////////////////////////////////////////////////////////////////////////////////
// augmented_surface_interface:
// Implements the augmenter interface to a Surface. The "augmented"-state is set
// to null upon destruction. A window property will be set during the lifetime
// of this class to prevent multiple instances from being created for the same
// Surface.
class AugmentedSurface : public SurfaceObserver {
public:
explicit AugmentedSurface(Surface* surface) : surface_(surface) {
surface_->AddSurfaceObserver(this);
surface_->set_is_augmented(true);
// No need to create AX Tree for augmented surfaces because they're
// equivalent to quads.
// TODO(b/296326746): Revert this CL and set the property to the root
// surface once arc accessibility is refactored.
surface_->window()->SetProperty(ui::kAXConsiderInvisibleAndIgnoreChildren,
true);
surface_->set_leave_enter_callback(Surface::LeaveEnterCallback());
surface_->set_legacy_buffer_release_skippable(true);
}
AugmentedSurface(const AugmentedSurface&) = delete;
AugmentedSurface& operator=(const AugmentedSurface&) = delete;
~AugmentedSurface() override {
if (surface_) {
surface_->RemoveSurfaceObserver(this);
}
}
void SetCorners(float x,
float y,
float width,
float height,
float top_left,
float top_right,
float bottom_right,
float bottom_left) {
surface_->SetRoundedCorners(
gfx::RRectF(gfx::RectF(x, y, width, height),
gfx::RoundedCornersF(top_left, top_right, bottom_right,
bottom_left)),
/*commit_override=*/false);
}
void SetDestination(float width, float height) {
surface_->SetViewport(gfx::SizeF(width, height));
}
void SetBackgroundColor(std::optional<SkColor4f> background_color) {
surface_->SetBackgroundColor(background_color);
}
void SetClipRect(float x, float y, float width, float height) {
std::optional<gfx::RectF> clip_rect;
if (width >= 0 && height >= 0) {
clip_rect = gfx::RectF(x, y, width, height);
}
surface_->SetClipRect(clip_rect);
}
void SetFrameTraceId(int64_t frame_trace_id) {
surface_->SetFrameTraceId(frame_trace_id);
}
// SurfaceObserver:
void OnSurfaceDestroying(Surface* surface) override {
surface->RemoveSurfaceObserver(this);
surface_ = nullptr;
}
private:
raw_ptr<Surface> surface_;
};
void augmented_surface_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
void augmented_surface_set_corners_DEPRECATED(wl_client* client,
wl_resource* resource,
wl_fixed_t top_left,
wl_fixed_t top_right,
wl_fixed_t bottom_right,
wl_fixed_t bottom_left) {
LOG(WARNING) << "Deprecated. The server doesn't support this request.";
}
void augmented_surface_set_destination_size(wl_client* client,
wl_resource* resource,
wl_fixed_t width,
wl_fixed_t height) {
if (width < 0 || height < 0) {
wl_resource_post_error(resource, AUGMENTED_SURFACE_ERROR_BAD_VALUE,
"Dimension can't be negative (%d, %d)", width,
height);
return;
}
GetUserDataAs<AugmentedSurface>(resource)->SetDestination(
wl_fixed_to_double(width), wl_fixed_to_double(height));
}
void augmented_surface_set_rounded_corners_bounds_DEPRECATED(
wl_client* client,
wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height,
wl_fixed_t top_left,
wl_fixed_t top_right,
wl_fixed_t bottom_right,
wl_fixed_t bottom_left) {
LOG(WARNING) << "Deprecated. The server does not support this request.";
}
void augmented_surface_set_background_color(wl_client* client,
wl_resource* resource,
wl_array* color_data) {
std::optional<SkColor4f> sk_color;
// Empty data means no color.
if (color_data->size) {
float* data = reinterpret_cast<float*>(color_data->data);
sk_color = {data[0], data[1], data[2], data[3]};
}
GetUserDataAs<AugmentedSurface>(resource)->SetBackgroundColor(sk_color);
}
void augmented_surface_set_trusted_damage_DEPRECATED(wl_client* client,
wl_resource* resource,
int enabled) {
LOG(WARNING) << "Deprecated. The server doesn't support this request.";
}
void augmented_surface_set_rounded_corners_clip_bounds(wl_client* client,
wl_resource* resource,
wl_fixed_t x,
wl_fixed_t y,
wl_fixed_t width,
wl_fixed_t height,
wl_fixed_t top_left,
wl_fixed_t top_right,
wl_fixed_t bottom_right,
wl_fixed_t bottom_left) {
if (width < 0 || height < 0 || top_left < 0 || bottom_left < 0 ||
bottom_right < 0 || top_right < 0) {
wl_resource_post_error(resource, AUGMENTED_SURFACE_ERROR_BAD_VALUE,
"The size and corners must have positive values "
"(%d, %d, %d, %d, %d, %d)",
width, height, top_left, top_right, bottom_right,
bottom_left);
return;
}
// Rounded corners on local surface coordinates is supported since version 9.
if (wl_resource_get_version(resource) < 9) {
LOG(ERROR) << "Rounded corners clip bounds are set on the root surface "
<< "coordinates which is deperecated. Use 9 or newer version "
<< "for surface augmenter.";
}
GetUserDataAs<AugmentedSurface>(resource)->SetCorners(
wl_fixed_to_double(x), wl_fixed_to_double(y), wl_fixed_to_double(width),
wl_fixed_to_double(height), wl_fixed_to_double(top_left),
wl_fixed_to_double(top_right), wl_fixed_to_double(bottom_right),
wl_fixed_to_double(bottom_left));
}
void augmented_surface_set_clip_rect(wl_client* client,
wl_resource* resource,
wl_fixed_t x,
wl_fixed_t y,
wl_fixed_t width,
wl_fixed_t height) {
GetUserDataAs<AugmentedSurface>(resource)->SetClipRect(
wl_fixed_to_double(x), wl_fixed_to_double(y), wl_fixed_to_double(width),
wl_fixed_to_double(height));
}
void augmented_surface_set_frame_trace_id(wl_client* client,
wl_resource* resource,
uint32_t id_hi,
uint32_t id_lo) {
base::CheckedNumeric<int64_t> id(id_hi);
id <<= 32;
id += id_lo;
if (!id.IsValid()) {
wl_resource_post_error(
resource, AUGMENTED_SURFACE_ERROR_BAD_VALUE,
"The frame trace ID cannot be converted to a valid int64_t (%u, %u)",
id_hi, id_lo);
return;
}
GetUserDataAs<AugmentedSurface>(resource)->SetFrameTraceId(id.ValueOrDie());
}
const struct augmented_surface_interface augmented_implementation = {
augmented_surface_destroy,
augmented_surface_set_corners_DEPRECATED,
augmented_surface_set_destination_size,
augmented_surface_set_rounded_corners_bounds_DEPRECATED,
augmented_surface_set_background_color,
augmented_surface_set_trusted_damage_DEPRECATED,
augmented_surface_set_rounded_corners_clip_bounds,
augmented_surface_set_clip_rect,
augmented_surface_set_frame_trace_id,
};
////////////////////////////////////////////////////////////////////////////////
// augmented_sub_surface_interface:
// Implements the augmenter interface to a Surface. The "augmented"-state is set
// to null upon destruction. A window property will be set during the lifetime
// of this class to prevent multiple instances from being created for the same
// Surface.
class AugmentedSubSurface : public SubSurfaceObserver {
public:
explicit AugmentedSubSurface(SubSurface* sub_surface)
: sub_surface_(sub_surface) {
sub_surface_->AddSubSurfaceObserver(this);
sub_surface_->SetProperty(kSubSurfaceHasAugmentedSubSurfaceKey, true);
}
AugmentedSubSurface(const AugmentedSubSurface&) = delete;
AugmentedSubSurface& operator=(const AugmentedSubSurface&) = delete;
~AugmentedSubSurface() override {
if (sub_surface_) {
sub_surface_->SetProperty(kSubSurfaceHasAugmentedSubSurfaceKey, false);
sub_surface_->RemoveSubSurfaceObserver(this);
}
}
void SetPosition(float x, float y) {
sub_surface_->SetPosition(gfx::PointF(x, y));
}
void SetTransform(const gfx::Transform& transform) {
sub_surface_->SetTransform(transform);
}
// SurfaceObserver:
void OnSubSurfaceDestroying(SubSurface* sub_surface) override {
sub_surface->RemoveSubSurfaceObserver(this);
sub_surface_ = nullptr;
}
private:
raw_ptr<SubSurface> sub_surface_;
};
void augmented_sub_surface_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
void augmented_sub_surface_set_position(wl_client* client,
wl_resource* resource,
wl_fixed_t x,
wl_fixed_t y) {
GetUserDataAs<AugmentedSubSurface>(resource)->SetPosition(
wl_fixed_to_double(x), wl_fixed_to_double(y));
}
void augmented_sub_surface_set_clip_rect_DEPRECATED(wl_client* client,
wl_resource* resource,
wl_fixed_t x,
wl_fixed_t y,
wl_fixed_t width,
wl_fixed_t height) {
LOG(WARNING) << "Deprecated. The server doesn't support this request.";
}
void augmented_sub_surface_set_transform(wl_client* client,
wl_resource* resource,
wl_array* matrix_data) {
gfx::Transform transform;
// Empty data represents the identity matrix.
if (matrix_data->size == 6 * sizeof(float)) {
// | a c x |
// | b d y | -> float[6] { a b c d x y }
float* data = reinterpret_cast<float*>(matrix_data->data);
// If b and c are 0, make a simplified transform using AxisTransform2d.
if (data[1] == 0 && data[2] == 0) {
transform = gfx::Transform(gfx::AxisTransform2d::FromScaleAndTranslation(
gfx::Vector2dF(data[0], data[3]), gfx::Vector2dF(data[4], data[5])));
} else {
transform = gfx::Transform::Affine(data[0], data[1], data[2], data[3],
data[4], data[5]);
}
} else if (matrix_data->size != 0) {
wl_resource_post_error(resource, AUGMENTED_SUB_SURFACE_ERROR_INVALID_SIZE,
"The matrix must contain 0 or 6 %zu-byte floats "
"(%zu bytes given)",
sizeof(float), matrix_data->size);
return;
}
GetUserDataAs<AugmentedSubSurface>(resource)->SetTransform(transform);
}
const struct augmented_sub_surface_interface
augmented_sub_surface_implementation = {
augmented_sub_surface_destroy, augmented_sub_surface_set_position,
augmented_sub_surface_set_clip_rect_DEPRECATED,
augmented_sub_surface_set_transform};
////////////////////////////////////////////////////////////////////////////////
// wl_buffer_interface:
void buffer_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
const struct wl_buffer_interface buffer_implementation = {buffer_destroy};
////////////////////////////////////////////////////////////////////////////////
// surface_augmenter_interface:
void augmenter_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
void HandleBufferReleaseCallback(wl_resource* resource) {
wl_buffer_send_release(resource);
wl_client_flush(wl_resource_get_client(resource));
}
void augmenter_create_solid_color_buffer(wl_client* client,
wl_resource* resource,
uint32_t id,
wl_array* color_data,
int width,
int height) {
float* data = reinterpret_cast<float*>(color_data->data);
SkColor4f color = {data[0], data[1], data[2], data[3]};
std::unique_ptr<SolidColorBuffer> buffer =
std::make_unique<SolidColorBuffer>(color, gfx::Size(width, height));
wl_resource* buffer_resource = wl_resource_create(
client, &wl_buffer_interface, wl_resource_get_version(resource), id);
buffer->set_release_callback(base::BindRepeating(
&HandleBufferReleaseCallback, base::Unretained(buffer_resource)));
SetImplementation(buffer_resource, &buffer_implementation, std::move(buffer));
}
void augmenter_get_augmented_surface(wl_client* client,
wl_resource* resource,
uint32_t id,
wl_resource* surface_resource) {
Surface* surface = GetUserDataAs<Surface>(surface_resource);
if (surface->is_augmented()) {
wl_resource_post_error(resource,
SURFACE_AUGMENTER_ERROR_AUGMENTED_SURFACE_EXISTS,
"an augmenter for that surface already exists");
return;
}
wl_resource* augmented_resource =
wl_resource_create(client, &augmented_surface_interface,
wl_resource_get_version(resource), id);
SetImplementation(augmented_resource, &augmented_implementation,
std::make_unique<AugmentedSurface>(surface));
}
void augmenter_get_augmented_sub_surface(wl_client* client,
wl_resource* resource,
uint32_t id,
wl_resource* sub_surface_resource) {
SubSurface* sub_surface = GetUserDataAs<SubSurface>(sub_surface_resource);
if (sub_surface->GetProperty(kSubSurfaceHasAugmentedSubSurfaceKey)) {
wl_resource_post_error(resource,
SURFACE_AUGMENTER_ERROR_AUGMENTED_SURFACE_EXISTS,
"an augmenter for that sub-surface already exists");
return;
}
wl_resource* augmented_resource =
wl_resource_create(client, &augmented_sub_surface_interface,
wl_resource_get_version(resource), id);
SetImplementation(augmented_resource, &augmented_sub_surface_implementation,
std::make_unique<AugmentedSubSurface>(sub_surface));
}
const struct surface_augmenter_interface augmenter_implementation = {
augmenter_destroy, augmenter_create_solid_color_buffer,
augmenter_get_augmented_surface, augmenter_get_augmented_sub_surface};
} // namespace
void bind_surface_augmenter(wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
wl_resource* resource =
wl_resource_create(client, &surface_augmenter_interface,
std::min(version, kSurfaceAugmenterVersion), id);
wl_resource_set_implementation(resource, &augmenter_implementation, data,
nullptr);
}
} // namespace exo::wayland