chromium/components/exo/wayland/surface_augmenter.cc

// 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